开发者

springboot集成JWT之双重token的实现

开发者 https://www.devze.com 2025-04-01 14:29 出处:网络 作者: 编梦小匠
目录一,单个token缺点二,双重token(AccessToken,refreshToken)(一)设计思路(二)后端代码1,TokenUtil(生成token和获取用户信息)2,JwtInterceptor(检验accessToken是否合法)3, WebConfig(设置拦截规则
目录
  • 一,单个token缺点
  • 二,双重token(AccessToken,refreshToken)
    • (一)设计思路
    • (二)后端代码
      • 1,TokenUtil(生成token和获取用户信息)
      • 2,JwtInterceptor(检验accessToken是否合法)
      • 3, WebConfig(设置拦截规则)
      • 4,登陆接口处生成accessToken和refreshToken
      • 5,编写更新refreshToken的接口
    • (三)前端代码
      • 1,登录成功后存储accessToken
      • 2,axIOS请求拦截器处设置请求头
      • 3,axios响应拦截器配置
      • 4,守卫路由配置

一,单个token缺点

token一般存储在浏览器中,容易被盗取,而为了防止被盗取长期使用,token的有效时间必然无法不能设置太长,此举也必然引起用户的频繁登录,给用户带来不好的体验

二,双重token(accessToken,refreshToken)

(一)设计思路

1,将accessToken作为是否登录的标识,存储在浏览器当中。由于该token可浏览器看到,容易被盗取,可将有效时间设置的尽可能短一些,解决盗取长期使用问题

2,将refreshToken作为是否更新accessToken的标识,只要refreshToken不过期,则自动更新accessToken,可将有效时间设置的长些,解决频繁登录问题

3,不直接将refreshToken存储到浏览器上,而是将其存储到accessToken的载荷里,后端通过获取accessToken的载荷内容获取到refreshToken,防止refreshToken被盗取

4,accessToken构成:

         载荷:refreshToken

         有效时间:尽可能短(这里我设置为30分钟)

         密钥:用户的密码

     refreshToken构成:

         载荷:用户的id

         有效时间:长(这里我设置为一天)

         密钥:用户的密码

(二)后端代码

1,TokenUtil(生成token和获取用户信息)

具体步骤看注释

@Component
@Slf4j
public class TokenUtils {
    private static IUserService staticAdminService;

    @Resource
    private IUserService adminService;

    @PostConstruct
    public void setUserService() {
        staticAdminService = adminService;
    }
    
	//	生成token
    public static String genToken(String adminId, String sign, Integer time) {
    	//生成过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, time*60);

        return JWT.create().withAudience(adminId) //载荷
                .withExpiresAt(instance.getTime()) //time分钟后过期
                .sign(Algorithm.HMAC256(sign)); // 密钥
    }
    
    //根据accessToken获取用户信息
    //  1.通过accessToken的载荷拿到refreshToken
    //  2.通过refreshToken的载荷拿到userId
    //  3.调用根据用户id获取用户信息的方法拿到用户信息
    public static User getCurrentAdmin() {
        String accessToken = null;
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Requwww.devze.comestContextHolder.getRequestAttributes()).getRequest();
            accessToken = request.getHeader("token");
            System.out.println("access"+accessToken);
            if (StrUtil.isBlank(accessToken)) {
                log.error("获取当前登录的accessToken失败, token: {}", accessToken);
                return null;
            }
            String refreshToken = JWT.decode(accessToken).getAudience().get(0);
            if (StrUtil.isBlank(refreshToken)) {
                log.error("获取当前登录的refreshToken失败, token: {}", refreshToken);
                return null;
            }
            String userId = JWT.decode(refreshToken).getAudience().get(0);
            return staticAdminService.getById(Integer.valueOf(userId));
        } catch (Exception e) {
            log.error("获取当前登录的管理员信息失败, token={}", accessToken,  e);
            return null;
        }
    }
}

2,JwtInterceptor(检验accessToken是否合法)

实现思路:

(1)检验accessToken是否为空

(2)通过accessToken的载荷内容拿到refreshToken

(3)验证获取到的refreshToken是否合法,若不合法,则accessToken为无效token,合法则返回密钥

(4)通过该密钥去判断accessToken是否过期

@Slf4j
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private IUserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String accessToken = request.getHeader("token");
        if (StrUtil.isBlank(accessToken)) {
            accessToken = request.getParameter("token");
        }
        //执行认证
        if (StrUtil.isBlank(accessToken)) {
            throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg());
        }
        String refreshToken;
        try{
            refreshToken = JWT.decode(accessToken).getAudience().get(0);
        }catch (Exception e){
            throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
        }
        String password = verifyRefreshToken(refreshToken);
        try {
            // 用户密码加签验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build();
            jwtVerifier.verify(accessToken); // 验证token
        } catch (JWTVerificationException e) {
            throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
        }
        return true;
    }
	
	//验证refreshToken是否合法
    public String verifyRefreshjsToken(String token){
        // 获取 token 中的adminId
        String adminId;
        User user;
        try {
            adminId = JWT.decode(token).getAudience().get(0);
            // 根据token中的userid查询数据库
            user = userService.getById(Integer.parseInt(adminId));
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
        }
        if (user == null) {
            throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg());
        }

        try {
            // 用户密码加签验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
            jwtVerifier.verify(token); // 验证token
        } catch (JWTVerificationException e) {
            throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
        }
        return user.getPassword();
    }
}

