当前位置: 首页> 教育> 大学 > springboot3+jdk17+shiro+jwt+redis

springboot3+jdk17+shiro+jwt+redis

时间:2025/7/8 14:48:33来源:https://blog.csdn.net/m0_51495726/article/details/139290261 浏览次数:1次

springboot3+jdk17整合jwt+shiro+redis实现登录认证

注意,jdk17的规范是Jakarta EE,虽然最新版本shiro适配springboot3,但是部分包要单独适配

  • 首先讲一下整体流程
  1. 用户首次登录时,会发送一个包含用户名和密码的请求到服务器。这个请求通常是一个POST请求,发送到一个特定的登录URL,例如 /user/login。
  2. 服务器接收到请求后,会验证用户名和密码。如果验证成功,服务器会生成一个JWT,并将其发送回用户。这个JWT包含了用户的一些信息,例如用户ID,以及一个签名。签名是用服务器的私钥生成的,可以用来验证JWT的真实性和完整性。
  3. 用户收到JWT后,会将其存储在本地,例如在浏览器的localStorage中。然后,每次发送请求到服务器时,都会在请求的Header中携带这个JWT,通常是在 Authorization 字段中。
  4. 服务器接收到请求后,会首先检查请求的Header中是否包含JWT。如果没有包含,那么服务器就会拒绝这个请求,因为它无法验证请求的来源。如果包含了JWT,那么服务器就会验证这个JWT的签名。如果签名验证失败,那么服务器也会拒绝这个请求,因为这意味着JWT可能被篡改。如果签名验证成功,那么服务器就会从JWT中提取出用户信息,然后处理这个请求。
  5. 在处理请求时,服务器可能还需要进行授权检查,例如检查用户是否有权限访问某个资源。这个检查通常是通过查询数据库来完成的。
  6. 如果用户长时间没有活动,那么服务器会认为用户已经退出登录,此时的JWT就会过期。用户再次发送请求时,服务器就会发现JWT已经过期,然后拒绝这个请求。用户需要重新登录,以获取新的JWT。
  7. 在每次用户请求时,服务器会检查他们的JWT令牌是否即将过期。如果令牌即将过期,并且用户在Redis中被标记为活跃,那么服务器会自动为他们续签令牌。这个过程在JwtFilter类的onAccessDenied方法中实现。
  8. 如果用户在过去2小时内有任何活动,那么他们会被标记为活跃用户。这是通过在每次用户请求时调用markUserActive方法来实现的。这个方法会在Redis中设置一个键,键的格式是 “active_users:” 加上当前的日期和时间,并将这个键对应的位(由用户ID指定)设置为 true。这个键的过期时间为2小时。
  9. 如果用户在过去2小时内没有任何活动,那么他们的活跃状态就会被移除,即Redis中对应的键会过期并被删除。如果这时用户发送的请求中的JWT令牌即将过期,那么服务器不会为他们续签令牌,而是要求他们重新登录。
  • 整个认证流程登录和登录之后每一次的认证是不一样的,

    img

  • 登录后由工具类jwtUtil生成一个token,返回给前端用于之后每次请求

引入依赖

  • pom.xml,注意jwt相关包版本不能太低

    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency>
    <!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><classifier>jakarta</classifier><version>1.12.0</version><!-- 排除仍使用了javax.servlet的依赖 --><exclusions><exclusion><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId></exclusion><exclusion><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId></exclusion></exclusions></dependency><!-- 引入适配jakarta的依赖包 --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><classifier>jakarta</classifier><version>1.12.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><classifier>jakarta</classifier><version>1.12.0</version><exclusions><exclusion><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId></exclusion></exclusions></dependency>
    <!--        jwt创建、解析、验证 JWT 的功能,并且支持对 JWT 进行签名和加密 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.7.0</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--       jedis 是 Redis 官方推荐的 Java 客户端,提供了比较全面的 Redis 命令的支持。--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.2</version></dependency>
    

