原文链接:

JWT详解:https://blog.csdn.net/weixin_45070175/article/details/118559272

1、什么是JWT

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探;
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Heade分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串;
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可;
  4. 前端在每次请求时将JWT Token放入HTTP请求头中Authorization属性中(解决XSS和XSRF问题);
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等;
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果;

2、 JWT认证的优势

对比传统的session认证方式,JWT的优势是:

  1. 简洁:JWT Token数据量小,传输速度也很快;
  2. 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
  3. 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务;
  4. 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题;
  5. 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端;
  6. 因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证;

3、JWT结构

JWT由3部分组成:标头(Header)有效载荷(Payload)签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串;

  1. JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

3.1 Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存;

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

3.2 Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

  1. iss: 发行人
  2. exp: 到期时间
  3. sub: 主题
  4. aud: 用户
  5. nbf: 在此之前不可用
  6. iat: 发布时间
  7. jti: JWT ID用于标识该JWT

这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

  1. {
  2. "sub": "1234567890",
  3. "name": "Helen",
  4. "admin": true
  5. }

请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

3.3 3.Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名;

  1. HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象:

注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

  • header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据;
  • signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值;

4、Java中使用JWT

官网推荐了6个Java使用JWT的开源库,其中比较推荐使用的是java-jwtjjwt-root

4.1.java-jwt

4.1.1 对称签名

4.1.1.1 依赖
  1. <dependency>
  2. <groupId>com.auth0</groupId>
  3. <artifactId>java-jwt</artifactId>
  4. <version>3.10.3</version>
  5. </dependency>
4.1.1.2 生成JWT的token
  1. /**
  2. * @author : huayu
  3. * @date : 25/11/2022
  4. * @param : []
  5. * @return : void
  6. * @description : 生成JWT的token
  7. */
  8. @Test
  9. public void testGenerateToken(){
  10. // 指定token过期时间为10秒
  11. Calendar calendar = Calendar.getInstance();
  12. // calendar.add(Calendar.SECOND, 10);
  13. //为了测试不过期,指定token过期时间为100秒
  14. calendar.add(Calendar.SECOND, 100);
  15. String token = JWT.create()
  16. .withHeader(new HashMap<>()) // Header
  17. .withClaim("userId", 001) // Payload
  18. .withClaim("userName", "huayu")
  19. .withExpiresAt(calendar.getTime()) // 过期时间
  20. .sign(Algorithm.HMAC256("!34ADAS")); // 签名用的secret
  21. System.out.println(token);
  22. }

测试结果:

4.1.1.3 解析JWT字符串
  1. /**
  2. * @author : huayu
  3. * @date : 25/11/2022
  4. * @param : []
  5. * @return : void
  6. * @description : 解析JWT字符串
  7. */
  8. @Test
  9. public void testResolveToken(){
  10. // 创建解析对象,使用的算法和secret要与创建token时保持一致
  11. JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!34ADAS")).build();
  12. // 解析指定的token
  13. DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6Imh1YXl1IiwiZXhwIjoxNjY5MzQ1NTE2LCJ1c2VySWQiOjF9.mN9DIfqy6ZKl6gwQ4WM5gmrQL2y0Q0bvleTy7AfTuFo");
  14. // 获取解析后的token中的payload信息
  15. Claim userId = decodedJWT.getClaim("userId");
  16. Claim userName = decodedJWT.getClaim("userName");
  17. log.info("userId:{}",userId.asInt());
  18. log.info("userName:{}",userName.asString());
  19. // 输出超时时间
  20. log.info("超出时间:{}",decodedJWT.getExpiresAt());
  21. }

测试:

我们设置过期时间位100秒,再次测试:

4.1.1.4 封装成工具类
  1. public class JWTUtils {
  2. // 签名密钥
  3. private static final String SECRET = "!DAR$";
  4. /**
  5. * 生成token
  6. * @param payload token携带的信息
  7. * @return token字符串
  8. */
  9. public static String getToken(Map<String,String> payload){
  10. // 指定token过期时间为7天
  11. Calendar calendar = Calendar.getInstance();
  12. // calendar.add(Calendar.DATE, 7);
  13. // 指定token过期时间为 12分钟
  14. // calendar.add(Calendar.MINUTE, 12);
  15. // 指定token过期时间为 100秒
  16. calendar.add(Calendar.SECOND, 100);
  17. JWTCreator.Builder builder = JWT.create().withHeader(new HashMap<>());
  18. // 构建payload
  19. payload.forEach((k,v) -> builder.withClaim(k,v));
  20. // 指定过期时间和签名算法
  21. String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
  22. return token;
  23. }
  24. /**
  25. * 解析token
  26. * @param token token字符串
  27. * @return 解析后的token
  28. */
  29. public static DecodedJWT decode(String token){
  30. JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
  31. DecodedJWT decodedJWT = jwtVerifier.verify(token);
  32. return decodedJWT;
  33. }
  34. }
