/*
|
* Copyright 2018-2020 by jason. All Rights Reserved.
|
*
|
* Permission to use, copy, modify, and distribute this software and its
|
* documentation for any purpose and without fee is hereby granted,
|
* provided that the above copyright notice appear in all copies and that
|
* both that copyright notice and this permission notice appear in
|
* supporting documentation, and that the name of Vinay Sajip
|
* not be used in advertising or publicity pertaining to distribution
|
* of the software without specific, written prior permission.
|
* VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
|
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
* VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
|
* ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
*
|
*/
|
|
package cn.mb.cloud.gateway.filter;
|
|
import cn.mb.cloud.common.gateway.common.PermissionResult;
|
import cn.mb.cloud.common.gateway.config.FilterIgnorePropertiesConfig;
|
import cn.mb.cloud.gateway.auth.AuthService;
|
import cn.mb.cloud.gateway.auth.ResultUtil;
|
import cn.mb.cloud.gateway.handler.AppTypeContextHolder;
|
import cn.mb.cloud.gateway.util.RedisUtil;
|
import com.alibaba.fastjson.JSON;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
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.context.annotation.Bean;
|
import org.springframework.core.Ordered;
|
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
|
import org.springframework.stereotype.Component;
|
import org.springframework.util.AntPathMatcher;
|
import org.springframework.web.server.ServerWebExchange;
|
import reactor.core.publisher.Mono;
|
|
import java.util.*;
|
|
/**
|
* @author: jason
|
* @Date: 2019-05-20 15:50
|
* @Description:
|
*/
|
@Slf4j
|
@Component
|
public class PermissionsFilter implements GlobalFilter, Ordered {
|
|
@Autowired
|
private RedisUtil redisUtil;
|
|
@Autowired
|
private FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
|
|
@Autowired
|
AuthService authService;
|
|
private AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
/**
|
* 账户
|
*/
|
@Value("${ignore.account.prefix}")
|
private String IGNORE_ACCOUNT_PREFIX;
|
/**
|
* 账户
|
*/
|
@Value("${ignore.account.uris}")
|
private String IGNORE_ACCOUNT_URIS;
|
/**
|
* 福利
|
*/
|
@Value("${ignore.activity.prefix}")
|
private String IGNORE_ACTIVITY_PREFIX;
|
/**
|
* 福利
|
*/
|
@Value("${ignore.activity.uris}")
|
private String IGNORE_ACTIVITY_URIS;
|
/**
|
* 赛事
|
*/
|
@Value("${ignore.competition.prefix}")
|
private String IGNORE_COMPETITION_PREFIX;
|
/**
|
* 赛事
|
*/
|
@Value("${ignore.competition.uris}")
|
private String IGNORE_COMPETITION_URIS;
|
/**
|
* 课程
|
*/
|
@Value(("${ignore.course.prefix}"))
|
private String IGNORE_COURSE_PREFIX;
|
/**
|
* 课程
|
*/
|
@Value(("${ignore.course.uris}"))
|
private String IGNORE_COURSE_URIS;
|
/**
|
* 其他
|
*/
|
@Value(("${ignore.other.prefix}"))
|
private String IGNORE_OTHER_PREFIX;
|
/**
|
* 其他
|
*/
|
@Value(("${ignore.other.uris}"))
|
private String IGNORE_OTHER_URIS;
|
/**
|
* 管理后台
|
*/
|
@Value(("${ignore.management.prefix}"))
|
private String IGNORE_MANAGEMENT_PREFIX;
|
/**
|
* 管理后台
|
*/
|
@Value(("${ignore.management.uris}"))
|
private String IGNORE_MANAGEMENT_URIS;
|
|
|
|
|
@Override
|
public int getOrder() {
|
return -1;
|
}
|
|
|
@Override
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpResponse response = exchange.getResponse();
|
String appType = request.getHeaders().getFirst(AppTypeContextHolder.APP_TYPE);
|
AppTypeContextHolder.setDeviceType(appType);
|
PermissionResult permissionResult = hasPermission(exchange, request);
|
AppTypeContextHolder.clear();
|
|
//token无效 invaild token
|
if (permissionResult == PermissionResult.TOKEN_INVALID) {
|
response.setRawStatusCode(200);
|
byte[] bits = JSON.toJSONBytes(ResultUtil.tokenErr());
|
DataBuffer buffer = response.bufferFactory().wrap(bits);
|
HttpHeaders headers = response.getHeaders();
|
String first = headers.getFirst("Content-Type");
|
if(StringUtils.isEmpty(first)){
|
headers.add("Content-Type", "application/json;charset=UTF-8");
|
}
|
return response.writeWith(Mono.just(buffer));
|
}
|
|
if (permissionResult != PermissionResult.PASS) {
|
response.setRawStatusCode(permissionResult.getValue());
|
byte[] bits = JSON.toJSONBytes(permissionResult.getName());
|
DataBuffer buffer = response.bufferFactory().wrap(bits);
|
HttpHeaders headers = response.getHeaders();
|
String first = headers.getFirst("Content-Type");
|
if(StringUtils.isEmpty(first)){
|
headers.add("Content-Type", "application/json;charset=UTF-8");
|
}
|
return response.writeWith(Mono.just(buffer));
|
}
|
|
//校验参数签名 Verify parameter signature
|
// ResultUtil resultUtil = preHandle(request);
|
// if(resultUtil.getCode() != 200){
|
// log.info("签名异常");
|
// response.setRawStatusCode(resultUtil.getCode());
|
// byte[] bits = JSON.toJSONBytes(resultUtil);
|
// DataBuffer buffer = response.bufferFactory().wrap(bits);
|
// HttpHeaders headers = response.getHeaders();
|
// String first = headers.getFirst("Content-Type");
|
// if(StringUtils.isEmpty(first)){
|
// headers.add("Content-Type", "application/json;charset=UTF-8");
|
// }
|
//
|
// return response.writeWith(Mono.just(buffer));
|
// }
|
|
return chain.filter(exchange.mutate().request(request.mutate().build()).build());
|
}
|
|
|
|
|
public ResultUtil preHandle(ServerHttpRequest req) {
|
String uri = req.getURI().getRawPath();
|
if(uri.contains("v2/api-docs")){
|
return ResultUtil.success();
|
}
|
if(uri.contains("/oauth")){
|
return ResultUtil.success();
|
}
|
if(uri.contains("/base")){
|
return ResultUtil.success();
|
}
|
String appid = "BT7NPhA0f775uzcUuftWjCE1TYZlWmHZ";
|
Map<String, Object> queryParams = QueryParamsUtil.getQueryParams(req);
|
String sign = (String)queryParams.get("sign");
|
if(org.springframework.util.StringUtils.isEmpty(sign)){
|
return ResultUtil.sign();
|
}
|
return authService.checkSyncAuth(appid, uri, sign, queryParams);
|
}
|
|
|
public PermissionResult hasPermission(ServerWebExchange exchange, ServerHttpRequest request) {
|
String uri = request.getURI().getRawPath();
|
if(uri.contains("v2/api-docs")){
|
return PermissionResult.PASS;
|
}
|
|
String token = getAccessToken(request);
|
//账户
|
if (uri.startsWith(IGNORE_ACCOUNT_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_ACCOUNT_URIS)){
|
String[] split = IGNORE_ACCOUNT_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
}
|
//福利
|
if (uri.startsWith(IGNORE_ACTIVITY_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_ACTIVITY_URIS)){
|
String[] split = IGNORE_ACTIVITY_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
}
|
//赛事
|
if (uri.startsWith(IGNORE_COMPETITION_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_COMPETITION_URIS)){
|
String[] split = IGNORE_COMPETITION_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
}
|
//课程
|
if (uri.startsWith(IGNORE_COURSE_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_COURSE_URIS)){
|
String[] split = IGNORE_COURSE_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
}
|
//其他
|
if (uri.startsWith(IGNORE_OTHER_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_OTHER_URIS)){
|
String[] split = IGNORE_OTHER_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
}
|
|
|
// ----------------------------------------------------------------- 放行综合后台授权登录-UPMS
|
if (uri.startsWith(IGNORE_MANAGEMENT_PREFIX)) {
|
if(!StringUtils.isEmpty(IGNORE_MANAGEMENT_URIS)){
|
String[] split = IGNORE_MANAGEMENT_URIS.split(",");
|
for (String s : split) {
|
if (uri.contains(s)) {
|
return PermissionResult.PASS;
|
}
|
}
|
}
|
|
|
String value = redisUtil.getValue(token.substring(token.length() - 32));
|
if(StringUtils.isEmpty(value)){
|
log.warn("Redis Token Store未找到对应的令牌,accessToken:" + token);
|
return PermissionResult.TOKEN_INVALID;
|
}
|
return PermissionResult.PASS;
|
|
// String accessToken = getAccessToken(request);
|
// if (StringUtils.isEmpty(accessToken)) {
|
// log.warn("访问令牌为空,accessToken:" + accessToken);
|
// return PermissionResult.TOKEN_INVALID;
|
// }
|
//
|
// boolean hasAuth = LoginHelper.userExist(accessToken);
|
// if (!hasAuth) {
|
// log.warn("Redis Token Store未找到对应的令牌,accessToken:" + accessToken);
|
// return PermissionResult.TOKEN_INVALID;
|
// } else {
|
// if (!LoginHelper.isExpired(LoginHelper.getUser(accessToken))) {
|
// log.warn("访问令牌已过期,accessToken:" + accessToken);
|
// return PermissionResult.TOKEN_EXPIRED;
|
// }
|
// LoginHelper.update(LoginHelper.getUser(accessToken), 86400000L);
|
// return PermissionResult.PASS;
|
// }
|
}
|
|
// 判断是否是忽略列表中的
|
//Check if it is in the ignore list.
|
if (!filterIgnorePropertiesConfig.getPermissionUrls().isEmpty()) {
|
Optional<String> optional = filterIgnorePropertiesConfig.getPermissionUrls().stream().filter(url -> pathMatcher.match(url, uri)).findFirst();
|
if (optional.isPresent()) {
|
return PermissionResult.PASS;
|
}
|
}
|
|
if (StringUtils.isEmpty(token)) {
|
log.warn("访问令牌为空,accessToken:" + token);
|
return PermissionResult.TOKEN_INVALID;
|
}
|
|
// OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(token);
|
// if (oAuth2AccessToken == null) {
|
// log.warn("Redis Token Store未找到对应的令牌,accessToken:" + token);
|
// return PermissionResult.TOKEN_INVALID;
|
// }
|
// if (oAuth2AccessToken.isExpired()) {
|
// log.warn("访问令牌已过期,accessToken:" + token);
|
// return PermissionResult.TOKEN_EXPIRED;
|
// }
|
// Long userId = (Long) oAuth2AccessToken.getAdditionalInformation().get("user_id");
|
// String username = (String) oAuth2AccessToken.getAdditionalInformation().get("username");
|
// //用户令牌有效则进行用户信息的注入到头信息中
|
// request.mutate().header("userId", userId.toString()).header("username", username);
|
// //是否开启简单认证模式如果开启则不验证URL权限只验证是否拥有token的合法性
|
// if (isBasic) {
|
// return PermissionResult.PASS;
|
// }
|
// HashSet<String> permissions = (HashSet<String>) redisTemplate.opsForValue().get(SecurityConstants.CACHE_USER_PERMISSIONS_KEY + "urls:" + userId);
|
// if (permissions == null || permissions.isEmpty()) {
|
// log.warn("用户URL权限为空,urlSet:" + JSON.toJSONString(permissions));
|
// return PermissionResult.FORBIDDEN;
|
// }
|
// Optional<String> optional = permissions.stream().filter(url -> pathMatcher.match(url, uri)).findFirst();
|
|
|
String value = redisUtil.getValue(token.substring(token.length() - 32));
|
if(StringUtils.isEmpty(value)){
|
log.warn("Redis Token Store未找到对应的令牌,accessToken:" + token);
|
return PermissionResult.TOKEN_INVALID;
|
}
|
|
return PermissionResult.PASS;
|
}
|
|
/**
|
* 获取请求中token
|
*
|
* @param request
|
* @return token
|
*/
|
private String getAccessToken(ServerHttpRequest request) {
|
String headerValue = request.getHeaders().getFirst("Authorization");
|
if (StringUtils.isEmpty(headerValue)) {
|
return null;
|
}
|
final String bearerType = OAuth2AccessToken.BEARER_TYPE.toLowerCase();
|
if (headerValue.toLowerCase().startsWith(bearerType)) {
|
String substring = headerValue.substring(bearerType.length());
|
return substring.trim();
|
} else {
|
return headerValue;
|
}
|
}
|
|
}
|