SpringCloud(四) gateway + JWT进行权限管理
jwt相关知识可以查看https://blog.csdn.net/sinat_29774479/article/details/898845001.引入依赖 关于jwt的操作我们采用jjwtjjwt是一个Java对jwt的支持库,我们使用这个库来创建、解码token<dependency><groupId>io.jsonwebtoken&...
·
jwt相关知识可以查看
https://blog.csdn.net/sinat_29774479/article/details/89884500
1.引入依赖 关于jwt的操作我们采用jjwt
jjwt是一个Java对jwt的支持库,我们使用这个库来创建、解码token
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2.结合jjwt,封装一个JWTUtil类
package com.wl.gateway.util.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author LWong
* @date 2019/12/24/024
*/
@Slf4j
public class JwtUtil {
private static final String KEY = "022bdc63c3c5a45879ee6581508b9d03adfec4a4658c0ab3d722e50c91a351c42c231cf43bb8f86998202bd301ec52239a74fc0c9a9aeccce604743367c9646b";
public static SecretKey generateKey() {
byte[] encodedKey = Base64.decodeBase64(KEY);
SecretKeySpec key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
// 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
Map<String, Object> claims = new HashMap<>();
claims.put("uid", "123456");
claims.put("user_name", "admin");
claims.put("nick_name", "X-rapido");
SecretKey key = generateKey();
// 这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(id)
// iat: jwt的签发时间
.setIssuedAt(now)
// issuer:jwt签发人
.setIssuer(issuer)
// sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
.setSubject(subject)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, key);
// 设置过期时间
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* 解密jwt
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) {
//签名秘钥,和生成的签名的秘钥一模一样
SecretKey key = generateKey();
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt).getBody();
return claims;
}
/**
* 检查token
*
* @return
*/
public static boolean checkToken(String jwtToken, ObjectMapper objectMapper) throws Exception {
//TODO 根据自己的业务修改
Claims claims = JwtUtil.parseJWT(jwtToken);
String subject = claims.getSubject();
JwtModel jwtModel = objectMapper.readValue(subject, JwtModel.class);
//TODO 对jwt里面的用户信息做判断
//获取token的过期时间,和当前时间作比较,如果小于当前时间,则token过期
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date expiration = claims.getExpiration();
log.info("======== token的过期时间:" + df.format(expiration));
return true;
}
}
3.添加过滤器JwtTokenFilter 结合gateway进行拦截
package com.wl.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wl.gateway.dto.Resp;
import com.wl.gateway.util.jwt.JwtUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* @author LWong
* @date 2019/12/24/024
*/
@Component
@ConfigurationProperties("org.my.jwt")
@Setter
@Getter
@Slf4j
public class JwtTokenFilter implements GlobalFilter, Ordered {
private String[] skipAuthUrls;
private ObjectMapper objectMapper;
public JwtTokenFilter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* 过滤器
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
//跳过不需要验证的路径
if (null != skipAuthUrls && Arrays.asList(skipAuthUrls).contains(url)) {
return chain.filter(exchange);
}
//获取token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
ServerHttpResponse resp = exchange.getResponse();
if (StringUtils.isBlank(token)) {
//没有token
return authErro(resp, "请登陆");
} else {
//有token
try {
JwtUtil.checkToken(token, objectMapper);
return chain.filter(exchange);
} catch (ExpiredJwtException e) {
log.error(e.getMessage(), e);
if (e.getMessage().contains("Allowed clock skew")) {
return authErro(resp, "认证过期");
} else {
return authErro(resp, "认证失败");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return authErro(resp, "认证失败");
}
}
}
/**
* 认证错误输出
*
* @param resp 响应对象
* @param mess 错误信息
* @return
*/
private Mono<Void> authErro(ServerHttpResponse resp, String mess) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Resp<String> returnData = new Resp<>(org.apache.http.HttpStatus.SC_UNAUTHORIZED, mess, mess);
String returnStr = "";
try {
returnStr = objectMapper.writeValueAsString(returnData);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
DataBuffer buffer = resp.bufferFactory().wrap(returnStr.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
4.在配置文件中配置gateway的路由规则
###################################
#服务启动端口的配置
###################################
server:
port: 8899
spring:
cloud:
#################################
# gateway相关配置
#################################
gateway:
################################
# 配置允许跨域请求
################################
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
# 路由定义
routes:
- id: baidu
uri: https://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- StripPrefix=1
- id: sina
uri: https://www.sina.com.cn/
predicates:
- Path=/sina/**
filters:
- StripPrefix=1
org:
my:
jwt:
#跳过认证的路由
skip-auth-urls:
- /baidu
############################################
# 有效时长
# 单位:d:天、h:小时、m:分钟、s:秒
###########################################
effective-time: 3m
5.编写测试接口
这里我自己封装了一个注解用于解析jwt 要看源码可以从文末的gittee地址上拉代码
package com.wl.gateway.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wl.gateway.annotation.JWTCheck;
import com.wl.gateway.dto.Resp;
import com.wl.gateway.dto.UserDTO;
import com.wl.gateway.util.jwt.JwtModel;
import com.wl.gateway.util.jwt.JwtUtil;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
/**
* @author LWong
* @date 2019/12/24/024
*/
@RestController
public class PersonController {
private ObjectMapper objectMapper;
@Value("${org.my.jwt.effective-time}")
private String effectiveTime;
public PersonController(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* 登陆认证接口
*
* @param userDTO
* @return
*/
@PostMapping("/login")
public Resp<String> login(@RequestBody UserDTO userDTO) throws Exception {
ArrayList<String> roleIdList = new ArrayList<>(1);
roleIdList.add("role_test_1");
JwtModel jwtModel = new JwtModel("test", roleIdList);
int effectivTimeInt = Integer.valueOf(effectiveTime.substring(0, effectiveTime.length() - 1));
String effectivTimeUnit = effectiveTime.substring(effectiveTime.length() - 1, effectiveTime.length());
String jwt = null;
switch (effectivTimeUnit) {
case "s": {
//秒
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 1000L);
break;
}
case "m": {
//分钟
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 60L * 1000L);
break;
}
case "h": {
//小时
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 60L * 60L * 1000L);
break;
}
case "d": {
//小时
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 24L * 60L * 60L * 1000L);
break;
}
}
return new Resp<String>(HttpStatus.SC_OK, "认证成功", jwt);
}
/**
* 为授权提示
*/
@GetMapping("/unauthorized")
public Resp<String> unauthorized() {
return new Resp<String>(HttpStatus.SC_UNAUTHORIZED, "未认证,请重新登陆", null);
}
/**
* jwt 检查注解测试 测试
*
* @return
*/
@GetMapping("/testJwtCheck")
@JWTCheck
public Resp<String> testJwtCheck(@RequestHeader("Authorization") String token, @RequestParam("name") @Valid String name) {
return new Resp<String>(HttpStatus.SC_OK, "请求成功咯", "请求成功咯" + name);
}
}
6.测试
1.未登录跳转sina 显示需要登录
2.登陆获取token
3.登陆后成功跳转sina
代码地址:https://gitee.com/wishlucky/gateway.git
更多推荐
已为社区贡献2条内容
所有评论(0)