手把手教你Shiro整合JWT实现登录认证

Posted 小二上酒8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你Shiro整合JWT实现登录认证相关的知识,希望对你有一定的参考价值。

1.所用技术

  • SpringBoot
  • Mybatis-plus
  • Shiro
  • JWT
  • Redis

2.前置知识

Shiro:

Shiro 是一个基于 Java 的开源的安全框架。

在 Shiro 的核心架构里面,Subject 是访问系统的用户。SecurityManager 是安全管理器,负责用户的认证和授权,相当于 Shiro 的老大哥。

Realm 相当于数据源,用户的认证和授权都在 Realm 的方法中进行。

cryptography 用来管理用户的密码,对密码进行加密解密操作。

JWT:

JWT 全称 json web token,其实就是将用户的登录信息、过期时间以及加密算法经过"揉搓"之后生成的一串字符串,这个字符串又叫做令牌,当然你也可以叫做 token。

用户要想访问系统,请求头中必须携带使用 JWT 生成的 token。token 校验通过了,才能访问系统,否则抛出异常。

3.流程讲解

1.用户点击注册,系统将密码加密后存入数据库中。

2.用户登录

主要是校验账号密码并生成 token。

3.访问资源

其实在 Shiro 整合 JWT 的系统中,关键就是通过 JwtFilter 过滤器去校验请求头中是否包含 token,如果有 token,就交给自定义的 Realm。

然后在 Realm 的认证方法里面校验 token 是否正确、是否过期等。

4.初始化 SpringBoot 项目

1.新建数据库表