4.1.1.5 JWTUtils 工具类测试
  1. /**
  2. * @author : huayu
  3. * @date : 25/11/2022
  4. * @param : []
  5. * @return : void
  6. * @description : 测试 JWTUtils 工具类 生成token 和 token 解析
  7. */
  8. @Test
  9. public void testJWTUtils(){
  10. //创建payload map 存放用户信息
  11. Map<String, String> payload = new HashMap();
  12. payload.put("userId","1");
  13. payload.put("userName","hauyu");
  14. //生成 token
  15. String token = JWTUtils.getToken(payload);
  16. //解析token
  17. DecodedJWT decodedJWT = JWTUtils.decode(token);
  18. Claim userId = decodedJWT.getClaim("userId");
  19. Claim userName = decodedJWT.getClaim("userName");
  20. log.info("userId:{}",userId.asString());
  21. log.info("userName:{}",userName.asString());
  22. // 输出超时时间
  23. log.info("超出时间:{}",decodedJWT.getExpiresAt());
  24. log.info("token:{}",token);
  25. }

测试结果:

4.1.2 非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

还没有测试成功,我的 RSA rsa = new RSA(null, RSA_PUBLIC_KEY); 只有一个参数,无法实例化RSA

4.2 jwt-root

4.2.1 对称签名

4.2.1.1 依赖
  1. <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
  2. <dependency>
  3. <groupId>io.jsonwebtoken</groupId>
  4. <artifactId>jjwt</artifactId>
  5. <version>0.9.1</version>
  6. </dependency>
4.2.1.2 工具类
  1. public class JWTUtils2 {
  2. // token时效:24小时
  3. public static final long EXPIRE = 1000 * 60 * 60 * 24;
  4. // 签名哈希的密钥,对于不同的加密算法来说含义不同
  5. public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
  6. /**
  7. * 根据用户id和昵称生成token
  8. * @param id 用户id
  9. * @param nickname 用户昵称
  10. * @return JWT规则生成的token
  11. */
  12. public static String getJwtToken(String id, String nickname){
  13. String JwtToken = Jwts.builder()
  14. .setHeaderParam("typ", "JWT")
  15. .setHeaderParam("alg", "HS256")
  16. .setSubject("baobao-user")
  17. .setIssuedAt(new Date())
  18. .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
  19. .claim("id", id)
  20. .claim("nickname", nickname)
  21. // HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
  22. .signWith(SignatureAlgorithm.HS256, APP_SECRET)
  23. .compact();
  24. return JwtToken;
  25. }
  26. /**
  27. * 判断token是否存在与有效
  28. * @param jwtToken token字符串
  29. * @return 如果token有效返回true,否则返回false
  30. */
  31. public static boolean checkToken(String jwtToken) {
  32. if(StringUtils.isEmpty(jwtToken)) return false;
  33. try {
  34. Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. return false;
  38. }
  39. return true;
  40. }
  41. /**
  42. * 判断token是否存在与有效
  43. * @param request Http请求对象
  44. * @return 如果token有效返回true,否则返回false
  45. */
  46. public static boolean checkToken(HttpServletRequest request) {
  47. try {
  48. // 从http请求头中获取token字符串
  49. String jwtToken = request.getHeader("token");
  50. if(StringUtils.isEmpty(jwtToken)) return false;
  51. Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  52. } catch (Exception e) {
  53. e.printStackTrace();
  54. return false;
  55. }
  56. return true;
  57. }
  58. /**
  59. * 根据token获取会员id
  60. * @param request Http请求对象
  61. * @return 解析token后获得的用户id
  62. */
  63. public static String getMemberIdByJwtToken(HttpServletRequest request) {
  64. String jwtToken = request.getHeader("token");
  65. if(StringUtils.isEmpty(jwtToken)) return "";
  66. Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  67. Claims claims = claimsJws.getBody();
  68. return (String)claims.get("id");
  69. }
  70. }
