SpringBoot JWT实现token登录刷新功能

目录
  • 1. 什么是JWT
  • 2. JWT组成部分
  • 3. JWT加密方式
  • 4.实战
  • 5.总结

1. 什么是JWT

Json web token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。简答理解就是一个身份凭证,用于服务识别。
JWT本身是无状态的,这点有别于传统的session,不在服务端存储凭证。这种特性使其在分布式场景,更便于扩展使用。

2. JWT组成部分

JWT有三部分组成,头部(header),载荷(payload),是签名(signature)。

  • 头部

头部主要声明了类型(jwt),以及使用的加密算法( HMAC SHA256)

  • 载荷

载荷就是存放有自定义信息的地方,例如用户标识,截止日期等

  • 签名

签名进行对之前的数据添加一层防护,防止被篡改。
签名生成过程: base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密。

// base64加密后的header和base64加密后的payload使用.连接组成的字符串
String str=base64(header).base64(payload);
// 加盐secret进行加密
String sign=HMACSHA256(encodedString, 'secret');

3. JWT加密方式

jwt加密分为两种对称加密和非对称加密。

  • 对称加密

对称加密指使用同一秘钥进行加密,解密的操作。加密解密的速度比较快,适合数据比较长时的使用。常见的算法为DES、3DES等

  • 非对称加密

非对称指通过公钥进行加密,通过私钥进行解密。加密和解密花费的时间长、速度相对较慢,但安全性更高,只适合对少量数据的使用。常见的算法RSA、ECC等。
两种加密方法没有谁更好,只有哪种场景更合适。

4.实战

本例采用了spring2.x,jwt使用了nimbus-jose-jwt版本,当然其他的jwt版本也都类似,封装的都是不错的。

1.maven关键配置如下

<dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.12.1</version>
        </dependency>
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

2.jwt工具类

对于这里的秘钥:采用了userId+salt+uuid的方式保证,即使是同一个用户每次生成的serect都是不同的

对于校验token有效性,包含三个过程:

  • 格式是否合法
  • token是否在有效期内
  • token是否在刷新的有效期内

对于token超过有效期,但在刷新有效期内,返回特定的code,前端进行识别,发起请求刷新token,达到用户无感知的过程。

