目录
- 一、令牌技术概览
- 二、JWT的核心组成与结构
- 1、Header(头)
- 2、Payload(载荷)
- 3、Signature(签名)
- 4、Base64编码
- 5、JWT的认证流程
- 三、使用JWT令牌
- 1、引入JWT令牌的依赖
- 2、引入JWT令牌的工具类
- 3、生成JWT令牌
- 4、统一拦截器配置
- 四、总结
一、令牌技术概览
在登录认证中,令牌是用户的身份标识,是合法的身份凭证,好像十分神秘和高大上,但其本质是一个字符串。
如果使用令牌技术进行会话跟踪,在浏览器发起请求,请求登录接口,如果登录成功,那么就在服务端生成一个令牌,令牌就是用户的合法身份凭证,在响应数据的时候,就可以将令牌直接响应给前端。
在前端程序接收到令牌之后,就需要将令牌存储起来,可以存储在Cookie中,也可以存储在localStorage这样的其他存储空间。之后,在后续的每一次请求中,都需要携带令牌,服务端的统一拦截器需要校验令牌的有效性。如果令牌有效,则说明用户已经执行了登录操作,拦截器就可以放行;如果令牌无效(解析令牌报错),那么则说明用户没有执行登录操作,拦截器就需要拦截,并返回错误代码,让用户登录。整个流程如下图所示:
此时,在同一个会话的多次请求之间,我们就通过将数据存储在令牌中的方式完成了数据共享。令牌技术有很多优点,比如:支持多端,不但支持PC端,而且支持移动端;令牌技术也可以解决服务器集群的认证问题,因为只需要成功解析令牌,就可以证明令牌是有效的,是无需在服务器存储的(存储在Redis);令牌的安全性也非常强悍。但也是因为其强悍的性能,令牌使用起来会更加的复杂,但是这些劣势在优势面前就不值一提了。
二、JWT的核心组成与结构
令牌的形式有很多,本文讲解功能强大、使用最广泛的JWT令牌。JWT令牌(JSON Web Token),定义了一种简洁的、自包含的格式,可用于通信双方以Json数据格式安全的传输信息。
简洁:JWT令牌的本质就是字符串,可以作为请求参数或者在请求头中直接传递。(可以存储在Cookie和Header里面,建议存储在Header里面)。
自包含:JWT可以自己定义需要存储的信息
安全:JWT令牌虽然是一个字符串,但是可以根据需求,在令牌中存储自定义的数据内容,比如在登录操作中,可以在JWT令牌中存储用户相关信息。
JWT由三部分Base64编码的字符串组成,以点号(.)分隔:Header.Payload.Signature。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjg5NjYxODUwLCJleHAiOjE2OTA5NTc4NTB9.Bq0pExJcSgaQD94qAYVSx4W__K8FWGeY7iUJKMyTIYFofZoqa2VfEvl8Na96kt2wYbjFImHmQntkdce6cHQ8_A1、Header(头)
该部分主要是记录令牌类型、令牌使用的签名算法等。例如:
{"typ":"JWT","alg":"HS512"}从这个Header信息就可以看出:这是一个JWT令牌,使用了HS512签名算法。
alg:指定加密算法(如HS256、RS256),确保令牌完整性。
typ:固定为"JWT",标识令牌类型。
2、Payload(载荷)
该部分主要是携带一些自定义的信息,或者一些默认的信息等,例如:
{"sub":"1","iat":1689661850,"exp":1690957850}从这个Payload信息可以看出:这个令牌携带的数据是一个用户数据,sub为1,iat为1689661850。
载荷存储用户声明(Claims),分为三类:
注册声明(Registered Claims):预定义字段(如sub表示主题,exp表示过期时间)。
公共声明(Public Claims):自定义字段(如role: “admin”)。
私有声明(Private Claims):业务特定数据(如用户ID)。
3、Signature(签名)
这个部分主要是令牌的签名,签名可以防止Token被篡改,可以提高令牌的安全性。其构成是将Header和Payload两个部分,加入指定密钥(Secret),并通过指定的签名算法计算而来。正是因为数字签名,所以说JWT令牌是非常安全的,一旦令牌中的任何一个部分、任何一个字符被篡改了,整个令牌在校验时都会失效。
4、Base64编码
JWT令牌是如何将原始的Json数据,转变为字符串的呢?在生成JWT令牌的时候,对原始数据进行了Base64编码(这并非是一种加密方式,只是一种编码方式)。
Base64编码:是一种基于64个可打印的字符来表示二进制数据的编码方式。所使用的64个字符分别是A到Z、a到z、0-9,一个加号(+),一个斜杠(/),加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。在有些情况下,Basae64编码可能会出现一个等号(=)。等号是一个补位的符号。
5、JWT的认证流程
1.用户登录:客户端提交凭证(如用户名/密码)至认证服务器。
2.令牌签发:服务器验证凭证后,生成JWT并返回客户端。
3.令牌存储:客户端将JWT保存于localStorage或Cookie中(更建议存在headers里面)。
4.请求携带:客户端在后续请求头中添加Token。
5.服务端验证:服务器解密签名、检查过期时间(exp)和发行者(iss),验证通过后执行业务逻辑。
三、使用JWT令牌
1、引入JWT令牌的依赖
想要使用JWT令牌,首先需要引入JWT对应的Maven坐标:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>2、引入JWT令牌的工具类
在引入了JWT依赖之后,就可以使用对应的工具类JwtUtil提供的API来完成JWT令牌的生成与校验:
packagecom.sky.utils;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtBuilder;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importjava.nio.charset.StandardCharsets;importjava.util.Date;importjava.util.Map;publicclassJwtUtil{/** * 生成jwt * 使用Hs256算法, 私匙使用固定秘钥 * * @param secretKey jwt秘钥 * @param ttlMillis jwt过期时间(毫秒) * @param claims 设置的信息 * @return */publicstaticStringcreateJWT(StringsecretKey,longttlMillis,Map<String,Object>claims){// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithmsignatureAlgorithm=SignatureAlgorithm.HS256;// 生成JWT的时间longexpMillis=System.currentTimeMillis()+ttlMillis;Dateexp=newDate(expMillis);// 设置jwt的bodyJwtBuilderbuilder=Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm,secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);returnbuilder.compact();}/** * Token解密 * * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个 * @param token 加密后的token * @return */publicstaticClaimsparseJWT(StringsecretKey,Stringtoken){// 得到DefaultJwtParserClaimsclaims=Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();returnclaims;}}3、生成JWT令牌
Map<String,Object>claims=newHashMap<>();claims.put(JwtClaimsConstant.EMP_ID,employee.getId());Stringtoken=JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);4、统一拦截器配置
packagecom.sky.interceptor;importcom.sky.constant.JwtClaimsConstant;importcom.sky.context.BaseContext;importcom.sky.properties.JwtProperties;importcom.sky.utils.JwtUtil;importio.jsonwebtoken.Claims;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/** * jwt令牌校验的拦截器 */@Component@Slf4jpublicclassJwtTokenAdminInterceptorimplementsHandlerInterceptor{@AutowiredprivateJwtPropertiesjwtProperties;/** * 校验jwt * * @param request * @param response * @param handler * @return * @throws Exception */publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//判断当前拦截到的是Controller的方法还是其他资源if(!(handlerinstanceofHandlerMethod)){//当前拦截到的不是动态方法,直接放行returntrue;}//1、从请求头中获取令牌Stringtoken=request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try{log.info("jwt校验:{}",token);Claimsclaims=JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(),token);LongempId=Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:",empId);BaseContext.setCurrentId(empId);//3、通过,放行returntrue;}catch(Exceptionex){//4、不通过,响应401状态码response.setStatus(401);returnfalse;}}}四、总结
JWT令牌是现在越来越流行,使用越来越广泛的会话跟踪技术,可以在多端使用,并且有极强的安全性能,还可以应对服务器集群问题,是解决登录认证问题的最佳选择。