Cloud-Platform 学习——Part3 gateway 网关与 JWT 鉴权

1.前端发送请求

前端发送请求时有一个全局拦截器,拦截所有请求并配置 Authorization 头部信息,配置token,一共给后台鉴权

// 请求拦截器
service.interceptors.request.use(
config => {
if (!permission.access(config, store)) {
// eslint-disable-next-line no-throw-literal
throw {
type: '403',
config: config
}
}
loading.show(config)
// 在请求发送之前做一些处理
if (!(/^https:\/\/|http:\/\//.test(config.url))) {
const token = util.cookies.get('token')
if (token && token !== 'undefined') {
// 让每个请求携带token-- ['Authorization']为自定义key 请根据实际情况自行修改
config.headers['Authorization'] = 'Bearer ' + token
}
}
return config
},
error => {
// 发送失败
console.log(error)
Promise.reject(error)
}
)

2.后台全局网关过滤器拦截请求

@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
log.info("check token and user permission....");
//获取gateway原先请求路径
LinkedHashSet requiredAttribute = serverWebExchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
ServerHttpRequest request = serverWebExchange.getRequest();
// 获取当前网关访问的URI,即转发后的请求路径
String requestUri = request.getPath().pathWithinApplication().value(); //获取转发后的uri,如Path=/api/auth/**,http://localhost:8765/api/auth/jwt/token——>jwt/token
if (requiredAttribute != null) {
Iterator<URI> iterator = requiredAttribute.iterator();
while (iterator.hasNext()) {
URI next = iterator.next();
if (next.getPath().startsWith(GATE_WAY_PREFIX)) {
//如果是 /api开头则获取 /api后面的路径
requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());
}
}
}
final String method = request.getMethod().toString(); //POST/GET
BaseContextHandler.setToken(null);
ServerHttpRequest.Builder mutate = request.mutate();
// 网关不进行拦截的URI配置,常见如验证码、Login接口
if (isStartWith(requestUri)) {
ServerHttpRequest build = mutate.build();
return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build()); //过滤器放行
}
IJWTInfo user = null;
try {
// 判断用户token,获取用户信息
user = getJWTUser(request, mutate);
} catch (Exception e) {
log.error("用户Token过期异常", e);
return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Error or Expired!"), HttpStatus.UNAUTHORIZED);
}
}
private boolean isStartWith(String requestUri) {
boolean flag = false;
for (String s : startWith.split(",")) {
if (requestUri.startsWith(s)) {
return true;
}
}
return flag;
}

3.JWT 鉴权并解析用户信息

private IJWTInfo getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader()); //获取请求头 Authorization信息
String authToken = null;
if (strings != null) {
authToken = strings.get(0);
}
if (StringUtils.isBlank(authToken)) {
strings = request.getQueryParams().get("token"); //如果 Authorization信息为空,则获取携带参数 token
if (strings != null) {
authToken = strings.get(0);
}
}
IJWTInfo infoFromToken = userAuthUtil.getInfoFromToken(authToken);
//从redis获取当前会话
String s = stringRedisTemplate.opsForValue().get(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + infoFromToken.getTokenId());
if (StringUtils.isBlank(s)) {
throw new UserTokenException("User token expired!");
}
ctx.header(userAuthConfig.getTokenHeader(), authToken);
BaseContextHandler.setToken(authToken);
return infoFromToken;
}

使用工具类解析 token

public static IJWTInfo getInfoFromToken(String token, byte[] pubKey) throws Exception {
if(token.startsWith("Bearer")){
token = token.replace("Bearer ","");
}
Jws<Claims> claimsJws = parserToken(token, pubKey);
Claims body = claimsJws.getBody();
return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)),StringHelper.getObjectValue(body.get(CommonConstants.JWT_ID)));
}

JWT 工具类

