当前位置:   article > 正文

Spring Gateway + Oauth2 + Jwt网关统一鉴权_oauth2.1鉴权

oauth2.1鉴权

之前文章里说过,分布式系统的鉴权有两种方式,一是在网关进行统一的鉴权操作,二是在各个微服务里单独鉴权。

第二种方式比较常见,代码网上也是很多。今天主要是说第一种方式。

1.网关鉴权的流程

重要前提:需要收集各个接口的uri路径和所需权限列表的对应关系,并存入缓存。

2.收集uri路径和对应权限

服务启动的时候,执行缓存数据的初始化操作:扫描服务内的所有controller接口方法,利用反射,获取方法的完整uri路径,方法上指定注解中的权限值,再存入Redis缓存

服务启动时做一些操作,方法有很多,可以继承CommandLineRunner或者其他方式。不熟悉的可以去查一下有关资料。

因为后续可能会有很多微服务,因此将该缓存数据的初始化的操作放在common模块中,微服务依赖该模块完成。

1.1.初始化方法

  1. import cn.hutool.core.collection.CollectionUtil;
  2. import cn.hutool.core.text.StrPool;
  3. import cn.hutool.core.util.ArrayUtil;
  4. import com.eden4cloud.common.core.contant.CacheConstants;
  5. import com.eden4cloud.common.security.anno.Perms;
  6. import com.eden4cloud.common.security.constant.MethodTypeConstant;
  7. import com.eden4cloud.common.security.utils.RequestUriUtils;
  8. import com.eden4cloud.common.util.StringUtils;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.apache.poi.util.StringUtil;
  11. import org.springframework.beans.BeansException;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.context.ApplicationContext;
  14. import org.springframework.context.ApplicationContextAware;
  15. import org.springframework.core.annotation.AnnotationUtils;
  16. import org.springframework.data.redis.core.RedisTemplate;
  17. import org.springframework.stereotype.Controller;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. /**
  24. * @Param:
  25. * @Return:
  26. * @Date: 2022/12/3 15:31
  27. * @Author: Yan
  28. * @Description: 接口权限初始化采集,获取数据库中的权限标识,没有权限标识的其它接口使用**表示
  29. */
  30. @Slf4j
  31. public class ApiPermsInit implements ApplicationContextAware {
  32. /**
  33. * 接口路径及权限列表
  34. * 比如:/user/list<br>
  35. * 不支持@PathVariable格式的URI
  36. */
  37. public static List<Map<String, String>> oauthUrls = new ArrayList<>();
  38. Map<String, String> uriAuthMap = new HashMap<>();
  39. /**
  40. * Url参数需要解密的配置
  41. * 比如:/user/list?name=加密内容<br>
  42. * 格式:Key API路径 Value 需要解密的字段
  43. * 示列:/user/list [name,age]
  44. */
  45. public static Map<String, List<String>> requestDecryptParamMap = new HashMap<>();
  46. private String applicationPath;
  47. @Autowired
  48. private RedisTemplate redisTemplate;
  49. @Override
  50. public void setApplicationContext(ApplicationContext ctx) throws BeansException {
  51. this.applicationPath = ctx.getEnvironment().getProperty("spring.application.name");
  52. Map<String, Object> beanMap = ctx.getBeansWithAnnotation(Controller.class);
  53. initData(beanMap);
  54. if (CollectionUtil.isNotEmpty(uriAuthMap)) {
  55. redisTemplate.boundHashOps(CacheConstants.OAUTH_URLS).putAll(uriAuthMap);
  56. }
  57. }
  58. /**
  59. * 初始化,获取所有接口的加解密配置状态并保存
  60. *
  61. * @param beanMap
  62. */
  63. private void initData(Map<String, Object> beanMap) {
  64. if (beanMap != null) {
  65. beanMap.values().parallelStream().map(Object::getClass).forEach(clz -> {
  66. for (Method method : clz.getDeclaredMethods()) {
  67. String uriKey = RequestUriUtils.getApiUri(clz, method, applicationPath);
  68. //收集带有Perms注解的api接口的uri路径
  69. Perms perms = AnnotationUtils.findAnnotation(method, Perms.class);
  70. if (StringUtils.isNotEmpty(uriKey) && perms != null && ArrayUtil.isNotEmpty(perms.value())) {
  71. //解析权限标识
  72. String authValue = StringUtil.join(perms.value(), StrPool.COMMA);
  73. uriAuthMap.put(uriKey, authValue);
  74. } else if (uriKey.startsWith(MethodTypeConstant.GET)) {
  75. /* 屏蔽没有请求方式的api接口 */
  76. //没有权限标识的,直接使用**表示
  77. uriAuthMap.put(uriKey, "**");
  78. } else if (uriKey.startsWith(MethodTypeConstant.POST)) {
  79. //没有权限标识的,直接使用**表示
  80. uriAuthMap.put(uriKey, "**");
  81. } else if (uriKey.startsWith(MethodTypeConstant.PUT)) {
  82. //没有权限标识的,直接使用**表示
  83. uriAuthMap.put(uriKey, "**");
  84. } else if (uriKey.startsWith(MethodTypeConstant.DELETE)) {
  85. //没有权限标识的,直接使用**表示
  86. uriAuthMap.put(uriKey, "**");
  87. } else {
  88. //不在上述情况中的,一般为框架提供的api接口
  89. log.info(uriKey);
  90. }
  91. }
  92. });
  93. }
  94. }
  95. }