public class JwtUtil {
    private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);

    private static final String BEARER_TYPE = "Bearer";
    private static final String PARAM_TOKEN = "token";
    /**
     * 秘钥
     */
    private static final String SECRET = "dfg#fh!Fdh3443";
    /**
     * 有效期12小时
     */
    private static final long EXPIRE_TIME = 12 * 3600 * 1000;
    /**
     * 刷新时间7天
     */
    private static final long REFRESH_TIME = 7 * 24 * 3600 * 1000;

    public static String generate(PayloadDTO payloadDTO)  {
        //创建JWS头,设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                .type(JOSEObjectType.JWT)
                .build();
        //将负载信息封装到Payload中
        Payload payload = new Payload(JSON.toJSONString(payloadDTO));
        //创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        try {
            //创建HMAC签名器
            JWSSigner jwsSigner = new MACSigner(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
            //签名
            jwsObject.sign(jwsSigner);
            return jwsObject.serialize();
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
    }

    public static String freshToken(String token)   {
        PayloadDTO payloadDTO;
        try {
            //从token中解析JWS对象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校验格式是否合适
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析异常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校验是否过期,未过期直接返回原token
        if (payloadDTO.getExp() >= System.currentTimeMillis()) {
            return token;
        }
        // 校验是否处于刷新时间内,重新生成token
        if (payloadDTO.getRef() >= System.currentTimeMillis()) {
            getRefreshPayload(payloadDTO);
            return generate(payloadDTO);
        }
        throw new BizException(TOKEN_EXP);
    }

    private static void verifyFormat(PayloadDTO payloadDTO, JWSObject jwsObject) throws JOSEException {
        //创建HMAC验证器
        JWSVerifier jwsVerifier = new MACVerifier(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
        if (!jwsObject.verify(jwsVerifier)) {
            throw new BizException(TOKEN_ERROR);
        }
    }

    public static String getTokenFromHeader(HttpServletRequest request) {
        // 先从header取值
        String value = request.getHeader("Authorization");
        if (!StringUtils.hasText(value)) {
            // header不存在从参数中获取
            value = request.getParameter(PARAM_TOKEN);
            if (!StringUtils.hasText(value)) {
                throw new BizException(TOKEN_MUST);
            }
        }
        if (value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
            return value.substring(BEARER_TYPE.length()).trim();
        }
        return value;
    }

    public static PayloadDTO verify(String token)  {
        PayloadDTO payloadDTO;
        try {
            //从token中解析JWS对象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校验格式是否合适
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析异常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校验是否过期
        if (payloadDTO.getExp() < System.currentTimeMillis()) {
            // 校验是否处于刷新时间内
            if (payloadDTO.getRef() >= System.currentTimeMillis()) {
                throw new BizException(TOKEN_REFRESH);
            }
            throw new BizException(TOKEN_EXP);
        }
        return payloadDTO;
    }

    public static PayloadDTO getDefaultPayload(Long userId) {
        long currentTimeMillis = System.currentTimeMillis();
        PayloadDTO payloadDTO = new PayloadDTO();
        payloadDTO.setJti(UUID.randomUUID().toString());
        payloadDTO.setExp(currentTimeMillis + EXPIRE_TIME);
        payloadDTO.setRef(currentTimeMillis + REFRESH_TIME);
        payloadDTO.setUserId(userId);
        return payloadDTO;

    }

    public static void getRefreshPayload(PayloadDTO payload) {
        long currentTimeMillis = System.currentTimeMillis();
        payload.setJti(UUID.randomUUID().toString());
        payload.setExp(currentTimeMillis + EXPIRE_TIME);
        payload.setRef(currentTimeMillis + REFRESH_TIME);
    }
}

3.权限拦截
本例中采用了自定义注解+切面的方式来实现token的校验过程。
自定义Auth注解提供了是否开启校验token,sign的选项,实际操作中可以添加更多的功能。

@Target(value = ElementType.METHOD)
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Auth {
    /**
     * 是否校验token,默认开启
     */
    boolean token() default true;

    /**
     * 是否校验sign,默认关闭
     */
    boolean sign() default false;
}

切面部分指定了对Auth进行切面,这种方法比采用拦截器方式更加灵活些。

@Component
@Aspect
public class AuthAspect {
    @Autowired
    private HttpServletRequest request;

    @Pointcut("@annotation(com.rain.jwt.config.Auth)")
    private void authPointcut(){}

    @Around("authPointcut()")
    public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取目标对象对应的字节码对象
        Class<?> targetCls=joinPoint.getTarget().getClass();
        //获取方法签名信息从而获取方法名和参数类型
        MethodSignature ms= (MethodSignature) joinPoint.getSignature();
        //获取目标方法对象上注解中的属性值
        Auth auth=ms.getMethod().getAnnotation(Auth.class);
        // 校验签名
        if (auth.token()) {
            String token = JwtUtil.getTokenFromHeader(request);
            JwtUtil.verify(token);
        }
        // 校验签名
        if (auth.sign()) {
            // todo
        }
        return joinPoint.proceed();
    }
}

4.测试接口

@RestController
@RequestMapping(value="/user")
@Api(tags = "用户")
public class UserController {

    @PostMapping(value = "/login")
    @Auth(token = false)
    @ApiOperation("登录")
    public Result<String> login(String username,String password) {
        // 用户常规校验
        Long userId = 100L;
        // 用户信息存入缓存
        // 生成token
        String token = JwtUtil.generate(JwtUtil.getDefaultPayload(userId));
        return Result.success(token);
    }

    @GetMapping(value = "refreshToken")
    @Auth
    @ApiOperation("刷新token")
    public Result<String> refreshToken(String token) {
        String freshToken = JwtUtil.freshToken(token);
        return Result.success(freshToken);
    }

    @GetMapping(value = "test")
    @Auth
    @ApiOperation("测试")
    public Result<String> test() {
        return Result.success("测试成功");
    }
}

5.总结

许多同学使用jwt经常将获取到的token放在redis中,在服务器端控制其有效性。这是一种处理token的方式,但这种方式跟jwt的思路是背道而去的,jwt本身就提供了过期的信息,将token的生命周期放入服务器中,又何必采用jwt的方式呢?直接来个uuid不香么。
最后来个项目地址

到此这篇关于SpringBoot JWT实现登录刷新token的文章就介绍到这了,更多相关SpringBoot JWT实现token登录内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Laravel (Lumen) 解决JWT-Auth刷新token的问题

    Laravel(Lumen)中使用JWT-Auth遇到一个问题,即token如何刷新. 一开始不太理解作者的设计思想,看了很多issue之后,慢慢明白jwt-refresh如何使用. 建一个路由,比如"auth/refresh-token" ,可以指向某个方法,也可以直接写个匿名函数. $app->post('auth/refresh-token', ['middleware' => 'jwt.refresh', function() { try { $old_token

  • 关于Vue 消除Token过期时刷新页面的重复提示问题

    token过期时,刷新页面,页面如果加载时向后端发起多个请求,会重复告警提示,经过处理,只提示一次告警. 1.问题现象   页面长时间未操作,再刷新页面时,第一次弹出"token失效,请重新登录!"提示,然后跳转到登录页面,接下来又弹出了n个"Token已过期"的后端返回消息提示. 2.原因分析   当前页面初始化,有多个向后端查询系统参数的调用,代码如下: created () { // ======================================

  • 详解ASP.NET Core Web Api之JWT刷新Token

    前言 如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋会将JWT过期时间设置成很长,有的几个小时,有的一天,有的甚至一个月,这么做当然存在问题,如果被恶意获得访问令牌,那么可在整个生命周期中使用访问令牌,也就是说存在冒充用户身份,此时身份认证服务器当然也就是始终信任该冒牌访问令牌,若要使得冒牌访问令牌无效,唯一的方案则是修改密钥,但是如果我们这么做了,

  • 如何实现无感刷新token

    目录 1.需求 方法一 方法二 方法三 2.实现 3.问题解决 问题一:如何防止多次刷新token 问题二:同时发起两个或者两个以上的请求时,其他接口怎么解决 前言: 最近在做需求的时候,涉及到登录token,产品提出一个问题:能不能让token过期时间长一点,我频繁的要去登录. 前端:后端,你能不能把token 过期时间设置的长一点. 后端:可以,但是那样做不安全,你可以用更好的方法. 前端:什么方法? 后端:给你刷新token的接口,定时去刷新token 前端:好,让我思考一下 1.需求 当

  • 基于springboot+jwt实现刷新token过程解析

    前一段时间讲过了springboot+jwt的整合,但是因为一些原因(个人比较懒)并没有更新关于token的刷新问题,今天跟别人闲聊,聊到了关于业务中token的刷新方式,所以在这里我把我知道的一些点记录一下,也希望能帮到一些有需要的朋友,同时也希望给我一些建议,话不多说,上代码! 1:这种方式为在线刷新,比方说设定的token有效期为30min,那么每次访问资源时,都会在拦截器中去判断一下token是否过期,如果没有过期就刷新token的时间为30min,反之则会重新登录,需要注意的是这种方式

  • 详解uniapp无痛刷新token方法

    前端在请求接口时,和后端定义好了,如果状态码为 401 ,则表明 token 过期,需要前端请求新的 token 大概流程如下: 1.用户登录之后,后端会返回两个 token ,分别为accessToken 和refreshToken 存储到Storage 平时请求数据时,请求头使用accessToken 来发送接口 2.当返回401 token 过期后, 我们通过接口向后端获取新的 token ,请求参数为refreshToken 3.我们拿到新的accessToken 和refreshTok

  • 请求时token过期自动刷新token操作

    1.在开发过程中,我们都会接触到token,token的作用是什么呢?主要的作用就是为了安全,用户登陆时,服务器会随机生成一个有时效性的token,用户的每一次请求都需要携带上token,证明其请求的合法性,服务器会验证token,只有通过验证才会返回请求结果. 2.当token失效时,现在的网站一般会做两种处理,一种是跳转到登陆页面让用户重新登陆获取新的token,另外一种就是当检测到请求失效时,网站自动去请求新的token,第二种方式在app保持登陆状态上面用得比较多. 3.下面进入主题,我

  • vue下axios拦截器token刷新机制的实例代码

    //创建http.js文件,以下是具体代码: //引入安装的axios插件 import axios from 'axios' import router from '@/router'; import Vue from 'vue' const qs = require("qs"); let _this = new Vue(); let isLock = false; let refreshSubscribers = []; //判断token是否过期 function isToken

  • SpringSecurity Jwt Token 自动刷新的实现

    功能需求 最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 token ,后续访问系统的请求都需要带上这个 token ,如果请求没有带上这个 token 或者 token 过期了,那么禁止访问系统.如果用户一直访问系统,那么还需要自动延长 token 的过期时间. 功能分析 1.token 的生成 使用现在比较流行的 jwt 来生成. 2.token 的自动延长 要实现 token 的自动延长,系统给用户 颁发 一个 token 无法实现,那么通过变通一个,给用户生成 2个 t

  • SpringBoot JWT实现token登录刷新功能

    目录 1. 什么是JWT 2. JWT组成部分 3. JWT加密方式 4.实战 5.总结 1. 什么是JWT Json web token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准.简答理解就是一个身份凭证,用于服务识别. JWT本身是无状态的,这点有别于传统的session,不在服务端存储凭证.这种特性使其在分布式场景,更便于扩展使用. 2. JWT组成部分 JWT有三部分组成,头部(header),载荷(payload),是签名(signature). 头

  • springboot+jwt实现token登陆权限认证的实现

    一 前言 此篇文章的内容也是学习不久,终于到周末有时间码一篇文章分享知识追寻者的粉丝们,学完本篇文章,读者将对token类的登陆认证流程有个全面的了解,可以动态搭建自己的登陆认证过程:对小项目而已是个轻量级的认证机制,符合开发需求: 二 jwt实现登陆认证流程 用户使用账号和面发出post请求 服务器接受到请求后使用私钥创建一个jwt,这边会生成token 服务器返回这个jwt给浏览器 浏览器需要将带有token的jwt放入请求头 每次手到客户端请求,服务器验证该jwt的token 验证成功返回

  • PHP实现JWT的Token登录认证

    1.JWT简介 JSON Web Token(缩写 JWT),是目前最流行的跨域认证解决方案. session登录认证方案:用户从客户端传递用户名.密码等信息,服务端认证后将信息存储在session中,将session_id放到cookie中. 以后访问其他页面,自动从cookie中取到session_id,再从session中取认证信息. 另一类解决方案,将认证信息,返回给客户端,存储到客户端.下次访问其他页面,需要从客户端传递认证信息回服务端. JWT就是这类方案的代表,将认证信息保存在客户

  • ASP.NET Core应用JWT进行用户认证及Token的刷新方案

    目录 一.什么是JWT? 为什么要使用JWT? 二.JWT的组成: Header Payload Signature 三.认证流程 四.应用实例 认证服务 User相关: TokenHelper: 应用服务 五.Token的刷新 本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新方案 一.什么是JWT? JWT(json web token)基于开放标准(RFC 7519),是一种无状态的分布式的身份验证方式,主要用于在网络应用环境间安全地传递声

  • koa+jwt实现token验证与刷新功能

    JWT JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的.自包含的方式,用于作为JSON对象在各方之间安全地传输信息.该信息可以被验证和信任,因为它是数字签名的. 本文只讲Koa2 + jwt的使用,不了解JWT的话请到这里)进行了解. koa环境 要使用koa2+jwt需要先有个koa的空环境,搭环境比较麻烦,我直接使用koa起手式,这是我使用koa+typescript搭建的空环境,如果你也经常用koa写写小demo,可以点个star,方便~ 安

  • springboot+jwt+微信小程序授权登录获取token的方法实例

    目录 前言 配置 XcxAuthenticationProvider XcxAuthenticationToken 小程序授权登录 前言 我们有时候在开发中,遇到这样的问题,就是我们需要小程序授权登录我们自己的后台,通过小程序的信息换取我们自己后台的token,实现账号密码.小程序授权登录的多种登录方式. 配置 在 SecurityConfig文件中配置 XcxAuthenticationProvider public class XcxAuthenticationProvider implem

  • SpringBoot框架集成token实现登录校验功能

    简介 公司新项目,需要做移动端(Android和IOS),登录模块,两个移动端人员提出用token来校验登录状态,一脸懵懵的,没做过,对于token的基本定义都模棱两可,然后查资料查查查,最终OK完成,写篇博客记录一下 思路: 1.基于session登录 基于session的登录(有回话状态),用户携带账号密码发送请求向服务器,服务器进行判断,成功后将用户信息放入session,用户发送请求判断session中是否有用户信息,有的话放行,没有的话进行拦截,但是考虑到时App产品,牵扯到要判断用户

  • SpringBoot 整合 Shiro 密码登录与邮件验证码登录功能(多 Realm 认证)

    导入依赖(pom.xml) <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!--集成jwt实现token认证--> <dependency

随机推荐