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

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

1.网关鉴权的流程

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

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

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

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

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

1.1.初始化方法

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

* 不支持@PathVariable格式的URI

*/

public static List> oauthUrls = new ArrayList<>();

Map uriAuthMap = new HashMap<>();

/**

* Url参数需要解密的配置

* 比如:/user/list?name=加密内容

* 格式:Key API路径 Value 需要解密的字段

* 示列:/user/list [name,age]

*/

public static Map> 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 beanMap = ctx.getBeansWithAnnotation(Controller.class);

initData(beanMap);

if (CollectionUtil.isNotEmpty(uriAuthMap)) {

redisTemplate.boundHashOps(CacheConstants.OAUTH_URLS).putAll(uriAuthMap);

}

}

/**

* 初始化,获取所有接口的加解密配置状态并保存

*

* @param beanMap

*/

private void initData(Map 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);

}

}

});

}

}

}

1.2.对应的工具类

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/**。

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

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

2.网关整合Security Oauth2

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

2.1.认证管理器

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 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);

}

}

2.2..鉴权管理器

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 {

@Autowired

private RedisTemplate redisTemplate;

private AntPathMatcher matcher = new AntPathMatcher();

/**

* *****当前匹配方法要求一个API接口方法上必须要有路径*****

*

* @param mono

* @param authorizationContext

* @return

*/

@Override

public Mono check(Mono 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 entries = redisTemplate.opsForHash().entries(CacheConstants.OAUTH_PERMS);

List 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 authorities1 = mono.block().getAuthorities();

List 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))

;

}

}

2.3.security安全配置

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);

}

}

2.5.其他配置

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();

// }

}

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

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 {//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 additionalInformation = oAuth2AccessToken.getAdditionalInformation();

List authorities = (List) 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 shortcutFieldOrder() {

return Collections.singletonList("ignoreUrls");

}

/**

* 7. 重写shortcutType()指定接收参数的字段类型

*/

@Override

public ShortcutType shortcutType() {

return ShortcutType.GATHER_LIST;

}

/**

* 3. 定义匿名内部实体类,定义接收参数的字段

*/

@Data

public static class IgnoreUrlsConfig {

//传递多个参数

private List ignoreUrls;

}

}

2.7.网关路由规则

#路由配置

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 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 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));

}

}

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: