Overview of Shiro Security Framework

Published 2026-09-06 22:03 Updated 2026-10-13 17:14 2630 words 14 min read ... Page views AI-reviewed An AI proof-read this post, but it still deserves a careful read. AI-rewritten Someone else wrote the original; AI helped me rewrite and polish it.

AI-reviewed An AI proof-read this post, but it still deserves a careful read.

This article introduces the core functions and architecture of the Apache Shiro security framework, focusing on the complete process of combining JWT to achieve stateless authentication in the front-end separation project. Through custom Tokens, filters, Realm and global configurations, Shiro can complete user authentication and authority verification without relying on server sessions, and supports annotated authorization to achieve flexible and lightweight security control. At the same time, it is pointed out that real projects need to load user information from the database to avoid storage of plaintext passwords, and need to be combined with a token invalidation mechanism to ensure security.

Overview of Shiro Security Framework

Shiro Core Overview

About Shiro

Apache Shiro is a lightweight, flexible Java security framework that handles authentication, authorization, session management, and cryptography-related functions. Compared with Spring Security, Shiro’s core API is more intuitive and relies less on specific containers and Web frameworks, so it is often used for permission control in small and medium-sized projects and back-end management systems.

Shiro’s four core functions

  • Authentication: Confirm who the current user is, such as verifying user name and password.
  • Authorization: Determine what operations the current user can perform, such as role verification and resource permission verification.
  • Session Management: Manage the creation, reading, and destruction of user sessions. Front-end separation projects can either use Sessions or be transformed into stateless solutions such as JWT as needed.
  • Cryptography (cryptography support): Provide hashing, encoding and other cryptography-related tools. When saving passwords in actual projects, random salts and a special password hashing scheme are usually needed.

Shiro core architecture

image-001
image-001

core components

  1. Subject (subject): Represents the user or program currently interacting with the system and is the main entry point for business code to use Shiro security capabilities.
  2. SecurityManager: Shiro’s core scheduler is responsible for coordinating security components such as authentication, authorization, and sessions.
  3. Realm (domain): A bridge between Shiro and application security data that can load user, role, and permission information from databases, caches, or other data sources. An application can configure one or more realms.
  4. Authenticator: Responsible for organizing the certification process and calling Realm to obtain certification information.
  5. Authorizer: Responsible for role and authority judgment, and call Realm to obtain authorization information.
  6. SessionManager: Responsible for life cycle management of Shiro Session; Session storage can be turned off in stateless scenarios.
  7. CacheManager: Used to cache authentication or authorization-related data to reduce duplicate queries.

environment construction

core dependence

pom.xml You can introduce Spring Boot, Shiro, and JWT related dependencies in:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
    <relativePath/>
</parent>