1.2.对应的工具类

  1. import com.eden4cloud.common.security.constant.MethodTypeConstant;
  2. import org.springframework.core.annotation.AnnotationUtils;
  3. import org.springframework.web.bind.annotation.*;
  4. import java.lang.reflect.Method;
  5. public class RequestUriUtils {
  6. private static final String SEPARATOR = "/";
  7. /**
  8. * 获取接口的uri路径
  9. *
  10. * @param clz
  11. * @param method
  12. * @param applicationPath
  13. * @return
  14. */
  15. public static String getApiUri(Class<?> clz, Method method, String applicationPath) {
  16. String methodType = "";
  17. StringBuilder uri = new StringBuilder();
  18. // 处理类路径
  19. RequestMapping reqMapping = AnnotationUtils.findAnnotation(clz, RequestMapping.class);
  20. if (reqMapping != null && reqMapping.value().length > 0) {
  21. uri.append(formatUri(reqMapping.value()[0]));
  22. }
  23. //处理方法上的路径
  24. GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
  25. PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
  26. RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
  27. PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
  28. DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
  29. if (getMapping != null && getMapping.value().length > 0) {
  30. methodType = MethodTypeConstant.GET;
  31. uri.append(formatUri(getMapping.value()[0]));
  32. } else if (postMapping != null && postMapping.value().length > 0) {
  33. methodType = MethodTypeConstant.POST;
  34. uri.append(formatUri(postMapping.value()[0]));
  35. } else if (putMapping != null && putMapping.value().length > 0) {
  36. methodType = MethodTypeConstant.PUT;
  37. uri.append(formatUri(putMapping.value()[0]));
  38. } else if (deleteMapping != null && deleteMapping.value().length > 0) {
  39. methodType = MethodTypeConstant.DELETE;
  40. uri.append(formatUri(deleteMapping.value()[0]));
  41. } else if (requestMapping != null && requestMapping.value().length > 0) {
  42. RequestMethod requestMethod = RequestMethod.GET;
  43. if (requestMapping.method().length > 0) {
  44. requestMethod = requestMapping.method()[0];
  45. }
  46. methodType = requestMethod.name().toLowerCase() + ":";
  47. uri.append(formatUri(requestMapping.value()[0]));
  48. }
  49. // 框架自带的接口,返回null后,直接忽略处理
  50. if (uri.indexOf("${") > 0) {
  51. return "";
  52. }
  53. // 针对Rest请求,路径上的请求参数进行处理,以**代替
  54. int idx = uri.indexOf("{");
  55. if (idx > 0) {
  56. uri = new StringBuilder(uri.substring(0, idx)).append("**");
  57. }
  58. return methodType + SEPARATOR + applicationPath + uri;
  59. }
  60. private static String formatUri(String uri) {
  61. if (uri.startsWith(SEPARATOR)) {
  62. return uri;
  63. }
  64. return SEPARATOR + uri;
  65. }
  66. }

