/* * 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 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 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 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 permissions = (HashSet) 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 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 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 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); } }