<properties>
    <java.version>17</java.version>
    <jjwt.version>0.12.5</jjwt.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Shiro -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.13.0</version>
    </dependency>

    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>${jjwt.version}</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>${jjwt.version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>${jjwt.version}</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.31</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.52</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Stateless scheme in front and rear end separation

Separation of front and back ends does not mean that a Session must be abandoned. This note uses Shiro + JWT Stateless CertificationPlan, so the following adaptations are needed:

  • After successful login, the backend issues the JWT, and the front-end saves the Token.
  • Subsequent requests carry tokens in the request header.
  • The custom Shiro filter reads the Token and hands it to Shiro for authentication.
  • Custom Realm is responsible for verifying the JWT and providing user identity information.
  • Turn off Shiro’s Session storage to avoid relying on server sessions to save login status.

Implementation of Shiro Stateless JWT Certification

This chapter implements customization in turn according to the actual request link AuthenticationToken, authentication filter, Realm, and global configuration classes.

Custom login Token

AuthenticationToken is Shiro’s authentication credential interface, which is used to encapsulate the identity information and credential information that need to be submitted to Shiro during the authentication process. Yes subject.login(token) One of the parameter types of.

In this example, the JWT string serves as both principal and credentials, and is customized by ShiroToken Encapsulation:

package com.hyxy.sdemo.shiro;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * 前后端分离场景中使用 JWT 作为认证凭证。
 */
public class ShiroToken implements AuthenticationToken {

    private final String token;

    public ShiroToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

ShiroToken It is only responsible for storing authentication data and is not responsible for verifying whether the Token is valid. The real authentication logic is still done by Realm.

Customize Shiro Filter

The filter is responsible for intercepting requests that require authentication and receives Authorization Read the JWT in the request header and invoke Shiro’s login process.

package com.hyxy.sdemo.shiro;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 从请求头中读取 JWT,并交给 Shiro 认证。
 */
public class ShiroAuthFilter extends AuthenticatingFilter {

    private static final String TOKEN_HEADER = "Authorization";

    @Override
    protected AuthenticationToken createToken(
            ServletRequest request,
            ServletResponse response) {
        String token = getRequestToken((HttpServletRequest) request);
        if (StrUtil.isBlank(token)) {
            return null;
        }
        return new ShiroToken(token);
    }

    @Override
    protected boolean onAccessDenied(
            ServletRequest request,
            ServletResponse response) throws Exception {
        String token = getRequestToken((HttpServletRequest) request);
        if (StrUtil.isBlank(token)) {
            responseJson(
                    response,
                    HttpStatus.UNAUTHORIZED.value(),
                    "用户未登录,请先登录"
            );
            return false;
        }

        // 有 Token 时执行登录,随后进入 Realm 校验 JWT。
        return executeLogin(request, response);
    }

    @Override
    protected boolean onLoginFailure(
            AuthenticationToken token,
            AuthenticationException e,
            ServletRequest request,
            ServletResponse response) {
        try {
            String msg = e.getMessage() != null ? e.getMessage() : "认证失败";
            responseJson(response, HttpStatus.UNAUTHORIZED.value(), msg);
        } catch (IOException ignored) {
        }
        return false;
    }

    /**
     * 读取 Authorization: Bearer xxx。
     */
    private String getRequestToken(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_HEADER);
        if (StrUtil.isNotBlank(token) && token.startsWith("Bearer ")) {
            return token.substring(7);
        }
        return token;
    }

    private void responseJson(ServletResponse response, int code, String msg)
            throws IOException {
        HttpServletResponse httpResp = (HttpServletResponse) response;
        httpResp.setContentType("application/json;charset=UTF-8");
        httpResp.setStatus(code);

        Map<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        httpResp.getWriter().write(JSON.toJSONString(result));
    }

    /**
     * 放行跨域预检 OPTIONS 请求。
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response)
            throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpRequest.getMethod())) {
            return true;
        }
        return super.preHandle(request, response);
    }
}

Custom Realm

Realm is responsible for two types of work: verifying JWT during authentication, and providing the current user’s roles and permissions during authorization.

package com.hyxy.sdemo.shiro;

import cn.hutool.core.util.StrUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * 自定义 Realm:认证时校验 JWT,授权时加载角色和权限。
 */
@Component
public class CustomShiroRealm extends AuthorizingRealm {

    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 当前 Realm 只处理自定义 ShiroToken。
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof ShiroToken;
    }

    /**
     * 授权:访问带角色或权限要求的接口时触发。
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 教学示例:暂时写死 admin 的角色和权限。
        if ("admin".equals(username)) {
            Set<String> roles = new HashSet<>();
            roles.add("admin");
            info.setRoles(roles);

            Set<String> permissions = new HashSet<>();
            permissions.add("order:list");
            permissions.add("order:add");
            info.setStringPermissions(permissions);
        }

        return info;
    }

    /**
     * 认证:过滤器调用登录流程时触发。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String jwt = (String) token.getPrincipal();

        if (StrUtil.isBlank(jwt) || !jwtUtil.verify(jwt)) {
            throw new AuthenticationException("Token 无效或已过期,请重新登录");
        }

        String username = jwtUtil.getUsername(jwt);

        // principal 保存用户名,credentials 保存当前 JWT。
        return new SimpleAuthenticationInfo(username, jwt, getName());
    }
}

Shiro Global Configuration

Global Configuration is responsible for registering Realm, filter chains, and annotated authorization support, and turning off Shiro Session storage.

package com.hyxy.sdemo.config;

import com.hyxy.sdemo.shiro.CustomShiroRealm;
import com.hyxy.sdemo.shiro.ShiroAuthFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 无状态 JWT 配置。
 */
@Configuration
public class ShiroConfig {

    /**
     * 绑定 Realm,并关闭 Shiro Session 存储。
     */
    @Bean
    public DefaultWebSecurityManager securityManager(
            CustomShiroRealm customShiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customShiroRealm);

        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(evaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    /**
     * 登录接口匿名访问,其余请求进入自定义 auth 过滤器。
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
            DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);

        Map<String, Filter> filterMap = factoryBean.getFilters();
        filterMap.put("auth", new ShiroAuthFilter());

        // LinkedHashMap 保证规则按插入顺序匹配。
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/**", "auth");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);

        return factoryBean;
    }

    /**
     * 开启 @RequiresPermissions、@RequiresRoles 等注解支持。
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor =
                new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

Shiro Certification System

JWT Stateless Certification Process

The certification process in this example is as follows:

  1. The user submits the account number and password to the login interface.
  2. The backend verifies the account and password, and issues JWT after the verification is successful.
  3. The front end saves JWT.
  4. Subsequent request approved Authorization The request head carries JWT.
  5. ShiroAuthFilter Package JWT as ShiroToken, and perform the Shiro login process.
  6. CustomShiroRealm#doGetAuthenticationInfo() Verify JWT and save the username as principal to the current Subject。
  7. Requests continue to enter the protected interface; if the interface also requires roles or permissions, continue to perform authorization checks.

login interface

The following code uses write dead accounts to simulate database verification. here subject.login(new ShiroToken(token)) Just ask Shiro to verify the just-generated JWT again in the current request; since the Session store is turned off, it will not establish a server-side login session that can be reused across requests.

package com.hyxy.sdemo.controller;

import cn.hutool.core.util.StrUtil;
import com.hyxy.sdemo.shiro.JwtUtil;
import com.hyxy.sdemo.shiro.ShiroToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 登录接口教学示例。
 */
@RestController
public class LoginController {

    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public Map<String, Object> login(
            @RequestParam String username,
            @RequestParam String password) {
        if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
            return result(400, "账号密码不能为空", null);
        }

        // 模拟数据库校验。
        if (!"admin".equals(username) || !"123456".equals(password)) {
            return result(400, "账号或密码错误", null);
        }

        // 1. 生成 JWT。
        String token = jwtUtil.createToken(username);

        // 2. 当前请求中交给 Shiro 校验 JWT。
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new ShiroToken(token));
        } catch (Exception e) {
            return result(400, "登录失败:" + e.getMessage(), null);
        }

        Map<String, String> data = new HashMap<>();
        data.put("token", token);
        return result(200, "登录成功", data);
    }

    @PostMapping("/logout")
    public Map<String, Object> logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return result(200, "退出成功", null);
    }

    private Map<String, Object> result(int code, String msg, Object data) {
        Map<String, Object> res = new HashMap<>();
        res.put("code", code);
        res.put("msg", msg);
        res.put("data", data);
        return res;
    }
}