收集结果:

 说明:

  1. 要求一个请求的完整路径格式为:请求方式:/服务名/类路径/方法路径;
  2. 请求方式必须要有,防止路径处理后,会出现重复,加上请求方式可以极大避免;
  3. 服务名是为了做网关路由使用,在配置网关的路由规则时,断言的路由规则即为/服务名;代码里获取服务路径的方式是:ctx.getEnvironment().getProperty("spring.application.name");如果觉得不安全可以在yml文件中自定义一个路径名,改一下此处的获取值即可。只需要记得一定要和网关的路由断言规则匹配就行!!!!
  4. 类路径必须有。
  5. 方法路径必须有。方法上的第一个路径必须是固定路径,而不能是请求参数,另外不同方法上的第一个固定路径避免设置成相同的;也是为了防止最终出现请求方式相同、路径也相同的情况。
  6. 对Rest风格,且方法路径上带有路径参数的路径必须做特殊处理,即将路径参数替换成**。举例如下:原完整路径为/user-Service/user/queryUser/{username}/{age},方法上的路径为/queryUser/{username}/{age}不论有多少个路径参数,从第一个路径参数开始,全部替换掉,处理为/queryUser/**,最终存入缓存所使用的完整路径为:GET:/user-Service/user/queryUser/**。否则当请求到达网关,你想要根据路径去缓存中匹配对应的路径时,你会发现没办法处理,因为从请求的uri路径上你是看不出来哪是固定路径,哪是路径参数的。例如:/user-Service/user/queryUser/zhangsan/18,这个例子你虽然你看都知道哪个是路径参数,但毕竟是框架,万一路径上有很多的/../../..,还怎么猜?有些规则该定死,还是要定死的。
  7. 真实请求到达网关后,我们对真实请求也做了一些处理,即:只保留/服务名/类路径/方法路径的第一个,后续的路径均使用一个/**替换掉。举例:真实请求为/user-Service/user/queryUser/zhangsan/18,我们处理后为/user-Service/user/queryUser/**。

经过上述的规定和路径处理后,在下面的代码中进行匹配操作:

  •  methodValue + uri + CacheConstants.FUZZY_PATH即为/user-Service/user/queryUser/**
  • k.toString()即为/user-Service/user/queryUser/**;
  • 判断结果为true

2.网关整合Security Oauth2

Gateway网关是基于webFlux实现的,所以和一般微服务整合方式不太一样。

2.1.认证管理器

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.security.authentication.ReactiveAuthenticationManager;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  6. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  7. import org.springframework.security.oauth2.provider.token.TokenStore;
  8. import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
  9. import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
  10. import org.springframework.stereotype.Component;
  11. import reactor.core.publisher.Mono;
  12. /**
  13. * @Since: 2023/4/13
  14. * @Author: Yan
  15. * @Description Jwt认证管理器,对token的真实性、有效性进行校验
  16. */
  17. @Component
  18. @Slf4j
  19. public class JwtAuthenticationManager implements ReactiveAuthenticationManager {
  20. @Autowired
  21. private TokenStore tokenStore;
  22. @Override
  23. public Mono<Authentication> authenticate(Authentication authentication) {
  24. return Mono.justOrEmpty(authentication)
  25. .filter(a -> a instanceof BearerTokenAuthenticationToken)
  26. .cast(BearerTokenAuthenticationToken.class)
  27. .map(BearerTokenAuthenticationToken::getToken)
  28. .flatMap((accessToken -> {
  29. //解析令牌
  30. OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
  31. if (oAuth2AccessToken == null) {
  32. return Mono.error(new InvalidBearerTokenException("无效的token"));
  33. } else if (oAuth2AccessToken.isExpired()) {
  34. return Mono.error(new InvalidBearerTokenException("token已过期"));
  35. }
  36. OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(accessToken);
  37. if (oAuth2Authentication == null) {
  38. return Mono.error(new InvalidBearerTokenException("无效的token"));
  39. } else {
  40. return Mono.just(oAuth2Authentication);
  41. }
  42. }))
  43. .cast(Authentication.class);
  44. }
  45. }