public class JWTHelper {
private static RsaKeyHelper rsaKeyHelper = new RsaKeyHelper();
/**
* 密钥加密token
*
* @param jwtInfo
* @param priKeyPath
* @param expire
* @return
* @throws Exception
*/
public static String generateToken(IJWTInfo jwtInfo, String priKeyPath, int expire) throws Exception {
String compactJws = Jwts.builder()
.setSubject(jwtInfo.getUniqueName())
.claim(CommonConstants.JWT_KEY_USER_ID, jwtInfo.getId())
.claim(CommonConstants.JWT_KEY_NAME, jwtInfo.getName())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.signWith(SignatureAlgorithm.RS256, rsaKeyHelper.getPrivateKey(priKeyPath))
.compact();
return compactJws;
}
/**
* 生成密钥加密的 token
* @param jwtInfo 包含用户名、用户id、用户名称、tokenId
* @param priKey 密钥
* @param expire token过期时间
* @return
* @throws Exception
*/
public static String generateToken(IJWTInfo jwtInfo, byte priKey[], int expire) throws Exception {
String compactJws = Jwts.builder()
.setSubject(jwtInfo.getUniqueName())
.claim(CommonConstants.JWT_KEY_USER_ID, jwtInfo.getId())
.claim(CommonConstants.JWT_KEY_NAME, jwtInfo.getName())
.claim(CommonConstants.JWT_ID, jwtInfo.getTokenId())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.signWith(SignatureAlgorithm.RS256, rsaKeyHelper.getPrivateKey(priKey))
.compact();
return compactJws;
}
/**
* 公钥解析token
*
* @param token
* @return
* @throws Exception
*/
public static Jws<Claims> parserToken(String token, String pubKeyPath) throws Exception {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(rsaKeyHelper.getPublicKey(pubKeyPath)).parseClaimsJws(token);
return claimsJws;
}
/**
* 公钥解析token
*
* @param token
* @return
* @throws Exception
*/
public static Jws<Claims> parserToken(String token, byte[] pubKey) throws Exception {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(rsaKeyHelper.getPublicKey(pubKey)).parseClaimsJws(token);
return claimsJws;
}
/**
* 获取token中的用户信息
*
* @param token
* @param pubKeyPath
* @return
* @throws Exception
*/
public static IJWTInfo getInfoFromToken(String token, String pubKeyPath) throws Exception {
Jws<Claims> claimsJws = parserToken(token, pubKeyPath);
Claims body = claimsJws.getBody();
return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)));
}
/**
* 获取token中的用户信息
*
* @param token
* @param pubKey
* @return
* @throws Exception
*/
public static IJWTInfo getInfoFromToken(String token, byte[] pubKey) throws Exception {
if(token.startsWith("Bearer")){
token = token.replace("Bearer ","");
}
Jws<Claims> claimsJws = parserToken(token, pubKey);
Claims body = claimsJws.getBody();
return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)),StringHelper.getObjectValue(body.get(CommonConstants.JWT_ID)));
}
}

4.检查权限

Mono<CheckPermissionInfo> checkPermissionInfoMono = webClientBuilder.build().
get().uri("http://ace-admin/api/user/{username}/check_permission?requestMethod=" + method + "&requestUri=" + requestUri, user.getUniqueName()).header(userAuthConfig.getTokenHeader(), BaseContextHandler.getToken()).retrieve().bodyToMono(CheckPermissionInfo.class);
IJWTInfo finalUser = user;
return checkPermissionInfoMono.flatMap(checkPermissionInfo -> {
// 当前用户具有访问权限
if (checkPermissionInfo.getIsAuth()) {
if (checkPermissionInfo.getPermissionInfo() != null) {
// 若资源存在则请求设置访问日志
setCurrentUserInfoAndLog(serverWebExchange, finalUser, checkPermissionInfo.getPermissionInfo());
}
ServerHttpRequest build = mutate.build();
return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
} else {
// 当前用户不具有访问权限
return getVoidMono(serverWebExchange, new TokenForbiddenResponse("Forbidden!Does not has Permission!"), HttpStatus.FORBIDDEN);
}
});

获取所有权限控制

