赞
踩
之前文章里说过,分布式系统的鉴权有两种方式,一是在网关进行统一的鉴权操作,二是在各个微服务里单独鉴权。
第二种方式比较常见,代码网上也是很多。今天主要是说第一种方式。
重要前提:需要收集各个接口的uri路径和所需权限列表的对应关系,并存入缓存。
服务启动的时候,执行缓存数据的初始化操作:扫描服务内的所有controller接口方法,利用反射,获取方法的完整uri路径,方法上指定注解中的权限值,再存入Redis缓存。
服务启动时做一些操作,方法有很多,可以继承CommandLineRunner或者其他方式。不熟悉的可以去查一下有关资料。
因为后续可能会有很多微服务,因此将该缓存数据的初始化的操作放在common模块中,微服务依赖该模块完成。
- import cn.hutool.core.collection.CollectionUtil;
- import cn.hutool.core.text.StrPool;
- import cn.hutool.core.util.ArrayUtil;
- import com.eden4cloud.common.core.contant.CacheConstants;
- import com.eden4cloud.common.security.anno.Perms;
- import com.eden4cloud.common.security.constant.MethodTypeConstant;
- import com.eden4cloud.common.security.utils.RequestUriUtils;
- import com.eden4cloud.common.util.StringUtils;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.poi.util.StringUtil;
- import org.springframework.beans.BeansException;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
- import org.springframework.core.annotation.AnnotationUtils;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Controller;
-
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- /**
- * @Param:
- * @Return:
- * @Date: 2022/12/3 15:31
- * @Author: Yan
- * @Description: 接口权限初始化采集,获取数据库中的权限标识,没有权限标识的其它接口使用**表示
- */
- @Slf4j
- public class ApiPermsInit implements ApplicationContextAware {
-
- /**
- * 接口路径及权限列表
- * 比如:/user/list<br>
- * 不支持@PathVariable格式的URI
- */
- public static List<Map<String, String>> oauthUrls = new ArrayList<>();
- Map<String, String> uriAuthMap = new HashMap<>();
-
- /**
- * Url参数需要解密的配置
- * 比如:/user/list?name=加密内容<br>
- * 格式:Key API路径 Value 需要解密的字段
- * 示列:/user/list [name,age]
- */
- public static Map<String, List<String>> requestDecryptParamMap = new HashMap<>();
-
- private String applicationPath;
-
- @Autowired
- private RedisTemplate redisTemplate;
-
- @Override
- public void setApplicationContext(ApplicationContext ctx) throws BeansException {
- this.applicationPath = ctx.getEnvironment().getProperty("spring.application.name");
- Map<String, Object> beanMap = ctx.getBeansWithAnnotation(Controller.class);
- initData(beanMap);
- if (CollectionUtil.isNotEmpty(uriAuthMap)) {
- redisTemplate.boundHashOps(CacheConstants.OAUTH_URLS).putAll(uriAuthMap);
- }
- }
-
- /**
- * 初始化,获取所有接口的加解密配置状态并保存
- *
- * @param beanMap
- */
- private void initData(Map<String, Object> beanMap) {
- if (beanMap != null) {
- beanMap.values().parallelStream().map(Object::getClass).forEach(clz -> {
- for (Method method : clz.getDeclaredMethods()) {
- String uriKey = RequestUriUtils.getApiUri(clz, method, applicationPath);
- //收集带有Perms注解的api接口的uri路径
- Perms perms = AnnotationUtils.findAnnotation(method, Perms.class);
- if (StringUtils.isNotEmpty(uriKey) && perms != null && ArrayUtil.isNotEmpty(perms.value())) {
- //解析权限标识
- String authValue = StringUtil.join(perms.value(), StrPool.COMMA);
- uriAuthMap.put(uriKey, authValue);
- } else if (uriKey.startsWith(MethodTypeConstant.GET)) {
- /* 屏蔽没有请求方式的api接口 */
- //没有权限标识的,直接使用**表示
- uriAuthMap.put(uriKey, "**");
- } else if (uriKey.startsWith(MethodTypeConstant.POST)) {
- //没有权限标识的,直接使用**表示
- uriAuthMap.put(uriKey, "**");
- } else if (uriKey.startsWith(MethodTypeConstant.PUT)) {
- //没有权限标识的,直接使用**表示
- uriAuthMap.put(uriKey, "**");
- } else if (uriKey.startsWith(MethodTypeConstant.DELETE)) {
- //没有权限标识的,直接使用**表示
- uriAuthMap.put(uriKey, "**");
- } else {
- //不在上述情况中的,一般为框架提供的api接口
- log.info(uriKey);
- }
- }
- });
- }
- }
- }
- import com.eden4cloud.common.security.constant.MethodTypeConstant;
- import org.springframework.core.annotation.AnnotationUtils;
- import org.springframework.web.bind.annotation.*;
-
- import java.lang.reflect.Method;
-
- public class RequestUriUtils {
-
- private static final String SEPARATOR = "/";
-
- /**
- * 获取接口的uri路径
- *
- * @param clz
- * @param method
- * @param applicationPath
- * @return
- */
- public static String getApiUri(Class<?> clz, Method method, String applicationPath) {
- String methodType = "";
- StringBuilder uri = new StringBuilder();
-
- // 处理类路径
- RequestMapping reqMapping = AnnotationUtils.findAnnotation(clz, RequestMapping.class);
- if (reqMapping != null && reqMapping.value().length > 0) {
- uri.append(formatUri(reqMapping.value()[0]));
- }
-
- //处理方法上的路径
- GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
- PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
- RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
- PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
- DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
-
- if (getMapping != null && getMapping.value().length > 0) {
- methodType = MethodTypeConstant.GET;
- uri.append(formatUri(getMapping.value()[0]));
- } else if (postMapping != null && postMapping.value().length > 0) {
- methodType = MethodTypeConstant.POST;
- uri.append(formatUri(postMapping.value()[0]));
- } else if (putMapping != null && putMapping.value().length > 0) {
- methodType = MethodTypeConstant.PUT;
- uri.append(formatUri(putMapping.value()[0]));
- } else if (deleteMapping != null && deleteMapping.value().length > 0) {
- methodType = MethodTypeConstant.DELETE;
- uri.append(formatUri(deleteMapping.value()[0]));
- } else if (requestMapping != null && requestMapping.value().length > 0) {
- RequestMethod requestMethod = RequestMethod.GET;
- if (requestMapping.method().length > 0) {
- requestMethod = requestMapping.method()[0];
- }
- methodType = requestMethod.name().toLowerCase() + ":";
- uri.append(formatUri(requestMapping.value()[0]));
- }
-
- // 框架自带的接口,返回null后,直接忽略处理
- if (uri.indexOf("${") > 0) {
- return "";
- }
-
- // 针对Rest请求,路径上的请求参数进行处理,以**代替
- int idx = uri.indexOf("{");
- if (idx > 0) {
- uri = new StringBuilder(uri.substring(0, idx)).append("**");
- }
-
- return methodType + SEPARATOR + applicationPath + uri;
- }
-
- private static String formatUri(String uri) {
- if (uri.startsWith(SEPARATOR)) {
- return uri;
- }
- return SEPARATOR + uri;
- }
- }
收集结果:
说明:
- 要求一个请求的完整路径格式为:请求方式:/服务名/类路径/方法路径;
- 请求方式必须要有,防止路径处理后,会出现重复,加上请求方式可以极大避免;
- 服务名是为了做网关路由使用,在配置网关的路由规则时,断言的路由规则即为/服务名;代码里获取服务路径的方式是:ctx.getEnvironment().getProperty("spring.application.name");如果觉得不安全可以在yml文件中自定义一个路径名,改一下此处的获取值即可。只需要记得一定要和网关的路由断言规则匹配就行!!!!
- 类路径必须有。
- 方法路径必须有。方法上的第一个路径必须是固定路径,而不能是请求参数,另外不同方法上的第一个固定路径避免设置成相同的;也是为了防止最终出现请求方式相同、路径也相同的情况。
- 对Rest风格,且方法路径上带有路径参数的路径必须做特殊处理,即将路径参数替换成**。举例如下:原完整路径为/user-Service/user/queryUser/{username}/{age},方法上的路径为/queryUser/{username}/{age},不论有多少个路径参数,从第一个路径参数开始,全部替换掉,处理为/queryUser/**,最终存入缓存所使用的完整路径为:GET:/user-Service/user/queryUser/**。否则当请求到达网关,你想要根据路径去缓存中匹配对应的路径时,你会发现没办法处理,因为从请求的uri路径上你是看不出来哪是固定路径,哪是路径参数的。例如:/user-Service/user/queryUser/zhangsan/18,这个例子你虽然你看都知道哪个是路径参数,但毕竟是框架,万一路径上有很多的/../../..,还怎么猜?有些规则该定死,还是要定死的。
- 真实请求到达网关后,我们对真实请求也做了一些处理,即:只保留/服务名/类路径/方法路径的第一个,后续的路径均使用一个/**替换掉。举例:真实请求为/user-Service/user/queryUser/zhangsan/18,我们处理后为/user-Service/user/queryUser/**。
经过上述的规定和路径处理后,在下面的代码中进行匹配操作:
Gateway网关是基于webFlux实现的,所以和一般微服务整合方式不太一样。
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.ReactiveAuthenticationManager;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.oauth2.common.OAuth2AccessToken;
- import org.springframework.security.oauth2.provider.OAuth2Authentication;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
- import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
- import org.springframework.stereotype.Component;
- import reactor.core.publisher.Mono;
-
- /**
- * @Since: 2023/4/13
- * @Author: Yan
- * @Description Jwt认证管理器,对token的真实性、有效性进行校验
- */
- @Component
- @Slf4j
- public class JwtAuthenticationManager implements ReactiveAuthenticationManager {
-
- @Autowired
- private TokenStore tokenStore;
-
-
- @Override
- public Mono<Authentication> authenticate(Authentication authentication) {
- return Mono.justOrEmpty(authentication)
- .filter(a -> a instanceof BearerTokenAuthenticationToken)
- .cast(BearerTokenAuthenticationToken.class)
- .map(BearerTokenAuthenticationToken::getToken)
- .flatMap((accessToken -> {
- //解析令牌
- OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
- if (oAuth2AccessToken == null) {
- return Mono.error(new InvalidBearerTokenException("无效的token"));
- } else if (oAuth2AccessToken.isExpired()) {
- return Mono.error(new InvalidBearerTokenException("token已过期"));
- }
- OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(accessToken);
- if (oAuth2Authentication == null) {
- return Mono.error(new InvalidBearerTokenException("无效的token"));
- } else {
- return Mono.just(oAuth2Authentication);
- }
- }))
- .cast(Authentication.class);
- }
- }
- import cn.hutool.core.text.AntPathMatcher;
- import cn.hutool.core.text.StrPool;
- import com.eden4cloud.common.core.contant.CacheConstants;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.security.authorization.AuthorizationDecision;
- import org.springframework.security.authorization.ReactiveAuthorizationManager;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.web.server.authorization.AuthorizationContext;
- import org.springframework.stereotype.Component;
- import reactor.core.publisher.Mono;
-
- import java.util.*;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- /**
- * @Since: 2023/4/13
- * @Author: Yan
- * @Description Jwt鉴权管理器:从Redis中获取所请求的Url所需要的权限,和用户token中所携带的权限进行比对
- */
- @Slf4j
- @Component
- public class JwtAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
-
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- private AntPathMatcher matcher = new AntPathMatcher();
-
- /**
- * *****当前匹配方法要求一个API接口方法上必须要有路径*****
- *
- * @param mono
- * @param authorizationContext
- * @return
- */
- @Override
- public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
- // 处理当前请求的uri路径,最终格式为:/服务路径/类路径/方法上的路径 /eden-system/user/add
- StringBuilder builder = new StringBuilder();
- String[] split = authorizationContext.getExchange().getRequest().getURI().getPath().split(StrPool.SLASH);
- String uri = builder.append(StrPool.SLASH).append(split[1])//服务路径
- .append(StrPool.SLASH).append(split[2])//类路径
- .append(StrPool.SLASH).append(split[3])//方法路径
- .toString();
- // 请求方式拼接处理 ,格式为 GET:
- String methodValue = authorizationContext.getExchange().getRequest().getMethodValue() + StrPool.COLON;
- // 获取所有路径的权限列表
- Map<Object, Object> entries = redisTemplate.opsForHash().entries(CacheConstants.OAUTH_PERMS);
- List<String> authorities = new ArrayList<>();
- AtomicBoolean authFlag = new AtomicBoolean(false);
- entries.forEach((k, v) -> {
- // 根据请求uri路径,获取到匹配的缓存权限数据
- if (k.equals(methodValue + uri)
- || matcher.match(methodValue + uri + CacheConstants.FUZZY_PATH, k.toString())) {
- if (CacheConstants.ANONYMOUS.equals(v.toString())) {
- // 权限为**,表示允许匿名访问
- authFlag.set(true);
- } else {
- // 收集当前路径所需的权限列表
- authorities.addAll(Arrays.asList((v.toString()).split(StrPool.COMMA)));
- }
- }
- });
- // Collection<? extends GrantedAuthority> authorities1 = mono.block().getAuthorities();
-
-
- List<String> finalAuthorities = authorities;
- return mono
- //判断是否认证成功
- .filter(Authentication::isAuthenticated)
- //获取认证后的全部权限列表
- .flatMapIterable(Authentication::getAuthorities)
- .map(GrantedAuthority::getAuthority)
- //如果包含在url要求的权限内,则返回true
- .any(auth -> authFlag.get() || finalAuthorities.contains(auth))
- .map(AuthorizationDecision::new)
- .defaultIfEmpty(new AuthorizationDecision(false))
- ;
- }
-
- }
- import com.eden4cloud.gateway.component.JwtAuthorizationManager;
- import com.eden4cloud.gateway.handler.RequestAccessDeniedHandler;
- import com.eden4cloud.gateway.handler.RequestAuthenticationEntrypoint;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.ReactiveAuthenticationManager;
- import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
- import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
- import org.springframework.security.config.web.server.ServerHttpSecurity;
- import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
- import org.springframework.security.web.server.SecurityWebFilterChain;
- import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
- import org.springframework.web.cors.reactive.CorsWebFilter;
-
- /**
- * @Since: 2023/4/2
- * @Author: Yan
- * @Description security安全配置
- */
- @Configuration
- @EnableWebFluxSecurity
- public class EdenGatewayWebSecurityConfig {
-
- @Autowired
- private JwtAuthorizationManager jwtAuthorizationManager;
-
- @Autowired
- private ReactiveAuthenticationManager authenticationManager;
-
- @Autowired
- private RequestAuthenticationEntrypoint requestAuthenticationEntrypoint;
-
- @Autowired
- private RequestAccessDeniedHandler requestAccessDeniedHandler;
-
- @Autowired
- private CorsWebFilter corsWebFilter;
-
- // @Autowired
- // private GlobalAuthenticationFilter authenticationFilter;
-
- @Bean
- public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
- AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(authenticationManager);
- authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
-
- http
- .csrf().disable()
- .authorizeExchange()
- //对oauth的端点进行放行
- .pathMatchers("/eden-oauth/oauth/**").permitAll()
- //其他请求必须鉴权,使用鉴权管理器
- .anyExchange().access(jwtAuthorizationManager)
- .and()
- //鉴权异常处理
- .exceptionHandling()
- .authenticationEntryPoint(requestAuthenticationEntrypoint)
- .accessDeniedHandler(requestAccessDeniedHandler)
- .and()
- //跨域过滤器
- .addFilterAt(corsWebFilter, SecurityWebFiltersOrder.CORS)
- //token认证过滤器
- .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
- // .addFilterAfter(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
- ;
- return http.build();
- }
- }
2.4.将网关作为资源服务
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
- import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
- import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
-
- /**
- * @Author: Yan
- * @Since: 2023/2/4
- * @Description: 资源服务器解析鉴权配置类
- */
- @Configuration
- public class EdenGatewayResourceServerConfig extends ResourceServerConfigurerAdapter {
-
- @Autowired
- private TokenStore tokenStore;
-
- //公钥
- private static final String RESOURCE_ID = "eden-gateway";
-
- /**
- * Http安全配置,对每个到达系统的http请求链接进行校验
- *
- * @param http
- * @throws Exception
- */
- @Override
- public void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/**").permitAll();
- }
-
- /**
- * 资源服务的安全配置
- *
- * @param resources
- * @throws Exception
- */
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
- resources
- .resourceId(RESOURCE_ID)
- .tokenStore(tokenStore)
- .stateless(true);
- }
- }
- import org.springframework.boot.autoconfigure.AutoConfigureAfter;
- import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.reactive.CorsWebFilter;
- import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
- import org.springframework.web.util.pattern.PathPatternParser;
-
- /**
- * @CreateTime: 2023-01-2023/1/11 15:09
- * @Author: Yan
- * @Description 注册网关过滤器示例
- */
- @Configuration
- @AutoConfigureAfter(GatewayAutoConfiguration.class)
- public class GatewayRoutesConfiguration {
-
- /**
- * 跨域配置
- *
- * @return
- */
- @Bean
- public CorsWebFilter corsWebFilter() {
- CorsConfiguration config = new CorsConfiguration();
- config.addAllowedMethod("*");
- config.addAllowedOrigin("*");
- config.addAllowedHeader("*");
-
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
- source.registerCorsConfiguration("/**", config);
-
- return new CorsWebFilter(source);
- }
-
- // @Bean(name = "ipKeyResolver")
- // public KeyResolver userIpKeyResolver() {
- // return new IpKeyResolver();
- // }
- //
- // @Bean
- // public RouteLocator routeLocator(RouteLocatorBuilder builder) {
- // StripPrefixGatewayFilterFactory filterFactory = new StripPrefixGatewayFilterFactory();
- // StripPrefixGatewayFilterFactory.Config partsConfig = filterFactory.newConfig();
- // partsConfig.setParts(1);
- //
- // MyGatewayFilterFactory factory = new MyGatewayFilterFactory();
- // MyGatewayFilterFactory.PathsConfig pathsConfig = factory.newConfig();
- // pathsConfig.setPaths(Arrays.asList("/AAA", "/BBB"));
- //
- // return builder.routes()
- // .route(r -> r.path("/life/**")
- // .uri("lb://eden-life")
- // .filters(factory.apply(pathsConfig), filterFactory.apply(partsConfig))
- // .id("eden-life"))
- // .build();
- // }
- }
- import cn.hutool.core.util.StrUtil;
- import com.alibaba.fastjson2.JSONObject;
- import com.eden4cloud.common.security.exception.InvalidTokenException;
- import com.eden4cloud.common.security.utils.JwtUtils;
- import com.eden4cloud.common.util.sign.Base64;
- import lombok.Data;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
- import org.springframework.http.server.reactive.ServerHttpRequest;
- import org.springframework.security.oauth2.common.OAuth2AccessToken;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- import java.nio.charset.StandardCharsets;
- import java.util.Collections;
- import java.util.List;
- import java.util.Map;
-
- /**
- * @CreateTime: 2023-01-2023/1/11 9:34
- * @Author: Yan
- * @Description 自定义鉴权过滤器工厂:设置访问白名单;重新封装鉴权认证通过的请求头;
- */
- //1. 编写实现类继承AbstractGatewayFilterFactory抽象类
- @Component
- public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.IgnoreUrlsConfig> {//4. 指定泛型,静态的内部实体类
-
- @Autowired
- private TokenStore tokenStore;
-
- //5. 重写无参构造方法,指定内部实体类接收参数
- public AuthGatewayFilterFactory() {
- super(IgnoreUrlsConfig.class);
- }
-
- @Override
- public GatewayFilter apply(IgnoreUrlsConfig config) {
- return (exchange, chain) -> {
- ServerHttpRequest request = exchange.getRequest();
- //直接放行部分请求路径,如登录、退出等 需要排除的路径弄成可yaml配置的
- boolean flag = config.ignoreUrls.contains(request.getURI().getPath())
- || config.ignoreUrls.stream().filter(i -> i.endsWith("/**"))
- .anyMatch(i -> exchange.getRequest().getURI().getPath().startsWith(i.replace("**", "")));
- if (flag) {
- return chain.filter(exchange);
- }
- //重新封装新的请求中数据
- ServerWebExchange webExchange = rebuildRequestHeaders(exchange, request);
- if (webExchange == null) {
- return Mono.error(new InvalidTokenException());
- }
- return chain.filter(webExchange);
- };
- }
-
- /**
- * 重新封装新的请求数据
- *
- * @param exchange
- * @param request
- * @return
- */
- private ServerWebExchange rebuildRequestHeaders(ServerWebExchange exchange, ServerHttpRequest request) {
- //获取请求头中的令牌
- String token = JwtUtils.getToken(request);
- if (StrUtil.isBlank(token)) {
- return null;
- }
-
- OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
- Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
- List<String> authorities = (List<String>) additionalInformation.get("authorities");
- //获取用户名
- String username = additionalInformation.get("user_name").toString();
- //获取用户权限
- JSONObject jsonObject = new JSONObject();
- jsonObject.put("username", username);
- jsonObject.put("authorities", authorities);
- //将解析后的token加密后重新放入请求头,方便后续微服务解析获取用户信息
- String base64 = Base64.encode(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
- request = exchange.getRequest().mutate().header("token", base64).build();
- exchange.mutate().request(request);
- return exchange;
- }
-
- /**
- * 6. 重写shortcutFieldOrder()指定接收参数的字段顺序
- * Returns hints about the number of args and the order for shortcut parsing.
- *
- * @return the list of hints
- */
- @Override
- public List<String> shortcutFieldOrder() {
- return Collections.singletonList("ignoreUrls");
- }
-
- /**
- * 7. 重写shortcutType()指定接收参数的字段类型
- */
- @Override
- public ShortcutType shortcutType() {
- return ShortcutType.GATHER_LIST;
- }
-
- /**
- * 3. 定义匿名内部实体类,定义接收参数的字段
- */
- @Data
- public static class IgnoreUrlsConfig {
- //传递多个参数
- private List<String> ignoreUrls;
- }
- }
- #路由配置
- spring:
- cloud:
- gateway:
- routes:
- - id: eden-life
- uri: lb://eden-life
- predicates:
- - Path=/eden-life/**
- filters:
- - StripPrefix=1
- - name: Auth
- args:
- ignoreUrls:
- - /auth-server/login
- - /oauth/**
- - /life/**
2.6和2.7主要是展示配置了动态的请求白名单功能。基于nacos的配置中心功能,可以实现动态刷新,白名单设置实时生效。
2.8.异常处理
- import cn.hutool.json.JSONUtil;
- import com.eden4cloud.common.core.entity.R;
- import com.eden4cloud.common.security.exception.SecurityExceptionEnum;
- import org.apache.http.HttpHeaders;
- import org.springframework.core.io.buffer.DataBuffer;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.MediaType;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.security.access.AccessDeniedException;
- import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- import java.nio.charset.StandardCharsets;
-
- /**
- * @Since: 2023/4/17
- * @Author: Yan
- * @Description TODO
- */
- @Component
- public class RequestAccessDeniedHandler implements ServerAccessDeniedHandler {
-
- @Override
- public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
- ServerHttpResponse response = exchange.getResponse();
- response.setStatusCode(HttpStatus.OK);
- response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
- DataBuffer buffer = response.bufferFactory()
- .wrap(JSONUtil.toJsonStr(R.error(SecurityExceptionEnum.NO_PERMISSION.getMsg())).getBytes(StandardCharsets.UTF_8));
- return response.writeWith(Mono.just(buffer));
- }
- }
- import cn.hutool.json.JSONUtil;
- import com.eden4cloud.common.core.entity.R;
- import com.eden4cloud.common.security.exception.SecurityExceptionEnum;
- import org.apache.http.HttpHeaders;
- import org.springframework.core.io.buffer.DataBuffer;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.MediaType;
- import org.springframework.http.server.reactive.ServerHttpResponse;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
- import org.springframework.stereotype.Component;
- import org.springframework.web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
-
- import java.nio.charset.StandardCharsets;
-
- /**
- * @Since: 2023/4/17
- * @Author: Yan
- * @Description TODO
- */
- @Component
- public class RequestAuthenticationEntrypoint implements ServerAuthenticationEntryPoint {
-
- @Override
- public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
- ServerHttpResponse response = exchange.getResponse();
- response.setStatusCode(HttpStatus.OK);
- response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
- DataBuffer buffer = response.bufferFactory()
- .wrap(JSONUtil.toJsonStr(R.error(SecurityExceptionEnum.INVALID_TOKEN.getMsg())).getBytes(StandardCharsets.UTF_8));
- return response.writeWith(Mono.just(buffer));
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。