You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

252 lines
9.2 KiB

package com.epmet.auth;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.epmet.commons.tools.constant.AppClientConstant;
import com.epmet.commons.tools.constant.Constant;
import com.epmet.commons.tools.constant.ServiceConstant;
import com.epmet.commons.tools.dto.form.HasOperPermissionFormDTO;
import com.epmet.commons.tools.dto.result.OperResouce;
import com.epmet.commons.tools.exception.EpmetErrorCode;
import com.epmet.commons.tools.exception.EpmetException;
import com.epmet.commons.tools.exception.RenException;
import com.epmet.commons.tools.feign.CommonOperAccessOpenFeignClient;
import com.epmet.commons.tools.feign.ResultDataResolver;
import com.epmet.commons.tools.redis.RedisKeys;
import com.epmet.commons.tools.redis.RedisUtils;
import com.epmet.commons.tools.security.dto.BaseTokenDto;
import com.epmet.commons.tools.utils.CpUserDetailRedis;
import com.epmet.commons.tools.utils.Result;
import com.epmet.filter.CpProperty;
import com.epmet.jwt.JwtTokenUtils;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import java.util.Date;
import java.util.List;
/**
* 内部认证处理器
*/
@Component
public class InternalAuthProcessor extends AuthProcessor implements ResultDataResolver {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private JwtTokenUtils jwtTokenUtils;
@Autowired
private CpUserDetailRedis cpUserDetailRedis;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
private CpProperty cpProperty;
@Autowired
private CommonOperAccessOpenFeignClient operAccessOpenFeignClient;
@Autowired
private RedisUtils redisUtils;
@Override
public ServerWebExchange auth(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestUri = request.getPath().pathWithinApplication().value();
String token = getTokenFromRequest(request);
boolean needAuth = needAuth(requestUri);
if (needAuth && StringUtils.isBlank(token)) {
// token不能为空
throw new RenException(EpmetErrorCode.ERR10005.getCode(), EpmetErrorCode.ERR10005.getMsg());
}
BaseTokenDto baseTokenDto = null;
String app = "";
String client = "";
String userId = "";
String customerId = "";
Date expiration = null;
if(StringUtils.isNotBlank(token)){
//是否过期
Claims claims = jwtTokenUtils.getClaimByToken(token);
if (claims != null) {
app = (String) claims.get(AppClientConstant.APP);
client = (String) claims.get(AppClientConstant.CLIENT);
userId = (String) claims.get(AppClientConstant.USER_ID);
expiration = claims.getExpiration();
baseTokenDto = cpUserDetailRedis.get(app, client, userId, BaseTokenDto.class);
}
}
if (baseTokenDto != null) {
customerId = baseTokenDto.getCustomerId();
}
if (needAuth) {
validateToken(baseTokenDto, token, expiration);
}
// 添加header
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
if (StringUtils.isNotBlank(app)) {
builder.header(AppClientConstant.APP, app);
}
if (StringUtils.isNotBlank(client)) {
builder.header(AppClientConstant.CLIENT, client);
}
if (StringUtils.isNotBlank(userId)) {
builder.header(AppClientConstant.USER_ID, userId);
}
if (baseTokenDto != null) {
String redisKey = baseTokenDto.getApp() + "-" + baseTokenDto.getClient() + "-" + baseTokenDto.getUserId();
logger.info("redisKey=" + redisKey);
builder.header(Constant.APP_USER_KEY, redisKey);
}
if(StringUtils.isNotBlank(customerId)){
builder.header(AppClientConstant.CUSTOMER_ID, customerId);
}
// 针对运营端的url拦截和校验
if (AppClientConstant.APP_OPER.equals(app)) {
HttpMethod method = request.getMethod();
Boolean hasAccess = checkRequestOperResource(userId, requestUri, method.toString());
if (!hasAccess) {
throw new EpmetException(EpmetErrorCode.EPMET_COMMON_OPERATION_FAIL.getCode(), "资源未授权", "资源未授权");
}
}
ServerHttpRequest shr = builder.build();
return exchange.mutate().request(shr).build();
}
/**
* 校验运营端用户是否有权访问该资源
* @param uri
* @param method
* @return
*/
private Boolean checkRequestOperResource(String userId, String uri, String method) {
String resourceJsonString = redisUtils.getString(RedisKeys.getOperExamineResourceUrls());
List<OperResouce> resources = JSON.parseObject(resourceJsonString, new TypeReference<List<OperResouce>>() {});
if (resources == null) {
// redis中没有缓存,需要api获取
resources = getResultDataOrThrowsException(operAccessOpenFeignClient.getExamineResourceUrls(), ServiceConstant.OPER_ACCESS_SERVER,
EpmetErrorCode.SERVER_ERROR.getCode(), "调用operaccess获取要校验的资源失败", "调用operaccess获取要校验的资源失败");
// 缓存
redisUtils.setString(RedisKeys.getOperExamineResourceUrls(), JSON.toJSONString(resources));
}
for (OperResouce resource : resources) {
if (antPathMatcher.match(resource.getResourceUrl(), uri)
&& resource.getResourceMethod().equals(method)) {
//需要校验权限的url
HasOperPermissionFormDTO form = new HasOperPermissionFormDTO();
form.setUri(uri);
form.setMethod(method);
form.setOperId(userId);
Result result = operAccessOpenFeignClient.hasOperPermission(form);
if (result == null || !result.success()) {
return false;
}
return true;
}
}
// 如果当前请求url不需要校验权限,那么返回true
return true;
}
/**
* 是否需要认证
* @param requestUri
* @return
*/
private boolean needAuth(String requestUri) {
for (String url : cpProperty.getInternalAuthUrlsWhiteList()) {
if (antPathMatcher.match(url, requestUri)) {
return false;
}
}
for (String url : cpProperty.getInternalAuthUrls()) {
if (antPathMatcher.match(url, requestUri)) {
return true;
}
}
return false;
}
/**
* 从请求中获取token
* @param request
* @return
*/
private String getTokenFromRequest(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(Constant.AUTHORIZATION_HEADER);
if (StringUtils.isBlank(token)) {
token = headers.getFirst(Constant.TOKEN_HEADER);
}
if (StringUtils.isBlank(token)) {
token = request.getQueryParams().getFirst(Constant.AUTHORIZATION_HEADER);
}
return token;
}
/**
* @Description 从用户token中取app,client,userId三项数据
* @return
* @author wxz
* @date 2021.06.11 15:04
*/
private BaseTokenDto getBaseTokenDto(String token, JwtTokenUtils jwtTokenUtils) {
//是否过期
Claims claims = jwtTokenUtils.getClaimByToken(token);
if (claims == null || jwtTokenUtils.isTokenExpired(claims.getExpiration())) {
return null;
}
//获取用户ID
String app = (String) claims.get("app");
String client = (String) claims.get("client");
String userId = (String) claims.get("userId");
return new BaseTokenDto(app, client, userId, token);
}
/**
* 校验Token是否异常
* @param tokenDto
* @param tokenStr
*/
private void validateToken(BaseTokenDto tokenDto, String tokenStr, Date expiration) {
if (null == tokenDto || jwtTokenUtils.isTokenExpired(expiration)) {
//说明登录状态时效(超时)
throw new RenException(EpmetErrorCode.ERR10006.getCode(), EpmetErrorCode.ERR10006.getMsg());
}else{
//Redis中存在数据,取出token,进行比对
if(!StringUtils.equals(tokenDto.getToken(),tokenStr)){
//用户携带token与Redis中不一致,说明当前用户此次会话失效,提示重新登陆
throw new RenException(EpmetErrorCode.ERR10007.getCode(), EpmetErrorCode.ERR10007.getMsg());
}
}
}
}