0


安全认证--JWT介绍及使用

安全认证--JWT介绍及使用

1.无状态登录原理

有状态登录和无状态登录详解

1.1.什么是有状态?

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,无法进行水平扩展
  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

1.2.什么是无状态

微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩
  • 减小服务端存储压力

1.3.如何实现无状态

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务端对token进行解密,判断是否有效。

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用

JWT

来生成token,保证token的安全性

1.4.JWT

1.4.1.简介

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io
GitHub上jwt的java客户端:https://github.com/jwtk/jjwt

1.4.2.数据格式

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:- token类型,这里是JWT- 签名算法,自定义我们会对头部进行base64加密(可解密),得到第一部分数据
  • Payload:载荷,就是有效数据,一般包含下面信息:- 标准载荷:JWT规定的信息,jwt的元数据: - JTI: JWT的id,当前jwt的唯一标识(像身份证号)- IAT: issue at 签发时间- EXP:过期时间- SUB:签发人- …- 自定义载荷: - 用户身份信息,(注意,这里因为采用base64加密,可解密,因此不要存放敏感信息)这部分也会采用base64加密,得到第二部分数据
  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性

生成的数据格式:
在这里插入图片描述
可以看到分为3段,每段就是上面的一部分数据。
什么是 JWT – JSON WEB TOKEN
傻傻分不清之 Cookie、Session、Token、JWT
JWT详细教程与使用

2.编写JWT工具

我们会用到比较流行的java语言的JWT工具,jjw

2.1.添加JWT依赖

我们需要先在项目中引入JWT依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency><!--json工具--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--日期时间工具类--><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency><!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

2.2.载荷对象

JWT中,会保存载荷数据,我们计划存储2部分:

  • jti:jwt的id
  • UserDetail:用户数据

为了方便后期获取,我们定义一个类来封装。

添加一个实体类,代表载荷信息

importlombok.Data;//载荷对象@DatapublicclassPayload{/**
     * tocken的id
     */privateString jti;/**
     * 用户数据
     */privateUserDetail userDetail;}

载荷中的UserDetail信息,也需要一个实体类表示,这里我们定义一个UserDetail类。

这里我们假设用户信息包含2部分:

  • id:用户id
  • username:用户名
importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;@Data@AllArgsConstructor(staticName ="of")@NoArgsConstructorpublicclassUserDetail{/**
     * 用户id
     */privateLong id;/**
     * 用户名
     */privateString username;}

2.3.工具

我创建一个JwtUtils 工具类,用来封装几个方法:

  • createJwt() :生成JWT
  • parseJwt() :验证并解析JWT
importcom.example.jwt.constants.RedisConstants;importcom.example.jwt.dto.Payload;importcom.example.jwt.dto.UserDetail;importcom.fasterxml.jackson.core.JsonProcessingException;importcom.fasterxml.jackson.databind.ObjectMapper;importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jws;importio.jsonwebtoken.JwtParser;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.security.Keys;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.util.StringUtils;importjavax.crypto.SecretKey;importjava.io.IOException;importjava.nio.charset.Charset;importjava.util.UUID;importjava.util.concurrent.TimeUnit;publicclassJwtUtils{/**
     * JWT解析器
     */privatefinalJwtParser jwtParser;/**
     * 秘钥
     */privatefinalSecretKey secretKey;//   @AutowiredprivateStringRedisTemplate redisTemplate;privatefinalstaticObjectMapper mapper =newObjectMapper();publicJwtUtils(String key){// 生成秘钥
        secretKey =Keys.hmacShaKeyFor(key.getBytes(Charset.forName("UTF-8")));// JWT解析器this.jwtParser =Jwts.parserBuilder().setSigningKey(secretKey).build();}/**
     * 生成jwt,自己指定的JTI
     *
     * @param userDetails 用户信息
     * @return JWT
     */publicStringcreateJwt(UserDetail userDetails){try{// 生成tokenidString jti=createJti();//存入redis中//            this.redisTemplate.opsForValue().set(RedisConstants.JTI_KEY_PREFIX+userDetails.getUsername(),jti,30, TimeUnit.MINUTES);returnJwts.builder().signWith(secretKey).setId(jti).claim("user", mapper.writeValueAsString(userDetails)).compact();}catch(JsonProcessingException e){thrownewRuntimeException(e);}}/**
     * 解析jwt,并将用户信息转为指定的Clazz类型
     *
     * @param jwt   token
     * @return 载荷,包含JTI和用户信息
     */publicPayloadparseJwt(String jwt){try{Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwt);Claims claims = claimsJws.getBody();Payload payload =newPayload();
            payload.setJti(claims.getId());
            payload.setUserDetail(mapper.readValue(claims.get("user",String.class),UserDetail.class));return payload;}catch(IOException e){thrownewRuntimeException(e);}}privateStringcreateJti(){returnStringUtils.replace(UUID.randomUUID().toString(),"-","");}/**
     * 刷新jwt有效期
     * @param username
     */publicvoidrefreshJwt(String  username){String key=RedisConstants.JTI_KEY_PREFIX+username;//重置key的过期时间
        redisTemplate.expire(key,30,TimeUnit.MINUTES);}}

在这里插入图片描述

2.4.测试

2.4.1.配置秘钥

application.yml

文件中配置秘钥:

yy:jwt:key: helloWorldJavaAuthServiceSecretKe

定义一个配置类,注册

JwtUtils

注入到Spring的容器。

importcom.example.jwt.utils.JwtUtils;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassJwtConfig{@Value("${yy.jwt.key}")privateString key;@BeanpublicJwtUtilsjwtUtils(){returnnewJwtUtils(key);}}

2.4.2.测试类

@AutowiredprivateJwtUtils jwtUtils;@Testpublicvoidtest(){// 生成jwtString jwt = jwtUtils.createJwt(UserDetail.of(112L,"lele"));System.out.println("jwt = "+ jwt);// 解析jwtPayload payload = jwtUtils.parseJwt(jwt);System.out.println("payload = "+ payload);}

结果:

jwt = eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyZTRkMGI1NjZiODY0YjUzODAwZTI3NGNhOWE0MTcxYSIsInVzZXIiOiJ7XCJpZFwiOjExMixcInVzZXJuYW1lXCI6XCJsZWxlXCJ9In0.NGa42tISwsLg_hyONasdGPGDigFFxkWbH04wd4ELztY
payload = Payload(jti=2e4d0b566b864b53800e274ca9a4171a, userDetail=UserDetail(id=112, username=lele))

2.5项目源码

源码地址

3.jwt优秀介绍

JWT详细教程与使用
傻傻分不清之 Cookie、Session、Token、JWT
什么是 JWT – JSON WEB TOKEN


本文转载自: https://blog.csdn.net/weixin_43811057/article/details/128968979
版权归原作者 伏加特遇上西柚 所有, 如有侵权,请联系我们删除。

“安全认证--JWT介绍及使用”的评论:

还没有评论