ShiroConfig

  • config目录下创建一个Shiro配置类

    package com.example.englishhub.config;/*** @Author: hahaha* @Date: 2024/4/11 16:29*/import com.example.englishhub.security.JwtFilter;
    import com.example.englishhub.security.JwtRealm;
    import jakarta.servlet.Filter;
    import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
    import org.apache.shiro.mgt.DefaultSubjectDAO;
    import org.apache.shiro.spring.LifecycleBeanPostProcessor;
    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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;import java.util.HashMap;
    import java.util.LinkedHashMap;/*** shiro的三个重要配置* 1、Realm* 2、DefaultWebSecurityManager* 3、ShiroFilterFactoryBean*//**1.用户请求,不携带token,就在JwtFilter处进行错误处理返回,让它去登陆2.用户请求,携带token,就到JwtFilter中获取jwt,使用JwtRealm进行认证,若token过期则返回401状态码3.在JwtRealm中进行认证判断这个token是否有效,*/@Configuration
    public class ShiroConfig {@Bean("securityManager")public DefaultWebSecurityManager securityManager(@Qualifier("jwtRealm") JwtRealm jwtRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置自定义RealmsecurityManager.setRealm(jwtRealm);// 关闭shiroDao功能,关闭sessionDefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();// 不需要将ShiroSession中的东西存到任何地方包括Http Session中)defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);securityManager.setSubjectDAO(subjectDAO);// securityManager.setSubjectFactory(subjectFactory());return securityManager;}@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {/***  注册jwt过滤器,除/login,/register外都先经过jwtFilter**   先经过过滤器,如果检测到请求头存在 token,则用 token 去 login,接着走 Realm 去验证*/ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);HashMap<String, Filter> filterMap = new HashMap<>();filterMap.put("jwt", new JwtFilter());shiroFilter.setFilters(filterMap);LinkedHashMap<String, String> map = new LinkedHashMap<>();map.put("/user/login", "anon");map.put("/user/register", "anon");// 所有请求通过我们自己的JWT Filtermap.put("/**", "jwt");shiroFilter.setFilterChainDefinitionMap(map);return shiroFilter;}/*** 解决@RequiresAuthentication注解不生效的配置*/@Bean("lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Bean@DependsOn({"lifecycleBeanPostProcessor"})public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 为Spring-Bean开启对Shiro注解的支持*/@Bean("authorizationAttributeSourceAdvisor")public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}
    }
    
  • 在启动后,首先加载 securityManager,关闭了原有的session(因为使用了jwtToken),将自定义的Realm注入(在其中完成对jwtToken的验证)

  • 之后添加我们自定义的jwt过滤器,起到拦截请求的作用(就相当于拦截器),排除特殊接口如登录注册以及接口文档

JwtToken

package com.example.englishhub.security;/*** @Author: hahaha* @Date: 2024/4/11 17:26*/import org.apache.shiro.authc.AuthenticationToken;/*** 继承AuthenticationToken,跟JwtRealmh中的doGetAuthenticationInfo的参数类型保持一致*/
public class JwtToken implements AuthenticationToken {private String token;public JwtToken(String token){this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

JwtFilter

  • 我在项目目录下新建了一个 security存放安全认证相关的类

    img

    package com.example.englishhub.security;import com.example.englishhub.exception.JwtValidationException;
    import com.example.englishhub.utils.Result;
    import com.example.englishhub.utils.ResultType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.servlet.ServletOutputStream;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.web.filter.AccessControlFilter;
    import org.apache.shiro.web.util.WebUtils;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;import javax.security.sasl.AuthenticationException;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;/*** @Description: JwtFilter* shiro的过滤器,用于拦截请求,* @Author: hahaha* @Date: 2024/4/11 17:26*/@Slf4j
    @Component
    public class JwtFilter extends AccessControlFilter {/*** isAccessAllowed()判断是否携带了有效的JwtToken* onAccessDenied()是没有携带JwtToken的时候进行账号密码登录*/@Overrideprotected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {/*** 1. 返回true,shiro就直接允许访问url* 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url*  这里先让它始终返回false来使用onAccessDenied()方法*  如果带有 token,则对 token 进行检查,否则直接通过**/try {onAccessDenied(servletRequest, servletResponse);
    //            return true;} catch (Exception e) {log.error("isAccessAllowed error:", e);responseError(servletResponse, ResultType.UNAUTHORIZED.getCode(), "Authentication failed: " + e.getMessage());
    //            return false;}return true;}/*** @param servletRequest* @param servletResponse* @throws Exception* @return 返回结果为true表明登录通过*/@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {/***  跟前端约定将jwtToken放在请求的Header的token中,token:token*/log.info("onAccessDenied方法被调用");
    //        HttpServletResponse response = (HttpServletResponse) servletResponse;HttpServletRequest request = (HttpServletRequest) servletRequest;String token = request.getHeader("token");//如果token为空的话,返回true,交给控制层进行判断;也会达到没有权限的作用if (token == null) {responseError(servletResponse, ResultType.UNAUTHORIZED.getCode(), "No token provided");return false;}JwtToken jwtToken = new JwtToken(token);try {//进行登录处理,委托realm进行登录认证,调用JwtRealm进行的认证,doGetAuthenticationInfogetSubject(servletRequest, servletResponse).login(jwtToken);return true;} catch (JwtValidationException e) {// Handle specific custom exceptionsresponseError(servletResponse, e.getStatusCode(), e.getMessage());return false;}
    //        catch (AuthenticationException e) {
    //            responseError(servletResponse, ResultType.UNAUTHORIZED.getCode(), "Authentication failed: " + e.getMessage());
    //            return false;
    //        }}@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {System.out.println("进入拦截器");HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;// 设置CORS头部resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));resp.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");resp.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization,token");resp.setHeader("Access-Control-Allow-Credentials", "true");if ("OPTIONS".equals(req.getMethod())) {resp.setStatus(HttpServletResponse.SC_OK);return false; // 阻止后续的过滤器链执行}return super.preHandle(request, response);}//失败要执行的方法private void responseError(ServletResponse response, String statusCode, String message) throws IOException {HttpServletResponse resp = WebUtils.toHttp(response);resp.setStatus(HttpStatus.UNAUTHORIZED.value());resp.setCharacterEncoding("UTF-8");resp.setContentType("application/json; charset=utf-8");ObjectMapper mapper = new ObjectMapper();try (ServletOutputStream out = resp.getOutputStream()) {Result<String> result = new Result<>();result.setStatusCode(statusCode);result.setMessage(message);String json = mapper.writeValueAsString(result);out.write(json.getBytes(StandardCharsets.UTF_8));} catch (IOException e) {throw new AuthenticationException("Response writing failed with IOException: " + e.getMessage());}}
    }
    
  • 在登录之后的每次请求都会携带 token,这可以通过前端配置一个请求拦截器实现,在后端从header头中取出该token,通过JwtToken类封装成shrio接受的token,委托 JwtRealm认证,传入JwtToken

JwtRealm

package com.example.englishhub.security;import com.example.englishhub.exception.JwtValidationException;
import com.example.englishhub.service.UserService;
import com.example.englishhub.utils.JwtUtil;
import com.example.englishhub.utils.ResultType;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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;/*** @Description: JwtRealm* shiro的Realm,用于处理JwtToken的认证和授权* @Author: hahaha* @Date: 2024/4/11 17:26*/@Slf4j
@Component
public class JwtRealm extends AuthorizingRealm {@Resourceprivate JwtUtil jwtUtil;@Autowiredprivate UserService userService;/*** 多重写一个support* 标识这个Realm是专门用来验证JwtToken*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String jwt = (String) token.getCredentials();// 获取jwt中关于用户id,解码过程中如果token过期或者被篡改会抛出异常
//        String id = null;log.info("处理jwt认证", jwt);try {// Validate the tokenString id = jwtUtil.validateToken(jwt);log.info("jwt认证成功,用户id:", id);// 标记用户为活跃状态userService.markUserActive(Integer.parseInt(id));return new SimpleAuthenticationInfo(jwt, jwt, getName());}catch (ExpiredJwtException e) {throw new JwtValidationException(ResultType.UNAUTHORIZED.getCode(), "Token已过期,请重新登录");} catch (JwtException e) {throw new JwtValidationException(ResultType.UNAUTHORIZED.getCode(), "无效的Token");}}/*** 授权时调用*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return new SimpleAuthorizationInfo();}
}
  • 调用工具类 JwtUtil,解析token获取其中的用户id,并对token过期等情况进行处理,抛出异常被捕获

  • 然后在filter层走失败的方法,返回结果给前端

JwtUtil

package com.example.englishhub.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.context.annotation.Configuration;import java.util.Base64;
import java.util.Date;
import java.util.HashMap;/*** jwt工具类** @author hahaha*/
@Configuration
public class JwtUtil {// 30 秒private static long EXPIRATION_TIME = 1000 * 30;// 1 hour
//    private static long EXPIRATION_TIME = 3600000 * 1;// 一天
//    private static long EXPIRATION_TIME = 3600000 * 1;
//private static long EXPIRATION_TIME = 10000 * 10;
//    private static String SECRET = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjY34DFDSSSd";// 秘钥// 使用Base64编码的密钥private static String SECRET = Base64.getEncoder().encodeToString("MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjY34DFDSSSd".getBytes());private static final String USER_ID = "id";/*** 生成jwtToken** @param id* @return*/public static String generateToken(String id) {HashMap<String, Object> map = new HashMap<>();// you can put any data in the mapmap.put(USER_ID, id);// 1. setClaims(map):将map中的数据存储到Claims中// 2. setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)):设置过期时间// 3. signWith(SignatureAlgorithm.HS512, SECRET):设置加密算法和密钥// 4. compact():生成tokenString token = Jwts.builder().setHeaderParam("typ", "JWT").setClaims(map).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).signWith(SignatureAlgorithm.HS512, SECRET).compact();return token;}/*** 校验jwtToken** @param token* @return*/// 优化验证逻辑public static String validateToken(String token) {Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();return claims.get(USER_ID, String.class);
//        // 使用自定义异常
//        if (StringUtils.isBlank(token)) {
//            throw new JwtValidationException(ResultType.UNAUTHORIZED.getCode(), "Token为空");
//        }
//        try {
//            Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
//            return claims.get(USER_ID, String.class);
//        } catch (ExpiredJwtException e) {
//            throw new JwtValidationException(ResultType.AGAIN_LOGIN.getCode(), "Token已过期,请重新登录");
//        } catch (JwtException e) {
//            throw new JwtValidationException(ResultType.UNAUTHORIZED.getCode(), "无效的Token");
//        }}public static void main(String[] args) {String id = "hahaha15";String token = generateToken(id);System.out.println(token);//token = "eyJhbGciOiJIUzUxMiJ9.eyJpZCI6IjY4NzZhYjFmYjk0MmZkNGYyN2Zm";id = validateToken(token);System.out.println(id);
//        HashMap<String, Object> map = new HashMap<>();
//        // you can put any data in the map
//        map.put("name", id);
//        token = Jwts.builder().setClaims(map).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
//                .signWith(SignatureAlgorithm.HS512, SECRET).compact();
//
//         String result = "";
//        try {
//
//            result = validateToken(token);
//            System.out.println(result);
//        }catch (Exception e){
//            System.out.println(e.toString());
//        }}
}

测试

  • 使用postman,前端就可以根据返回结果作相应处理,提示用户重新登录。

token续签

后端

  • 通过在shiro的过滤器中调用userService里的方法判断该用户是否在最近两小时为活跃状态,但是却报错了

Cannot invoke “com.example.englishhub.service.UserService.isUserActive(int)” because “this.userService” is null

userService 没有被正确注入到 JwtFilter 中,导致在执行 isAccessAllowed 方法时产生空指针异常。

@Autowired
private UserService userService;

然后询问gpt说可以使用构造函数注入

private final UserService userService;@Autowiredpublic JwtFilter(UserService userService) {this.userService = userService;}
  • 但是还是不行,过滤器可能在Spring上下文完全初始化之前实例化,因此无法访问Service层的bean。

所以改为使用 WebApplicationContextUtils

如果注入失败,可以通过 WebApplicationContextUtils 从Servlet上下文获取Spring的 WebApplicationContext,然后手动获取所需的bean。这种方法虽然有些繁琐,但可以解决过滤器中注入失败的问题。

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;public class JwtFilter extends AccessControlFilter {private UserService userService;@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {if (userService == null) {HttpServletRequest request = (HttpServletRequest) servletRequest;WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());userService = context.getBean(UserService.class);}// ... 接下来的代码 ...}
}
  • 经测试可以img

// 在JwtUtil类中添加检查令牌是否即将过期的方法
public boolean isTokenExpiring(String token) {Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();Date expiration = claims.getExpiration();long diff = expiration.getTime() - System.currentTimeMillis();return diff < 10 * 60 * 1000; // 10分钟
}// 在JwtFilter的onAccessDenied方法中添加续签逻辑
JwtToken jwtToken = new JwtToken(token);try {//进行登录处理,委托realm进行登录认证,调用JwtRealm进行的认证,doGetAuthenticationInfogetSubject(servletRequest, servletResponse).login(jwtToken);String id = jwtUtil.validateToken(token);if (jwtUtil.isTokenExpiring(token) && userService.isUserActive(Integer.parseInt(id))) {// 续签令牌String newToken = jwtUtil.generateToken(id);// 将新令牌发送给客户端HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;httpServletResponse.setHeader("token", newToken);}return true;}catch (Exception e) {log.error("onAccessDenied error:", e);responseError(servletResponse, ResultType.UNAUTHORIZED.getCode(), e.getMessage());return false;}

前端

  • 在接口服务那边通过拦截器判断是否需要新的token续签

    class ApiService {final Dio dio = Dio();void showFeedback(String type, String message, Color backgroundColor) {Get.snackbar(type, message,snackPosition: SnackPosition.BOTTOM,backgroundColor: backgroundColor,colorText: Colors.white);}ApiService() {dio..options.baseUrl = 'http://localhost:8899/englishhub/'..interceptors.add(InterceptorsWrapper(// 请求拦截器onRequest: (options, handler) async {final storageService = Get.find<StorageService>();String? token = storageService.getToken();// print('token: $token');if (token != null) {options.headers["token"] = token;}return handler.next(options);},// 响应拦截器onResponse: (response, handler) {final storageService = Get.find<StorageService>();// 检查是否有新令牌if (response.headers.value("token") != null) {String? newToken = response.headers.value("token");storageService.saveToken(newToken!); // 保存新令牌}print('请求结果: $response');return handler.next(response);},onError: (DioError e, handler) {print('请求错误: $e');// 401错误码表示token过期,清除token,跳转到登录页if (e.response?.statusCode == 401) {Get.find<StorageService>().clearToken();showFeedback('', 'token过期,请重新登录', Colors.red);Get.offAllNamed('/login');}},));}
    }
    

();

        // 检查是否有新令牌if (response.headers.value("token") != null) {String? newToken = response.headers.value("token");storageService.saveToken(newToken!); // 保存新令牌}print('请求结果: $response');return handler.next(response);},
onError: (DioError e, handler) {print('请求错误: $e');// 401错误码表示token过期,清除token,跳转到登录页if (e.response?.statusCode == 401) {Get.find<StorageService>().clearToken();showFeedback('', 'token过期,请重新登录', Colors.red);Get.offAllNamed('/login');}},));
}

}

关键字:springboot3+jdk17+shiro+jwt+redis

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: