Spring Cloud Gateway——(三)微服务网关实现JWT鉴权
1. JWT 实现微服务鉴权
1.1 什么是JWT?
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
-
JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
-
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。
-
载荷(playload)
载荷就是存放有效信息的地方
-
签证(signature)
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
-
1.2 JJWT签发与验证token
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
官方文档:https://github.com/jwtk/jjwt
1.3 JJWT快速入门
-
在项目中添加依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
-
创建测试类生成token
JwtBuilder builder= Jwts.builder() .setId("007") //设置唯一编号 .setSubject("小马")//设置主题 可以是JSON数据 .setIssuedAt(new Date())//设置签发日期 .setExpiration(new Date(System.currentTimeMillis()+3600))//设置过期时间 .signWith(SignatureAlgorithm.HS256,"xm");//设置签名 使用HS256算法,并设置SecretKey(字符串) //构建 并返回一个字符串 System.out.println( builder.compact() );
-
解析token
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGk...之前生成的token..."; Claims claims =Jwts.parser() .setSigningKey("xm") .parseClaimsJws(compactJwt) .getBody(); System.out.println(claims);
1.4 在微服务中实现jjwt鉴权
(1)在网关服务和用户登录服务中添加依赖依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
(2)在服务中创建类: JwtUtil 用于jjwt token生成和解析
package com.xm.system.util;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
/**
* @title: JwtUtil JWT工具类
* @projectName:
* @description: TODO
* @author: Zack_Tzh
* @date: 2019/12/10 9:31
*/
public class JwtUtil {
/**
* 设置有效期为 60 * 60 秒
*/
private static final Long JWT_TTL = 3600000L;
/**
* 设置秘钥明文
*/
private static final String JWT_KEY= "NVIDIA";
/**
* 创建toke
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id,String subject,Long ttlMillis){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
//唯一的ID
.setId(id)
// 主题 可以是JSON数据
.setSubject(subject)
// 签发者
.setIssuer("admin")
// 签发时间
.setIssuedAt(now)
//使用HS256对称加密算法签名, 第二个参数为秘钥
.signWith(signatureAlgorithm, secretKey)
// 设置过期时间
.setExpiration(expDate);
return builder.compact();
}
/**
* 解析JWT
* @param compactJwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String compactJwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws( compactJwt).getBody();
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
(3)在用户登录的login方法时,用户登录成功 则 签发TOKEN
/**
* 登录
* @param admin
* @return
*/
@PostMapping("/login")
public Result login(@RequestBody Admin admin){
boolean result = adminService.login(admin);//验证密码是否正确
if (result){
//密码是正确的
//生成jwt令牌,返回到客户端
Map<String,String> info = new HashMap<>();
info.put("username",admin.getLoginName());
//基于工具类生成jwt令牌
String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null);
info.put("token",jwt);
return new Result(true, StatusCodeEnum.OK,info);
}else{
//密码错误
return new Result(false, StatusCodeEnum.ERROR);
}
}
(3)在网关创建过滤器,用于token验证
package com.xm.system.filter;
import com.xm.system.util.JwtUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @title: AuthorizeFilter
* @projectName: xm
* @description: TODO
* @author: Zack_Tzh
* @date: 2019/12/10 14:46
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1. 获取请求
ServerHttpRequest request = exchange.getRequest();
//2. 则获取响应
ServerHttpResponse response = exchange.getResponse();
//3. 如果是登录请求则放行
if (request.getURI().getPath().contains("/admin/login")) {
return chain.filter(exchange);
}
//4. 获取请求头
HttpHeaders headers = request.getHeaders();
//5. 请求头中获取令牌
String token = headers.getFirst("token");
//6. 判断请求头中是否有令牌
if (StringUtils.isEmpty(token)) {
//7. 响应中放入返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete();
}
//9. 如果请求头中有令牌则解析令牌
try {
JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//11. 返回
return response.setComplete();
}
//12. 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}