安全认证--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
版权归原作者 伏加特遇上西柚 所有, 如有侵权,请联系我们删除。