4.2.1.3 请求方法
4.2.1.3.1 JWT规则生成的token 和 判断token是否存在与有效
  1. /**
  2. * @author : huayu
  3. * @date : 25/11/2022
  4. * @param : [id, nickname]
  5. * @return : java.lang.String
  6. * @description : JWT规则生成的token 和 判断token是否存在与有效
  7. */
  8. @ApiOperation(value = "JWT规则生成的token 和 判断token是否存在与有效")
  9. @PostMapping("testGetJwtToken")
  10. @ApiImplicitParams({
  11. @ApiImplicitParam(value = "用户id",name = "id"),
  12. @ApiImplicitParam(value = "昵称",name = "nickname")
  13. })
  14. public String testGetJwtToken(@RequestParam("id") String id,
  15. @RequestParam("nickname") String nickname){
  16. //JWT规则生成的token
  17. String jwtToken = JWTUtils2.getJwtToken(id, nickname);
  18. log.info("JWT规则生成的token jwtToken:{}",jwtToken);
  19. //判断token是否存在与有效
  20. boolean checkoutToken = JWTUtils2.checkToken(jwtToken);
  21. log.info("判断token是否存在与有效 checkoutToken:{}",checkoutToken);
  22. return jwtToken;
  23. }

测试结果:

4.2.1.3.2 根据token获取会员id
  1. /**
  2. * @author : huayu
  3. * @date : 25/11/2022
  4. * @param : [request]
  5. * @return : java.lang.String
  6. * @description : 根据token获取会员id
  7. */
  8. @ApiOperation(value = "根据token获取会员id ")
  9. @PostMapping("testGetMemberIdByJwtToken")
  10. public String testGetMemberIdByJwtToken(HttpServletRequest request){
  11. //根据token获取会员id
  12. String memberIdByJwtToken = JWTUtils2.getMemberIdByJwtToken(request);
  13. log.info("根据token获取会员id memberIdByJwtToken:{}",memberIdByJwtToken);
  14. return memberIdByJwtToken;
  15. }

测试结果:

4.2.2 非对称签名

还没有测试成功,我的 RSA rsa = new RSA(null, RSA_PUBLIC_KEY); 只有一个参数,无法实例化RSA

5、实际开发中的应用

在实际的SpringBoot项目中,一般我们可以用如下流程做登录:

  1. 在登录验证通过后,给用户生成一个对应的随机token(注意这个token不是指jwt,可以用uuid等算法生成),然后将这个token作为key的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间;
  2. 将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端;
  3. 前端之后每次请求都在请求头中的Authorization字段中携带JWT字符串;
  4. 后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload中的随机token,然后再用这个随机token得到key,从Redis中获取用户信息,如果能获取到就说明用户已经登录;
  1. public class JWTInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. String JWT = request.getHeader("Authorization");
  5. try {
  6. // 1.校验JWT字符串
  7. DecodedJWT decodedJWT = JWTUtils.decode(JWT);
  8. // 2.取出JWT字符串载荷中的随机token,从Redis中获取用户信息
  9. ...
  10. return true;
  11. }catch (SignatureVerificationException e){
  12. System.out.println("无效签名");
  13. e.printStackTrace();
  14. }catch (TokenExpiredException e){
  15. System.out.println("token已经过期");
  16. e.printStackTrace();
  17. }catch (AlgorithmMismatchException e){
  18. System.out.println("算法不一致");
  19. e.printStackTrace();
  20. }catch (Exception e){
  21. System.out.println("token无效");
  22. e.printStackTrace();
  23. }
  24. return false;
  25. }
  26. }

SpringCloud Alibaba(七) - JWT(JSON Web Token)的更多相关文章

  1. Java JWT: JSON Web Token

    Java JWT: JSON Web Token for Java and Android JJWT aims to be the easiest to use and understand libr ...

  2. 如何在SpringBoot中集成JWT(JSON Web Token)鉴权

    这篇博客主要是简单介绍了一下什么是JWT,以及如何在Spring Boot项目中使用JWT(JSON Web Token). 1.关于JWT 1.1 什么是JWT 老生常谈的开头,我们要用这样一种工具 ...

  3. JWT(JSON Web Token) 【转载】

    JWT(JSON Web Token) 什么叫JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. 一般来说,互联网用户认证是这样子的. 1.用户向服务器发送用户名和密码. ...

  4. [更新]一份包含: 采用RSA JWT(Json Web Token, RSA加密)的OAUTH2.0,HTTP BASIC,本地数据库验证,Windows域验证,单点登录的Spring Security配置文件

    没有任何注释,表怪我(¬_¬) 更新: 2016.05.29: 将AuthorizationServer和ResourceServer分开配置 2016.05.29: Token获取采用Http Ba ...

  5. ( 转 ) 什么是 JWT -- JSON WEB TOKEN

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...

  6. 关于JWT(Json Web Token)的思考及使用心得

    什么是JWT? JWT(Json Web Token)是一个开放的数据交换验证标准rfc7519(php 后端实现JWT认证方法一般用来做轻量级的API鉴权.由于许多API接口设计是遵循无状态的(比如 ...

  7. 什么是JWT(Json Web Token)

    什么是 JWT (Json Web Token) 用户认证是计算机安全领域一个永恒的热点话题. JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). 该to ...

  8. API安全验证之JWT(JSON WEB TOKEN) OLCMS

    假如www.olcms.com/getUserInfo获取用户信息,你怎么知道当前用户是谁?有人说登陆时候我把他UID写入session了,如果是API接口,没有session怎么办,那么就需要把UI ...

  9. 5分钟搞懂:JWT(Json Web Token)

    https://www.qikegu.com/easy-understanding/892 JWT 基于token的用户认证原理:让用户输入账号和密码,认证通过后获得一个token(令牌),在toke ...

  10. JWT(Json Web Token)认证

    目录 JWT(Json Web Token) JWT的数据结构 JWT的用法 JWT验证流程

随机推荐

  1. ProxySQL 配置ProxySQL

    转载自:https://www.jianshu.com/p/212397a1be67 假定你已经对ProxySQL的架构有所了解.本文对ProxySQL的所有配置都是使用Admin管理接口完成的,该管 ...

  2. 6.监控elasticsearch集群---放弃采用(获取不到数据),建议看另一篇文章:监控elasticsearch

    prometheus监控es,同样采用exporter的方案. 项目地址: elasticsearch_exporter:https://github.com/justwatchcom/elastic ...

  3. vue this.$router.push query传递对象方法

    this.$router.push({ path: '/home', query: { params: JSON.stringify({ name: 'lokka', age: 18 }) } }); ...

  4. Pjax 下动态加载插件方案

    在纯静态网站里,有时候会动态更新某个区域往会选择 Pjax(swup.barba.js)去处理,他们都是使用 ajax 和 pushState 通过真正的永久链接,页面标题和后退按钮提供快速浏览体验. ...

  5. Jquery封装的ajax的使用过程发生的问题

    Jquery封装的ajax的使用过程发生的问题 今天在做项目的时候使用到了ajax来完成项目前后端数据交互,在之后发现在前端没有数据显示,而后端数据确实存在,在多次检查代码之后,发现代码并不存在问题, ...

  6. 微信小程序第三方授权登录

    登录流程时序图: 1.调用uni.getProvider()获取服务供应商,参数service确定是选择对应的什么操作,此处选择授权登录oauth 代码如下: 2.调用登录接口uni.login(), ...

  7. IDEA中直接将 SpringBoot项目打包成 Docker镜像时 pom.xml的配置

    <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactI ...

  8. Podman容器基础(二)

    Podman容器技术基础(二) 目录 Podman容器技术基础(二) 容器的使用 用户操作 用户配置文件 容器卷 容器的使用 运行一个容器 [root@cent1 ~]# podman pull ht ...

  9. nginx启停shell脚本

    #!/bin/bash # 编写 nginx 启动脚本 # 本脚本编写完成后,放置在/etc/init.d/目录下,就可以被 Linux 系统自动识别到该脚本 # 如果本脚本名为/etc/init.d ...

  10. Azure Kubernetes(AKS)部署及查看应用资源

    简介 上一篇文章讲解了如何使用Azure DevOps持续部署应用到Azure Kubernetes上.但是部署是否成功?会不会遇到什么问题?项目运行中是否会出现问题?我们该怎么样查看这些问题,并且对 ...