2.2..鉴权管理器

  1. import cn.hutool.core.text.AntPathMatcher;
  2. import cn.hutool.core.text.StrPool;
  3. import com.eden4cloud.common.core.contant.CacheConstants;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.security.authorization.AuthorizationDecision;
  8. import org.springframework.security.authorization.ReactiveAuthorizationManager;
  9. import org.springframework.security.core.Authentication;
  10. import org.springframework.security.core.GrantedAuthority;
  11. import org.springframework.security.web.server.authorization.AuthorizationContext;
  12. import org.springframework.stereotype.Component;
  13. import reactor.core.publisher.Mono;
  14. import java.util.*;
  15. import java.util.concurrent.atomic.AtomicBoolean;
  16. /**
  17. * @Since: 2023/4/13
  18. * @Author: Yan
  19. * @Description Jwt鉴权管理器:从Redis中获取所请求的Url所需要的权限,和用户token中所携带的权限进行比对
  20. */
  21. @Slf4j
  22. @Component
  23. public class JwtAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
  24. @Autowired
  25. private RedisTemplate<String, Object> redisTemplate;
  26. private AntPathMatcher matcher = new AntPathMatcher();
  27. /**
  28. * *****当前匹配方法要求一个API接口方法上必须要有路径*****
  29. *
  30. * @param mono
  31. * @param authorizationContext
  32. * @return
  33. */
  34. @Override
  35. public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
  36. // 处理当前请求的uri路径,最终格式为:/服务路径/类路径/方法上的路径 /eden-system/user/add
  37. StringBuilder builder = new StringBuilder();
  38. String[] split = authorizationContext.getExchange().getRequest().getURI().getPath().split(StrPool.SLASH);
  39. String uri = builder.append(StrPool.SLASH).append(split[1])//服务路径
  40. .append(StrPool.SLASH).append(split[2])//类路径
  41. .append(StrPool.SLASH).append(split[3])//方法路径
  42. .toString();
  43. // 请求方式拼接处理 ,格式为 GET:
  44. String methodValue = authorizationContext.getExchange().getRequest().getMethodValue() + StrPool.COLON;
  45. // 获取所有路径的权限列表
  46. Map<Object, Object> entries = redisTemplate.opsForHash().entries(CacheConstants.OAUTH_PERMS);
  47. List<String> authorities = new ArrayList<>();
  48. AtomicBoolean authFlag = new AtomicBoolean(false);
  49. entries.forEach((k, v) -> {
  50. // 根据请求uri路径,获取到匹配的缓存权限数据
  51. if (k.equals(methodValue + uri)
  52. || matcher.match(methodValue + uri + CacheConstants.FUZZY_PATH, k.toString())) {
  53. if (CacheConstants.ANONYMOUS.equals(v.toString())) {
  54. // 权限为**,表示允许匿名访问
  55. authFlag.set(true);
  56. } else {
  57. // 收集当前路径所需的权限列表
  58. authorities.addAll(Arrays.asList((v.toString()).split(StrPool.COMMA)));
  59. }
  60. }
  61. });
  62. // Collection<? extends GrantedAuthority> authorities1 = mono.block().getAuthorities();
  63. List<String> finalAuthorities = authorities;
  64. return mono
  65. //判断是否认证成功
  66. .filter(Authentication::isAuthenticated)
  67. //获取认证后的全部权限列表
  68. .flatMapIterable(Authentication::getAuthorities)
  69. .map(GrantedAuthority::getAuthority)
  70. //如果包含在url要求的权限内,则返回true
  71. .any(auth -> authFlag.get() || finalAuthorities.contains(auth))
  72. .map(AuthorizationDecision::new)
  73. .defaultIfEmpty(new AuthorizationDecision(false))
  74. ;
  75. }
  76. }

