/*
|
* 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.cache.RedisFastJsonTemplate;
|
import cn.mb.cloud.common.core.constant.SecurityConstants;
|
import cn.mb.cloud.common.core.constant.enums.ErrorCodeConstants;
|
import cn.mb.cloud.common.core.util.ResponseData;
|
import cn.mb.cloud.common.gateway.common.PermissionResult;
|
import cn.mb.cloud.common.gateway.config.FilterIgnorePropertiesConfig;
|
import cn.mb.cloud.gateway.handler.AppTypeContextHolder;
|
import cn.mb.cloud.gateway.service.MbCloudRedisTokenStore;
|
import com.alibaba.fastjson.JSON;
|
import com.dsh.utils.login.LoginHelper;
|
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.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.HashMap;
|
import java.util.HashSet;
|
import java.util.Map;
|
import java.util.Optional;
|
|
/**
|
* @author: jason
|
* @Date: 2019-05-20 15:50
|
* @Description:
|
*/
|
@Slf4j
|
@Component
|
public class PermissionsFilter implements GlobalFilter, Ordered {
|
|
@Autowired
|
private RedisFastJsonTemplate redisTemplate;
|
|
@Autowired
|
private FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
|
|
@Autowired
|
private MbCloudRedisTokenStore redisTokenStore;
|
|
@Value("${cn.mbcloud.gateway.permissions.basic}")
|
private Boolean isBasic = Boolean.FALSE;
|
|
|
private AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
/**
|
* Redis Token 仓库前缀
|
*/
|
String REDIS_TOKEN_STORE_PREFIX = "oauth2_";
|
|
|
/**
|
* 拦截存储资源模块
|
*/
|
|
@Value("${ignore.image.prefix}")
|
String IGNORE_IMAGE_PREFIX;
|
/**
|
* 放行存储资源地址
|
*/
|
@Value("${ignore.image.uris}")
|
String IGNORE_IMAGE_URIS;
|
/**
|
* 拦截综合管理后台模块
|
*/
|
@Value("${ignore.upms.prefix}")
|
String IGNORE_UPMS_PREFIX;
|
/**
|
* 放行综合管理后台地址
|
*/
|
@Value("${ignore.upms.uris}")
|
String IGNORE_UPMS_URIS;
|
|
/**
|
* 拦截APP模块
|
*/
|
@Value("${ignore.app.prefix}")
|
String IGNORE_APP_PREFIX;
|
/**
|
* 放行APP地址
|
*/
|
@Value(("${ignore.app.uris}"))
|
String IGNORE_APP_URIS;
|
|
@Override
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
ServerHttpRequest request = exchange.getRequest();
|
String appType = request.getHeaders().getFirst(AppTypeContextHolder.APP_TYPE);
|
AppTypeContextHolder.setDeviceType(appType);
|
PermissionResult permissionResult = hasPermission(exchange, request);
|
AppTypeContextHolder.clear();
|
if (permissionResult != PermissionResult.PASS) {
|
ServerHttpResponse response = exchange.getResponse();
|
response.setRawStatusCode(permissionResult.getValue());
|
ResponseData responseData = ResponseData.builder().code(ErrorCodeConstants.FAIL.getValue()).msg("您还未登录").build();
|
byte[] bits = JSON.toJSONBytes(responseData);
|
DataBuffer buffer = response.bufferFactory().wrap(bits);
|
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
return response.writeWith(Mono.just(buffer));
|
} else {
|
return chain.filter(exchange.mutate().request(request.mutate().build()).build());
|
}
|
}
|
|
@Override
|
public int getOrder() {
|
return -Integer.MAX_VALUE;
|
}
|
|
public PermissionResult hasPermission(ServerWebExchange exchange, ServerHttpRequest request) {
|
String uri = request.getURI().getRawPath();
|
|
// 获取头信息
|
Map<String, String> params = getHeaderParams(request);
|
log.info("请求操作 -> " + uri + " || 请求参数 -> " + request.getQueryParams() + " || Header信息 -> " + params);
|
if(uri.contains("v2/api-docs")){
|
return PermissionResult.PASS;
|
}
|
// 校验放行URL地址
|
// PermissionResult result = ignoreURLFilter(uri, request);
|
// if (result != PermissionResult.PASS) {
|
// return result;
|
// }
|
// ----------------------------------------------------------------- 放行存储资源模块-IMAGE
|
if (uri.startsWith(IGNORE_IMAGE_PREFIX)) {
|
return PermissionResult.PASS;
|
}
|
|
// ----------------------------------------------------------------- 放行APP模块-APP
|
String token = getAccessToken(request);
|
if (uri.startsWith(IGNORE_APP_PREFIX) && token == null) {
|
if (IGNORE_APP_URIS.contains(uri) || uri.contains("/app/courseorder/notify")) {
|
return PermissionResult.PASS;
|
}
|
}
|
|
// ----------------------------------------------------------------- 放行综合后台授权登录-UPMS
|
if (uri.startsWith(IGNORE_UPMS_PREFIX)) {
|
if (IGNORE_UPMS_URIS.contains(uri)) {
|
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;
|
}
|
}
|
|
// 判断是否是忽略列表中的
|
if (!filterIgnorePropertiesConfig.getPermissionUrls().isEmpty()) {
|
Optional<String> optional = filterIgnorePropertiesConfig.getPermissionUrls().stream().filter(url -> pathMatcher.match(url, uri)).findFirst();
|
if (optional.isPresent()) {
|
return PermissionResult.PASS;
|
}
|
}
|
|
String accessToken = getAccessToken(request);
|
if (StringUtils.isEmpty(accessToken)) {
|
log.warn("访问令牌为空,accessToken:" + accessToken);
|
return PermissionResult.TOKEN_INVALID;
|
}
|
|
OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(accessToken);
|
if (oAuth2AccessToken == null) {
|
log.warn("Redis Token Store未找到对应的令牌,accessToken:" + accessToken);
|
return PermissionResult.TOKEN_INVALID;
|
}
|
if (oAuth2AccessToken.isExpired()) {
|
log.warn("访问令牌已过期,accessToken:" + accessToken);
|
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;
|
}
|
|
log.info("用户 -> " + username + " || 请求操作 -> " + uri + " || 请求参数 -> " + request.getQueryParams() + " || Header信息 -> " + params);
|
|
Optional<String> optional = permissions.stream().filter(url -> pathMatcher.match(url, uri)).findFirst();
|
return optional.isPresent() ? PermissionResult.PASS : PermissionResult.FORBIDDEN;
|
}
|
|
/**
|
* 获取请求中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;
|
}
|
}
|
|
/**
|
* 获取请求中Header参数
|
*
|
* @param request
|
* @return
|
*/
|
private Map<String, String> getHeaderParams(ServerHttpRequest request) {
|
String device = request.getHeaders().getFirst("Device");
|
String deviceModel = request.getHeaders().getFirst("DeviceModel");
|
String jPushDevice = request.getHeaders().getFirst("JPushDevice");
|
String versionCode = request.getHeaders().getFirst("VersionCode");
|
Map<String, String> map = new HashMap<>();
|
map.put("device", device);
|
map.put("deviceModel", deviceModel);
|
map.put("jPushDevice", jPushDevice);
|
map.put("versionCode", versionCode);
|
return map;
|
}
|
|
@Bean
|
public RedisTokenStore redisTokenStore(RedisConnectionFactory redisConnectionFactory) {
|
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
|
tokenStore.setPrefix(REDIS_TOKEN_STORE_PREFIX);
|
return tokenStore;
|
}
|
|
private void setIgnoreLoginAuthHeaderToken(ServerHttpRequest request) {
|
String accessToken = getAccessToken(request);
|
if (StringUtils.isEmpty(accessToken)) {
|
log.warn("访问令牌为空,accessToken:" + accessToken);
|
return;
|
}
|
|
OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(accessToken);
|
if (oAuth2AccessToken == null) {
|
log.warn("Redis Token Store未找到对应的令牌,accessToken:" + accessToken);
|
return;
|
}
|
if (oAuth2AccessToken.isExpired()) {
|
log.warn("访问令牌已过期,accessToken:" + accessToken);
|
return;
|
}
|
Long userId = (Long) oAuth2AccessToken.getAdditionalInformation().get("user_id");
|
String username = (String) oAuth2AccessToken.getAdditionalInformation().get("username");
|
//用户令牌有效则进行用户信息的注入到头信息中
|
request.mutate().header("userId", userId.toString()).header("username", username);
|
}
|
}
|