package com.xiaopeng.gateway.filter; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.common.core.exception.auth.NotLoginException; import com.xiaopeng.gateway.config.properties.AntiShakeProperties; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; 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.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.constant.HttpStatus; import com.xiaopeng.common.core.constant.SecurityConstants; import com.xiaopeng.common.core.constant.TokenConstants; import com.xiaopeng.common.core.utils.JwtUtils; import com.xiaopeng.common.core.utils.ServletUtils; import com.xiaopeng.common.core.utils.StringUtils; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.gateway.config.properties.IgnoreWhiteProperties; import io.jsonwebtoken.Claims; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * 网关鉴权 * * @author ruoyi */ @Component public class AuthFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); // 排除过滤的 uri 地址,nacos自行添加 @Autowired private IgnoreWhiteProperties ignoreWhite; @Autowired private AntiShakeProperties antiShakeProperties; @Autowired private RedisService redisService; @Value("${xiaopeng.xtiger.sso.issuer}") private String issuer; @Value("${xiaopeng.xtiger.sso.appId}") private String appId; @Value("${xiaopeng.xtiger.sso.serverHost}") private String serverHost; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest.Builder mutate = request.mutate(); String url = request.getURI().getPath(); // 跳过不需要验证的路径 if (StringUtils.matches(url, ignoreWhite.getWhites())) { return chain.filter(exchange); } //校验账户是否有效 try { verifyToken(request); }catch (Exception e){ return unauthorizedResponse(exchange, e.getMessage()); } String token = getToken(request); Claims claims = JwtUtils.parseToken(token); String userkey = JwtUtils.getUserKey(claims); String userid = JwtUtils.getUserId(claims); String username = JwtUtils.getUserName(claims); String userType = JwtUtils.getUserType(claims); String operationUserKey = JwtUtils.getOperationUserKey(claims); if (!checkPathPermission(userType, url)) { return unauthorizedResponse(exchange, "无权访问当前资源"); } String redisKey = ""; if ("system".equals(userType)) { redisKey = StringUtils.isNotBlank(userkey) ? getTokenKey(userkey) : ""; } if ("operation".equals(userType)) { redisKey = StringUtils.isNotBlank(operationUserKey) ? getTokenKey(operationUserKey) : ""; } if (StringUtils.isBlank(redisKey)) { return unauthorizedResponse(exchange, "登录状态已过期"); } boolean islogin = redisService.hasKey(redisKey); if (!islogin) { return unauthorizedResponse(exchange, "登录状态已过期"); } // 设置用户信息到请求 addHeader(mutate, SecurityConstants.USER_KEY, userkey); addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); addHeader(mutate, SecurityConstants.USER_TYPE, userType); addHeader(mutate, SecurityConstants.OPERATION_USER_KEY, operationUserKey); // 内部请求来源参数清除 removeHeader(mutate, SecurityConstants.FROM_SOURCE); return chain.filter(exchange.mutate().request(mutate.build()).build()); } private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { if (value == null) { return; } String valueStr = value.toString(); String valueEncode = ServletUtils.urlEncode(valueStr); mutate.header(name, valueEncode); } private void removeHeader(ServerHttpRequest.Builder mutate, String name) { mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); } private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) { log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath() + "\n" + msg); String s = UUID.randomUUID().toString(); String state1 = s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24); String s1 = UUID.randomUUID().toString(); String state2 = s1.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24); String nonce = RandomStringUtils.randomAlphanumeric(6); JSONObject jsonObject = JSON.parseObject("{\n" + " \"uid\": null,\n" + " \"username\": null,\n" + " \"authUrl\": \"" + issuer + "/oidc/auth?scope=openid+profile+offline_access+phone+email&response_type=code&redirect_uri=" + serverHost + "/dashboard&state=" + state1 + "&nonce=" + nonce + "&client_id=" + appId + "\",\n" + " \"logoutUrl\": null,\n" + " \"callbackUrl\": \"" + serverHost + "/dashboard?state=" + state2 + "\"\n" + " }"); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), jsonObject.toString(), HttpStatus.UNAUTHORIZED); } /** * 获取缓存key */ private String getTokenKey(String token) { return CacheConstants.LOGIN_TOKEN_KEY + token; } private boolean checkPathPermission(String userType, String url) { boolean appPath = StringUtils.isNotBlank(url) && url.contains("/maintenance-app"); if ("operation".equals(userType)) { return appPath; } if ("system".equals(userType)) { return !appPath; } return true; } /** * 获取请求token */ private String getToken(ServerHttpRequest request) { String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION); // 如果前端设置了令牌前缀,则裁剪掉前缀 if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) { token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); } return token; } /** * 验证token * @param request * @throws Exception */ public void verifyToken(ServerHttpRequest request) throws Exception{ String token = getToken(request); if (StringUtils.isEmpty(token)) { throw new NotLoginException("令牌不能为空"); } Claims claims = JwtUtils.parseToken(token); if (claims == null) { throw new NotLoginException("令牌已过期或验证不正确!"); } String userid = JwtUtils.getUserId(claims); if (StringUtils.isEmpty(userid)) { throw new NotLoginException("令牌验证失败"); } } @Override public int getOrder() { return -300; } }