3, WebConfig(设置拦截规则)

@Configuration
public class WebConfig implements  WebMvcConfigurer {
    // 加编程客栈自定义拦截器JwtInterceptor,设置拦截规则
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/refreshToken/refresh");
    }
    @Bean
    public JwtInterceptor jwtInterceptor(){
        return new JwtInterceptor();
    }
}

4,登陆接口处生成accessToken和refreshToken

String refreshToken= TokenUtils.genToken(user.getId().toString(),user.getPassword(),24*60);
String accessToken=TokenUtils.genToken(refreshToken,user.getPassword(),30);

5,编写更新refreshToken的接口

实现思路:

(1)检验accessToken是否为空

(2)通过accessToken的载荷内容拿到refreshToken

(3)验证获取到的refreshToken是否合法,不合法,则accessToken为无效token(此处无效token单指自己编写,不是后端生成的token),不对accessToken进行更新操作,合法则重新生成新的accessToken

@RestController
@RequestMapping("/refreshToken")
public class refreshTokenController {
    @Autowired
    private IUserService userService;

    @GetMapping("/refresh")
    public Result refresh(@RequestParam String accessToken) {
        if (StrUtil.isBlank(accessToken)) {
            throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg());
        }
        String refreshToken;
        try{
            refreshToken = JWT.decode(accessToken).getAudience().get(0);
        }catch (Exception e){
            throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
        }
        if(StrUtil.isBlank(refreshToken)){
            return Result.error(ErrorCode.REFRESH_TOKEN_NULL.getCode(), ErrorCode.REFRESH_TOKEN_NULL.getMsg());
        }
        // 获取 token 中的adminId
        String adminId;
        User user;
        try {
            adminId = JWT.decode(refreshToken).getAudience().get(0);
            // 根据token中的userid查询数据库
            user = userService.getById(Integer.parseInt(adminId));
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        编程客栈    throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
        }
        if (user == null) {
            throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg());
        }

        try {
            // 用户密码加签验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
            jwtVerifier.verify(refreshToken); // 验证token
        } catch (JWTVerificationException e) {
            throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
        }
        String currentAccessToken= TokenUtils.genToken(refreshToken,user.getPassword(),30);
        return Result.success(currentAccessToken);
    }
}

(三)前端代码

1,登录成功后存储awww.devze.comccessToken

存储方式不限,在这我是存储在localStorage里,并在vuex里共享该数据

//storage.js
const TOKEN_KEY = 'tk'
export const getInfo = () => {
  const token = localStorage.getItem(TOKEN_KEY)
  return token || ''
}
export const setInfo = (token) => {
  localStorage.setItem(TOKEN_KEY, token)
}
export const removeInfo = () => {
  localStorage.removeItem(TOKEN_KEY)
}

//token模块
import { getInfo, setInfo ,removeInfo} from '@/utils/storage'
export default {
  namespaced: true,
  state () {
    return {
      tokenInfo: getInfo()
    }
  },
  mutations: {
    setTokenInfo (state, info) {
      state.tokenInfo = info
      setInfo(state.tokenInfo)
    },
    removeTokenInfo () {
      removeInfo()
    }
  },
  actions: {}
}
store.commit('token/setTokenInfo', data)

2,axios请求拦截器处设置请求头

instance.interceptors.request.use(function (config) {
  const accessToken = store.state.token.tokenInfo
  if (accessToken) {
    config.headers.token = accessToken
  }
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

3,axios响应拦截器配置

设置accessToken过期则判断refreshToken是否过期,如果未过期,重新生成accessToken,并重新发送请求

instance.interceptors.response.use(async function (response) {
  if (response.data.code === 200) {
    return response.data
  } else if (response.data.code === 1008 || response.data.code === 1009 || response.data.code === 1010) {
  	//accessToken过期
    const { data } = await refreshToken(store.state.token.tokenInfo)
    store.commit('token/setTokenInfo', data)
    return instance(response.config)
  } else if (response.data.code === 1014) {
  	//refreshToken过期
    window.location.href = '/login'
  } else {
    Vue.prototype.$message.error(response.data.msg)
    return Promise.reject(response.data.msg)
  }
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

4,守卫路由配置

判断是否有accessToken,是否在登录注册页面,若都无则跳转到登录页,重新登录

router.beforeEach(async (to, from, next) => {
  const token = store.state.token.tokenInfo
  if (
    // 检查用户是否已登录
    !token &&
    // ❗️ 避免无限重定向
    to.path !== '/login' && to.path !== '/register'
  ) {
    // 将用户重定向到登录页面
    next({ path: '/login' })
  } else {
    next()
  }
})

到此这篇关于springboot集成JWT之双重token的实现的文章就介绍到这了,更多相关springboot 双重token内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)! 

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号