CREATE TABLE `t_user` (
  `id` bigint NOT NULL COMMENT 'id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `sex` tinyint DEFAULT '0' COMMENT '性别:0-女 1-男',
  `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账号',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
  `created_date` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_date` datetime DEFAULT NULL COMMENT '修改时间',
  `is_deleted` int DEFAULT '0' COMMENT '删除标识',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

2.新建 SpringBoot 项目

添加依赖:

<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--引入shiro整合Springboot依赖-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.5.3</version>
    </dependency>
    <!--引入jwt-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.10.3</version>
    </dependency>
    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--myql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--mybatis plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>
    <!--逆向工程-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!--freemarker-->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.6</version>
    </dependency>
    <!--hutool-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.5.7</version>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

修改配置文件:

主要是设置数据源、mybatis-plus、redis 以及 jwt 秘钥。

server:
  port: 8081
  servlet:
    context-path: /shiro_jwt
spring:
  # 数据源
  datasource:
    url: jdbc:mysql://localhost:3306/shiro_jwt?allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: 12345678
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  # Redis
  redis:
    host: 172.16.255.3
    port: 6379
    database: 0
    password: 123456
# MybatisPlus
mybatis-plus:
  global-config:
    db-config:
      field-strategy: IGNORED
      column-underline: true
      logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
      db-type: mysql
      id-type: assign_id
  mapper-locations: classpath*:/mapper/**Mapper.xml
  type-aliases-package: com.zhifou.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#jwt
jwt:
  secret: zhifou_secret!

代码生成:

使用 Mybatis-plus 代码生成器生成entity、controller、service、dao、mapper 文件

配置 Redis(不是重点):

加解密工具类:

这里主要使用了 hutool 的加解密工具方法。

全局异常处理:

统一返回结果:

5.配置 JWT

1.JWT 工具类

主要用来生成 token、校验 token

2.JWTFilter

在 shiro 中,shiroFilter 用来拦截所有请求。

但是 shiro 要和 jwt 整合,所以要使用自定义的过滤器 JwtFilter。

JwtFilter 的主要作用就是拦截请求,判断请求头中书否携带 token。如果携带,就交给 Realm 处理。

@Component
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter 
    private String errorMsg;

    // 过滤器拦截请求的入口方法
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
        // 判断请求头是否带上“Token”
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        // 游客访问电商平台首页可以不用携带 token
        if (StringUtils.isEmpty(token)) 
            return true;
        
        try 
            // 交给 myRealm
            SecurityUtils.getSubject().login(new JwtToken(token));
            return true;
         catch (Exception e) 
            errorMsg = e.getMessage();
            e.printStackTrace();
            return false;
        
    

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception 
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setStatus(400);
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.println(JSONUtil.toJsonStr(Result.fail(errorMsg)));
        out.flush();
        out.close();
        return false;
    

    /**
     * 对跨域访问提供支持
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception 
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域发送一个option请求
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) 
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        
        return super.preHandle(request, response);
    


3.JwtToken

shiro 在没有和 jwt 整合之前,用户的账号密码被封装成了 UsernamePasswordToken 对象,UsernamePasswordToken 其实是 AuthenticationToken 的实现类。

这里既然要和 jwt 整合,JWTFilter 传递给 Realm 的 token 必须是 AuthenticationToken 的实现类。

6.配置 Shiro

1.ShiroConfig

ShiroConfig 主要包含 2 部分:过滤器、安全管理器

过滤器:

安全管理器:

2.自定义 Realm

自定义 Realm 的认证方法主要用来校验 token 的合法性:

@Component
public class MyRealm extends AuthorizingRealm 

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 限定这个realm只能处理JwtToken
     */
    @Override
    public boolean supports(AuthenticationToken token) 
        return token instanceof JwtToken;
    

    /**
     * 授权(授权部分这里就省略了)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
        // 获取到用户名,查询用户权限
        return null;
    

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
        // 获取token信息
        String token = (String) authenticationToken.getCredentials();
        // 校验token:未校验通过或者已过期
        if (!jwtUtil.verifyToken(token) || jwtUtil.isExpire(token)) 
            throw new AuthenticationException("token已失效,请重新登录");
        
        //用户信息
        User user = (User) redisUtil.get("token_" + token);
        if (null == user) 
            throw new UnknownAccountException("用户不存在");
        
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, token, this.getName());
        return simpleAuthenticationInfo;
    

7.测试

1.登录

@PostMapping("/login")
public Result login(@RequestParam String username, @RequestParam String password) 
    // 从数据库中查找用户的信息,信息正确生成token
    return userService.login(username, password);

复制代码

2.访问资源

token 失效:

token 正常:

SpringBoot整合Shiro+JWT实现认证及权限校验

序言

本文讲解如何使用SpringBoot整合Shiro框架来实现认证及权限校验,但如今的互联网已经成为前后端分离的时代,所以本文在使用SpringBoot整合Shiro框架的时候会联合JWT一起搭配使用。

Shiro

Shiroapache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份
认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

Shiro架构图

Shiro 核心组件

用户、角色、权限之间的关系

  • 用户拥有不同角色
  • 角色拥有不同权限

1、UsernamePasswordTokenShiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token
2、SecurityManagerShiro 的核心部分,负责安全认证和授权。
3、SujectShiro 的一个抽象概念,包含了用户信息。
4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
5、AuthenticationInfo,用户的角色信息集合,认证时使用。
6、AuthorzationInfo,角色的权限信息集合,授权时使用。
7、DefaultWebSecurityManager,安全管理器,开发者自定义的Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。

JWT

JWT(JSON WEB TOKEN)JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。

JWT的构成
JWT由三部分构成:Header(头部)、Payload(载荷)和Signature(签名)。

1.Header(头) 作用:记录令牌类型、签名算法等 例如:“alg":"HS256","type","JWT

2.Payload(有效载荷)作用:携带一些用户信息 例如"userId":"1","username":"mayikt"

3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串

项目环境

  • Shiro:1.4.1
  • SpringBoot:2.5.6
  • JDK:1.8

搭建项目

pom依赖

<dependencies>
<!--    lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
<!--        aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
<!--        web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.1</version>
        </dependency>
<!--        shiro-chcache-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.1</version>
        </dependency>
<!--        jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
<!--        fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.15</version>
        </dependency>
    </dependencies>

JWTUtil

public class JWTUtils 

    /**
     * 过期时间
     */
    private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;

    /**
     * 校验
     * @param token
     * @param username
     * @param password
     * @return
     */
    public static boolean verify(String token, String username, String password) 
        try 
            Algorithm algorithm = Algorithm.HMAC256(password);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
         catch (Exception e) 
            return false;
        
    

    /**
     * 颁发令牌
     * @param username
     * @param password
     * @return
     */
    public static String sign(String username, String password) 
        try 
            //设置过期时间:获取当前时间+过期时间(毫秒)
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            //设置签名的加密算法:HMAC256
            Algorithm algorithm = Algorithm.HMAC256(password);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
         catch (UnsupportedEncodingException e) 
            return null;
        
    

    /**
     * 获取用户名
     * @param token
     * @return
     */
    public static String getUsername(String token) 
        if (token == null || "".equals(token)) 
            return null;
        
        try 
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
         catch (JWTDecodeException e) 
            return null;
        
    



JWTToken

JWTToken是定义的一个Token类,继承了AuthenticationToken类,实现getPrincipalgetCredentials方法,(这两个方法本来是用于获取token中的信息,和识别token的,但JWTUtils已经为我们提供了这样的方法,所以这两个方法对于JWTToken没有意义)。用于将客户端传来的Token进行封装,便于Realm识别Token类型,进行认证和授权。

public class JWTToken implements AuthenticationToken 

    /**
     * 密钥
     */
    private String token;

    public JWTToken(String token) 
        this.token = token;
    

    @Override
    public Object getPrincipal() 
        return token;
    

    @Override
    public Object getCredentials() 
        return token;
    


JWTFilter过滤器

因为 JWT 的整合,我们需要⾃定义⾃⼰的过滤器 JWTFilterJWTFilter 继承了 BasicHttpAuthenticationFilter,并部分原⽅法进⾏了重写。

public class JWTFilter extends BasicHttpAuthenticationFilter 

    /**
     * Header中的Token标志
     */
    private static String LOGIN_SIGN = "Authorization";

    /**
     * 是否允许访问
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
        if (isLoginAttempt(request, response)) 
            try 
                executeLogin(request, response);
             catch (Exception e) 
                if (e instanceof AuthorizationException) 
                    throw new AuthorizationException("访问资源权限不足!");
                 else 
                    //token 异常 认证失败
                    throw new AuthenticationException("token 异常 认证失败");
                
            
        
        return true;
    

    /**
     * 是登录尝试
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) 
        HttpServletRequest req = (HttpServletRequest) request;
        //判断是否是登录请求
        String authorization = req.getHeader(LOGIN_SIGN);
        return authorization != null;
    

    /**
     * 执行登录
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception 
        HttpServletRequest req = (HttpServletRequest) request;
        String header = req.getHeader(LOGIN_SIGN);
        JWTToken token = new JWTToken(header);
        //提交给realm进⾏登⼊,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);

        return true;
    

自定义ShiroRealm

自定义的Realm对象,该对象继承于AuthorizingRealm,实现了Shiro具体认证和授权的方法。

  • doGetAuthenticationInfo方法用于->认证:校验帐号和密码
  • doGetAuthorizationInfo方法用于->授权:授予角色和权限

另外需要注意:
必须要重写supports方法,因为是自己定义的Tokenshiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义Token

public class ShiroRealm extends AuthorizingRealm 

    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuService menuService;
    @Autowired
    private UserService userService;

    /**
     * 因为是自己定义的Token,shiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义token。
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) 
        return token instanceof JWTToken;
    

    /**
     * 认证:校验帐号和密码
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException 
        String token = (String) authenticationToken.getCredentials();
        //从token中获取用户名
        String username = JWTUtils.getUsername(token);
        //获取数据库中存取的用户,密码是加密后的
        User user = userService.selectByUserName(username);
        if (user != null) 
            // 密码验证
            if (!JWTUtils.verify(token, username, user.getPassword())) 
                // 密码不正确
                throw new IncorrectCredentialsException();
            
            return new SimpleAuthenticationInfo(token, token, getName());
         else 
            throw new UnknownAccountException();
        
    

    /**
     * 授权:授予角色和权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
        //获取用户名
        String userName = JWTUtils.getUsername(principals.toString());
        //根据用户名查询用户
        User user = userService.selectByUserName(userName);
        //实例化一个授权信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if (user != null) 
            //赋予角色
            List<Role> roles = roleService.selectRoleByUserId(user.getId());
            for (Role role : roles) 
                //将角色添加到授权信息中
                info.addRole(role.getRoleKey());
            
            //赋予资源
            List<Menu> permissions = menuService.selectPermsByUserId(user.getId());
            for (Menu permission : permissions) 
                //将权限添加授权信息中
                info.addStringPermission(permission.getPerms());
            
        
        return info;
    


ShiroConfig

ShiroConfig用于进行Shiro的相关配置,主要包括ShiroFilterFactoryBeanDefaultWebSecurityManagerRealm的配置。

@Configuration
public class ShiroConfig 

    /**
     * 生命周期处理器
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() 
        return new LifecycleBeanPostProcessor();
    

    /**
     * 加密方式
     * @return
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() 
        // 散列凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置哈希算法名称,这里使用MD5算法
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 设置哈希迭代,这里迭代2次,相当于 md5(md5(""))
        credentialsMatcher.setHashIterations(2);
        // 设置存储的凭据16进制编码,需要和生成密码时的一样,默认是 Base64
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    

    /**
     * 自定义Realm
     * @param cacheManager
     * @return
     */
    @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm shiroRealm(EhCacheManager cacheManager) 
        ShiroRealm realm = new ShiroRealm();
        realm.setCacheManager(cacheManager);
        return realm;
    

    /**
     * 缓存管理器
     * @return
     */
    @Bean(name = "ehCacheManager")
    @DependsOn("lifecycleBeanPostProcessor")
    public EhCacheManager ehCacheManager() 
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return ehCacheManager;
    

    /**
     * 安全管理器
     * @param shiroRealm
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm) 
        // 实例化会话管理器
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置缓存管理器
        securityManager.setCacheManager(ehCacheManager());

        /**
         * 关闭shiro自带的session
         * 详情见文档: http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);

        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(evaluator);

        securityManager.setSubjectDAO(subjectDAO);

        // 设置自定义Realm
        securityManager.setRealm(shiroRealm);
        return securityManager;
    

    /**
     * 过滤工厂
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) 
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwt", new JWTFilter());

        factoryBean.setFilters(filters);

        Map<String, String> filterChainDefinitionManager = new LinkedHashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterChainDefinitionManager.put("/**", "jwt");
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
        return factoryBean;
    

    /**
     * 自动代理配置
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() 
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    

    /**
     * 开启注解支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor 以上是关于手把手教你Shiro整合JWT实现登录认证的主要内容,如果未能解决你的问题,请参考以下文章

sso单点登录的原理详解,及Shiro同时支持Session和JWT Token两种认证方式,和Session和JWT整合方案

SpringBoot整合Shiro+JWT实现认证及权限校验

SpringBoot整合Shiro+JWT实现认证及权限校验

手把手教你前后分离架构 系统认证鉴权实现

手把手教你如何使用Spring Security(中):接口认证

手把手教你 .NET Core 3.1 JWT身份认证