2.3.security安全配置

  1. import com.eden4cloud.gateway.component.JwtAuthorizationManager;
  2. import com.eden4cloud.gateway.handler.RequestAccessDeniedHandler;
  3. import com.eden4cloud.gateway.handler.RequestAuthenticationEntrypoint;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.security.authentication.ReactiveAuthenticationManager;
  8. import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
  9. import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
  10. import org.springframework.security.config.web.server.ServerHttpSecurity;
  11. import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
  12. import org.springframework.security.web.server.SecurityWebFilterChain;
  13. import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
  14. import org.springframework.web.cors.reactive.CorsWebFilter;
  15. /**
  16. * @Since: 2023/4/2
  17. * @Author: Yan
  18. * @Description security安全配置
  19. */
  20. @Configuration
  21. @EnableWebFluxSecurity
  22. public class EdenGatewayWebSecurityConfig {
  23. @Autowired
  24. private JwtAuthorizationManager jwtAuthorizationManager;
  25. @Autowired
  26. private ReactiveAuthenticationManager authenticationManager;
  27. @Autowired
  28. private RequestAuthenticationEntrypoint requestAuthenticationEntrypoint;
  29. @Autowired
  30. private RequestAccessDeniedHandler requestAccessDeniedHandler;
  31. @Autowired
  32. private CorsWebFilter corsWebFilter;
  33. // @Autowired
  34. // private GlobalAuthenticationFilter authenticationFilter;
  35. @Bean
  36. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  37. AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(authenticationManager);
  38. authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
  39. http
  40. .csrf().disable()
  41. .authorizeExchange()
  42. //对oauth的端点进行放行
  43. .pathMatchers("/eden-oauth/oauth/**").permitAll()
  44. //其他请求必须鉴权,使用鉴权管理器
  45. .anyExchange().access(jwtAuthorizationManager)
  46. .and()
  47. //鉴权异常处理
  48. .exceptionHandling()
  49. .authenticationEntryPoint(requestAuthenticationEntrypoint)
  50. .accessDeniedHandler(requestAccessDeniedHandler)
  51. .and()
  52. //跨域过滤器
  53. .addFilterAt(corsWebFilter, SecurityWebFiltersOrder.CORS)
  54. //token认证过滤器
  55. .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
  56. // .addFilterAfter(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
  57. ;
  58. return http.build();
  59. }
  60. }

2.4.将网关作为资源服务

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  4. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  5. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
  6. import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
  7. import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
  8. import org.springframework.security.oauth2.provider.token.TokenStore;
  9. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
  10. /**
  11. * @Author: Yan
  12. * @Since: 2023/2/4
  13. * @Description: 资源服务器解析鉴权配置类
  14. */
  15. @Configuration
  16. public class EdenGatewayResourceServerConfig extends ResourceServerConfigurerAdapter {
  17. @Autowired
  18. private TokenStore tokenStore;
  19. //公钥
  20. private static final String RESOURCE_ID = "eden-gateway";
  21. /**
  22. * Http安全配置,对每个到达系统的http请求链接进行校验
  23. *
  24. * @param http
  25. * @throws Exception
  26. */
  27. @Override
  28. public void configure(HttpSecurity http) throws Exception {
  29. http
  30. .authorizeRequests()
  31. .antMatchers("/**").permitAll();
  32. }
  33. /**
  34. * 资源服务的安全配置
  35. *
  36. * @param resources
  37. * @throws Exception
  38. */
  39. @Override
  40. public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
  41. resources
  42. .resourceId(RESOURCE_ID)
  43. .tokenStore(tokenStore)
  44. .stateless(true);
  45. }
  46. }

2.5.其他配置

  1. import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  2. import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.cors.CorsConfiguration;
  6. import org.springframework.web.cors.reactive.CorsWebFilter;
  7. import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
  8. import org.springframework.web.util.pattern.PathPatternParser;
  9. /**
  10. * @CreateTime: 2023-01-2023/1/11 15:09
  11. * @Author: Yan
  12. * @Description 注册网关过滤器示例
  13. */
  14. @Configuration
  15. @AutoConfigureAfter(GatewayAutoConfiguration.class)
  16. public class GatewayRoutesConfiguration {
  17. /**
  18. * 跨域配置
  19. *
  20. * @return
  21. */
  22. @Bean
  23. public CorsWebFilter corsWebFilter() {
  24. CorsConfiguration config = new CorsConfiguration();
  25. config.addAllowedMethod("*");
  26. config.addAllowedOrigin("*");
  27. config.addAllowedHeader("*");
  28. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
  29. source.registerCorsConfiguration("/**", config);
  30. return new CorsWebFilter(source);
  31. }
  32. // @Bean(name = "ipKeyResolver")
  33. // public KeyResolver userIpKeyResolver() {
  34. // return new IpKeyResolver();
  35. // }
  36. //
  37. // @Bean
  38. // public RouteLocator routeLocator(RouteLocatorBuilder builder) {
  39. // StripPrefixGatewayFilterFactory filterFactory = new StripPrefixGatewayFilterFactory();
  40. // StripPrefixGatewayFilterFactory.Config partsConfig = filterFactory.newConfig();
  41. // partsConfig.setParts(1);
  42. //
  43. // MyGatewayFilterFactory factory = new MyGatewayFilterFactory();
  44. // MyGatewayFilterFactory.PathsConfig pathsConfig = factory.newConfig();
  45. // pathsConfig.setPaths(Arrays.asList("/AAA", "/BBB"));
  46. //
  47. // return builder.routes()
  48. // .route(r -> r.path("/life/**")
  49. // .uri("lb://eden-life")
  50. // .filters(factory.apply(pathsConfig), filterFactory.apply(partsConfig))
  51. // .id("eden-life"))
  52. // .build();
  53. // }
  54. }