In a stateless JWT scheme, what really determines whether subsequent requests are authenticated is whether the client continues to carry a valid JWT. Separate call logout() JWTs that have already been issued cannot be automatically invalidated; if the course further implements “forced exit”, additional mechanisms such as token blacklists, version numbers, or shorter expiration times are usually required.

protected interface

package com.hyxy.sdemo.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 需要登录,并且需要 order:list 权限。
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/list")
    @RequiresPermissions("order:list")
    public Map<String, Object> list() {
        Map<String, Object> res = new HashMap<>();
        res.put("code", 200);
        res.put("msg", "查询订单成功");
        res.put("data", "订单列表数据");
        return res;
    }
}
image-002
image-002

access OrderController:

image-003
image-003
image-004
image-004

Vue 3 front-end adaptation

The core work of the front-end is to save the Token returned by the login interface and pass it in subsequent requests. Authorization The request header carries a Token.

// Vue 3 登录核心代码
const login = async () => {
    const res = await axios.post('/api/login', {
        username: 'admin',
        password: '123456'
    });

    if (res.data.code === 200) {
        localStorage.setItem('token', res.data.data.token);
        ElMessage.success('登录成功');
    }
};

// Axios 请求拦截器:统一携带 Token。
axios.interceptors.request.use(config => {
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
});

Use the database for account and password verification

In the previous stateless example, the login interface directly writes down the account and password in order to highlight the JWT process. Real projects often require reading user information from a database.

Need to note: the following UsernamePasswordToken The authentication method belongs to another set of authentication entrances and cannot be directly compared to the previous “Only Supported ShiroToken”of CustomShiroRealm Mixing. If you use this method, you should have the corresponding Realm support UsernamePasswordToken, and Shiro’s credential matcher completes the password comparison.

Realm Query Database

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
        throws AuthenticationException {
    String username = (String) token.getPrincipal();
    User user = userService.selectByUserName(username);

    if (user == null) {
        throw new UnknownAccountException("账号不存在");
    }

    return new SimpleAuthenticationInfo(
            user.getUserName(),
            user.getPassword(),
            getName()
    );
}

SimpleAuthenticationInfo The user credentials are stored in the database. call subject.login(token) Later, Shiro will configure the CredentialsMatcher Compare the password submitted by the user with the password in the database. The production environment should not store clear passwords.

use UsernamePasswordToken login

@PostMapping("/login")
public Map<String, Object> login(
        @RequestParam String username,
        @RequestParam String password) {
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    Subject subject = SecurityUtils.getSubject();

    try {
        subject.login(token);
    } catch (UnknownAccountException e) {
        throw new RuntimeException("账号不存在", e);
    } catch (IncorrectCredentialsException e) {
        throw new RuntimeException("账号或密码错误", e);
    } catch (AuthenticationException e) {
        throw new RuntimeException("登录认证失败", e);
    }

    String jwt = jwtUtil.createToken(username);
    Map<String, String> data = new HashMap<>();
    data.put("token", jwt);
    return result(200, "登录成功", data);
}

If you still want to use the aforementioned JWT stateless filter to process subsequent requests after logging in, you need to retain both “account password authentication” and “JWT authentication” processing logic. You cannot simply use one that only supports ShiroToken Realm replaces the entire certification process.

Shiro authorization system

Core principles of authorization

After the user is successfully authenticated, when accessing an interface with roles or permissions restrictions, Shiro will use the current Subject The principal triggers the doGetAuthorizationInfo(), load role and permission information, and then complete access judgment.

Common authorization methods include annotated authorization and filter chain authorization. This note mainly uses annotated authorization.

Common permission annotations

  • @RequiresAuthentication: Current users are required to have completed certification.
  • @RequiresGuest: Require current users to be in tourist status.
  • @RequiresRoles("admin"): Require the current user to have the specified role.
  • @RequiresPermissions("sys:user:list"): Require the current user to have specified resource permissions.

Example of annotation permissions

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/user")
public class UserController {

    /**
     * 需要 sys:user:list 权限。
     */
    @GetMapping("/list")
    @RequiresPermissions("sys:user:list")
    public Map<String, Object> userList() {
        Map<String, Object> res = new HashMap<>();
        res.put("code", 200);
        res.put("msg", "查询用户列表成功");
        res.put("data", "用户列表数据");
        return res;
    }

    /**
     * 需要 admin 角色。
     */
    @GetMapping("/delete")
    @RequiresRoles("admin")
    public Map<String, Object> deleteUser() {
        Map<String, Object> res = new HashMap<>();
        res.put("code", 200);
        res.put("msg", "删除用户成功");
        return res;
    }
}

In the previous teaching edition CustomShiroRealm middle,admin Users only configure order:list and order:add Permission, so if you directly access the above sys:user:list Interface, will be rejected for lack of this permission. Roles and permissions should be dynamically loaded from a database or other permissions data source from actual projects.

Written by AI-rewritten

If you enjoyed this, leave a comment~

Loading...