public List<PermissionInfo> getAllPermission() {
String key = RedisKeyConstant.REDIS_KEY_ALL_PERMISISON;
String s = stringRedisTemplate.opsForValue().get(key);
if (s == null || org.apache.commons.lang.StringUtils.isBlank(s)) {
List<Menu> menus = menuBiz.selectListAll();//获取菜单权限
List<PermissionInfo> result = new ArrayList<PermissionInfo>();
menu2permission(menus, result);
List<Element> elements = elementBiz.getAllElementPermissions();//获取操作权限
element2permission(result, elements);//封装在 PermissionInfo中
s = JSON.toJSONString(result);
stringRedisTemplate.opsForValue().set(key, s, 12, TimeUnit.HOURS); //放到 redis中
}
List<PermissionInfo> permissionInfos = JSON.parseArray(s, PermissionInfo.class);
return permissionInfos;
}

判断当前请求是否需要做权限控制

// 判断当前请求是否需要做权限控制
List<PermissionInfo> matchPermission = allPermission.parallelStream().filter(permissionInfo -> {
String uri = permissionInfo.getUri();
if (uri.indexOf("{") > 0) {
uri = uri.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
}
String regEx = "^" + uri + "$";
return (Pattern.compile(regEx).matcher(requestUri).find())
&& requestMethod.equals(permissionInfo.getMethod());
}).collect(Collectors.toList());
// 说明当前请求不做权限控制
if (matchPermission.size() == 0) {
checkPermissionInfo.setIsAuth(true);
return Mono.just(checkPermissionInfo);
}

获取当前用户拥有的权限

public List<PermissionInfo> getPermissionByUsername(String username) {
String key = String.format(RedisKeyConstant.REDIS_KEY_USER_PERMISISON, username);
String s = stringRedisTemplate.opsForValue().get(key);
if (s == null || org.apache.commons.lang.StringUtils.isBlank(s)) {
User user = userBiz.getUserByUsername(username);
List<Menu> menus = menuBiz.getUserAuthorityMenuByUserId(user.getId());
List<PermissionInfo> result = new ArrayList<PermissionInfo>();
menu2permission(menus, result);
List<Element> elements = elementBiz.getAuthorityElementByUserId(user.getId() + "");
element2permission(result, elements);
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(result), 12, TimeUnit.HOURS);
return result;
}
List<PermissionInfo> permissionInfos = JSON.parseArray(s, PermissionInfo.class);
return permissionInfos;
}

判断用户拥有的权限里面有没有当前请求需要的权限

// 判断当前用户是否拥有该访问资源的权限
List<PermissionInfo> permissions = this.getPermissionByUsername(username);
PermissionInfo current = null;
for (PermissionInfo info : permissions) {
//判断用户拥有的权限里面有没有当前请求需要的权限
boolean anyMatch = matchPermission.parallelStream().anyMatch(permissionInfo -> permissionInfo.getCode().equals(info.getCode()));
if (anyMatch) {
current = info;
break;
}
}
if (current == null) {
// 当前用户不拥有该权限
checkPermissionInfo.setIsAuth(false);
} else {
// 当前用户拥有该资源的访问权限
checkPermissionInfo.setIsAuth(true);
checkPermissionInfo.setPermissionInfo(current);
}
return Mono.just(checkPermissionInfo);

根据以上的判断返回用户信息

return checkPermissionInfoMono.flatMap(checkPermissionInfo -> {
// 当前用户具有访问权限
if (checkPermissionInfo.getIsAuth()) {
if (checkPermissionInfo.getPermissionInfo() != null) {
// 若资源存在则请求设置访问日志
setCurrentUserInfoAndLog(serverWebExchange, finalUser, checkPermissionInfo.getPermissionInfo());
}
ServerHttpRequest build = mutate.build();
return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
} else {
// 当前用户不具有访问权限
return getVoidMono(serverWebExchange, new TokenForbiddenResponse("Forbidden!Does not has Permission!"), HttpStatus.FORBIDDEN);
}
});

5.大致流程图

在这里插入图片描述

posted @   Acegzx  阅读(48)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示