2.6.自定义鉴权过滤器工厂

  1. import cn.hutool.core.util.StrUtil;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.eden4cloud.common.security.exception.InvalidTokenException;
  4. import com.eden4cloud.common.security.utils.JwtUtils;
  5. import com.eden4cloud.common.util.sign.Base64;
  6. import lombok.Data;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.cloud.gateway.filter.GatewayFilter;
  9. import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
  10. import org.springframework.http.server.reactive.ServerHttpRequest;
  11. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  12. import org.springframework.security.oauth2.provider.token.TokenStore;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.web.server.ServerWebExchange;
  15. import reactor.core.publisher.Mono;
  16. import java.nio.charset.StandardCharsets;
  17. import java.util.Collections;
  18. import java.util.List;
  19. import java.util.Map;
  20. /**
  21. * @CreateTime: 2023-01-2023/1/11 9:34
  22. * @Author: Yan
  23. * @Description 自定义鉴权过滤器工厂:设置访问白名单;重新封装鉴权认证通过的请求头;
  24. */
  25. //1. 编写实现类继承AbstractGatewayFilterFactory抽象类
  26. @Component
  27. public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.IgnoreUrlsConfig> {//4. 指定泛型,静态的内部实体类
  28. @Autowired
  29. private TokenStore tokenStore;
  30. //5. 重写无参构造方法,指定内部实体类接收参数
  31. public AuthGatewayFilterFactory() {
  32. super(IgnoreUrlsConfig.class);
  33. }
  34. @Override
  35. public GatewayFilter apply(IgnoreUrlsConfig config) {
  36. return (exchange, chain) -> {
  37. ServerHttpRequest request = exchange.getRequest();
  38. //直接放行部分请求路径,如登录、退出等 需要排除的路径弄成可yaml配置的
  39. boolean flag = config.ignoreUrls.contains(request.getURI().getPath())
  40. || config.ignoreUrls.stream().filter(i -> i.endsWith("/**"))
  41. .anyMatch(i -> exchange.getRequest().getURI().getPath().startsWith(i.replace("**", "")));
  42. if (flag) {
  43. return chain.filter(exchange);
  44. }
  45. //重新封装新的请求中数据
  46. ServerWebExchange webExchange = rebuildRequestHeaders(exchange, request);
  47. if (webExchange == null) {
  48. return Mono.error(new InvalidTokenException());
  49. }
  50. return chain.filter(webExchange);
  51. };
  52. }
  53. /**
  54. * 重新封装新的请求数据
  55. *
  56. * @param exchange
  57. * @param request
  58. * @return
  59. */
  60. private ServerWebExchange rebuildRequestHeaders(ServerWebExchange exchange, ServerHttpRequest request) {
  61. //获取请求头中的令牌
  62. String token = JwtUtils.getToken(request);
  63. if (StrUtil.isBlank(token)) {
  64. return null;
  65. }
  66. OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
  67. Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
  68. List<String> authorities = (List<String>) additionalInformation.get("authorities");
  69. //获取用户名
  70. String username = additionalInformation.get("user_name").toString();
  71. //获取用户权限
  72. JSONObject jsonObject = new JSONObject();
  73. jsonObject.put("username", username);
  74. jsonObject.put("authorities", authorities);
  75. //将解析后的token加密后重新放入请求头,方便后续微服务解析获取用户信息
  76. String base64 = Base64.encode(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
  77. request = exchange.getRequest().mutate().header("token", base64).build();
  78. exchange.mutate().request(request);
  79. return exchange;
  80. }
  81. /**
  82. * 6. 重写shortcutFieldOrder()指定接收参数的字段顺序
  83. * Returns hints about the number of args and the order for shortcut parsing.
  84. *
  85. * @return the list of hints
  86. */
  87. @Override
  88. public List<String> shortcutFieldOrder() {
  89. return Collections.singletonList("ignoreUrls");
  90. }
  91. /**
  92. * 7. 重写shortcutType()指定接收参数的字段类型
  93. */
  94. @Override
  95. public ShortcutType shortcutType() {
  96. return ShortcutType.GATHER_LIST;
  97. }
  98. /**
  99. * 3. 定义匿名内部实体类,定义接收参数的字段
  100. */
  101. @Data
  102. public static class IgnoreUrlsConfig {
  103. //传递多个参数
  104. private List<String> ignoreUrls;
  105. }
  106. }

2.7.网关路由规则

  1. #路由配置
  2. spring:
  3. cloud:
  4. gateway:
  5. routes:
  6. - id: eden-life
  7. uri: lb://eden-life
  8. predicates:
  9. - Path=/eden-life/**
  10. filters:
  11. - StripPrefix=1
  12. - name: Auth
  13. args:
  14. ignoreUrls:
  15. - /auth-server/login
  16. - /oauth/**
  17. - /life/**

2.6和2.7主要是展示配置了动态的请求白名单功能。基于nacos的配置中心功能,可以实现动态刷新,白名单设置实时生效。

2.8.异常处理

  1. import cn.hutool.json.JSONUtil;
  2. import com.eden4cloud.common.core.entity.R;
  3. import com.eden4cloud.common.security.exception.SecurityExceptionEnum;
  4. import org.apache.http.HttpHeaders;
  5. import org.springframework.core.io.buffer.DataBuffer;
  6. import org.springframework.http.HttpStatus;
  7. import org.springframework.http.MediaType;
  8. import org.springframework.http.server.reactive.ServerHttpResponse;
  9. import org.springframework.security.access.AccessDeniedException;
  10. import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13. import reactor.core.publisher.Mono;
  14. import java.nio.charset.StandardCharsets;
  15. /**
  16. * @Since: 2023/4/17
  17. * @Author: Yan
  18. * @Description TODO
  19. */
  20. @Component
  21. public class RequestAccessDeniedHandler implements ServerAccessDeniedHandler {
  22. @Override
  23. public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
  24. ServerHttpResponse response = exchange.getResponse();
  25. response.setStatusCode(HttpStatus.OK);
  26. response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
  27. DataBuffer buffer = response.bufferFactory()
  28. .wrap(JSONUtil.toJsonStr(R.error(SecurityExceptionEnum.NO_PERMISSION.getMsg())).getBytes(StandardCharsets.UTF_8));
  29. return response.writeWith(Mono.just(buffer));
  30. }
  31. }
  1. import cn.hutool.json.JSONUtil;
  2. import com.eden4cloud.common.core.entity.R;
  3. import com.eden4cloud.common.security.exception.SecurityExceptionEnum;
  4. import org.apache.http.HttpHeaders;
  5. import org.springframework.core.io.buffer.DataBuffer;
  6. import org.springframework.http.HttpStatus;
  7. import org.springframework.http.MediaType;
  8. import org.springframework.http.server.reactive.ServerHttpResponse;
  9. import org.springframework.security.core.AuthenticationException;
  10. import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13. import reactor.core.publisher.Mono;
  14. import java.nio.charset.StandardCharsets;
  15. /**
  16. * @Since: 2023/4/17
  17. * @Author: Yan
  18. * @Description TODO
  19. */
  20. @Component
  21. public class RequestAuthenticationEntrypoint implements ServerAuthenticationEntryPoint {
  22. @Override
  23. public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
  24. ServerHttpResponse response = exchange.getResponse();
  25. response.setStatusCode(HttpStatus.OK);
  26. response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
  27. DataBuffer buffer = response.bufferFactory()
  28. .wrap(JSONUtil.toJsonStr(R.error(SecurityExceptionEnum.INVALID_TOKEN.getMsg())).getBytes(StandardCharsets.UTF_8));
  29. return response.writeWith(Mono.just(buffer));
  30. }
  31. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/84499
推荐阅读
相关标签
  

闽ICP备14008679号