相关文章推荐
犯傻的手链  ·  Spark SQL query with ...·  1 年前    · 
低调的炒面  ·  Oops!!! - 简书·  2 年前    · 

一般的资源服务器鉴权的实现都比较简单,在JWT作为access_token的情况下,只需要配置JwtTokenStore即可使用@PreAuthorize和@PostAuthorize等注解进行基于角色权限的鉴权。

然而在Spring Cloud Gateway中,使用得是基于Web Flux的鉴权,在配置和实现上与之前有所区别。且我们需要在网关实现基于URL的鉴权,因此需要进行自定义实现。

网关对请求的主要处理流程包括ReactiveAuthenticationManager->ReactiveAuthorizationManager->Gateway Filters。其中ReactiveAuthenticationManager用于封装JWT为OAuth2Authentication并判断Token的有效性;ReactiveAuthorizationManager用于基于URL的鉴权;Gateway Filters是网关的过滤流程。

ReactiveAuthorizationManager实现

实现该接口主要是为了鉴权,我们在这里使用URL进行鉴权,通过获取用户Authentication里的Authorities,判断是否允许访问该路径。

AccessManager实现

这里只是简单的判断是否是跨域的检验请求,是的话直接放行。具体的鉴权实现在UrlAuthorityChecker里。

* 描述:鉴权管理器 * @author xhsf * @create 2020/11/26 11:26 @Component public class AccessManager implements ReactiveAuthorizationManager <AuthorizationContext> { private final UrlAuthorityChecker urlAuthorityChecker; public AccessManager (UrlAuthorityChecker urlAuthorityChecker) { this .urlAuthorityChecker = urlAuthorityChecker; * 实现权限验证判断 @Override public Mono<AuthorizationDecision> check (Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) { ServerHttpRequest request = authorizationContext.getExchange().getRequest(); // 对应跨域的预检请求直接放行 if (request.getMethod() == HttpMethod.OPTIONS) { return Mono.just( new AuthorizationDecision ( true )); // 进行鉴权 String url = request.getURI().getPath(); return authenticationMono .map(auth -> new AuthorizationDecision (urlAuthorityChecker.check(auth.getAuthorities(), url))) .defaultIfEmpty( new AuthorizationDecision ( false ));

UrlAuthorityChecker实现

这里有两个重点,一个是权限名与授权路径的对应关系,比如get_user权限可以访问 /users/* 路径。另外一个是白名单,比如 /oauth/** 为白名单里面的路径。但是我们并不想直接在代码里面把这两个配置写死,因为有可能会需要修改权限名与授权路径的对应关系和白名单列表,因此我们添加定时任务,定时的更新这两个列表,同时把白名单作为一个服务,可以通过相应的接口(或者图形界面)进行修改。

* 描述:基于Url的权限检查器 * @author xhsf * @create 2020/11/26 15:10 @Component @EnableScheduling public class UrlAuthorityChecker { @Reference private PermissionService permissionService; @Reference private WhiteListService whiteListService; * 刷新permissionNameAuthorizationUrlMap的间隔 private static final int REFRESH_DELAY = 60000 ; * 权限名和授权URL的对应关系 private Map<String, String> permissionNameAuthorizationUrlMap; * 路径白名单 private List<String> whiteList; private final AntPathMatcher antPathMatcher; public UrlAuthorityChecker (AntPathMatcher antPathMatcher) { this .antPathMatcher = antPathMatcher; * 通过用户拥有的权限进行鉴权 * @param authorities 权限列表 * @param url 请求的Url * @return 是否通过鉴权 public boolean check (Collection<? extends GrantedAuthority> authorities, String url) { // 判断该路径是否在白名单里,如果是直接放行 for (String permittedUrl : whiteList) { if (antPathMatcher.match(permittedUrl, url)) { return true ; // 判断用户的权限里有没有满足获取该路径资源的 for (GrantedAuthority authority : authorities) { String authorizationUrl = permissionNameAuthorizationUrlMap.get(authority.getAuthority()); if (authorizationUrl != null && antPathMatcher.match(authorizationUrl, url)) { return true ; return false ; * 创建newPermissionNameAuthorizationUrlMap并替换旧的 * 这里添加定时任务,会每REFRESH_DELAY毫秒刷新一次permissionNameAuthorizationUrlMap @Scheduled(initialDelay = 0, fixedDelay = REFRESH_DELAY) private void updatePermissionNameAuthorizationUrlMap () { List<PermissionDTO> permissionDTOList = permissionService.getAllPermission().getData(); Map<String, String> newPermissionNameAuthorizationUrlMap = new ConcurrentHashMap <>(); for (PermissionDTO permissionDTO : permissionDTOList) { newPermissionNameAuthorizationUrlMap.put( ResourceServerConstant.AUTHORITY_PREFIX + permissionDTO.getPermissionName(), permissionDTO.getAuthorizationUrl()); permissionNameAuthorizationUrlMap = newPermissionNameAuthorizationUrlMap; * 创建newWhiteList并替换旧的 * 这里添加定时任务,会每REFRESH_DELAY毫秒刷新一次whiteList @Scheduled(initialDelay = 0, fixedDelay = REFRESH_DELAY) private void updateWhiteList () { Result<List<String>> getWhiteListResult = whiteListService.getWhiteList(); whiteList = getWhiteListResult.getData();

这里简单的添加了CorsConfigurationSource,该Bean会被Spring Boot自动识别,并构造一个CorsWebFilter进行跨域过滤,只要在ServerHttpSecurity里配置了cors()。

* 描述:跨域配置 * @author xhsf * @create 2020/11/28 0:34 @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource () { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); CorsConfiguration corsConfiguration = new CorsConfiguration (); corsConfiguration.addAllowedOrigin( "*" ); corsConfiguration.addAllowedHeader( "*" ); corsConfiguration.addAllowedMethod( "*" ); corsConfiguration.setAllowCredentials( true ); source.registerCorsConfiguration( "/**" , corsConfiguration); return source;

ResourceServerConfig配置

这里把网关作为资源服务器,进行鉴权操作。该类是用来配置各种过滤器,以实现具体的鉴权逻辑。具体如下代码注释。

* 描述:资源服务器配置 * @author xhsf * @create 2020/11/27 15:19 @EnableWebFluxSecurity public class ResourceServerConfig { private final AccessManager authorizationManager; private final CustomServerAccessDeniedHandler customServerAccessDeniedHandler; private final CustomServerAuthenticationEntryPoint customServerAuthenticationEntryPoint; public ResourceServerConfig (AccessManager authorizationManager, CustomServerAccessDeniedHandler customServerAccessDeniedHandler, CustomServerAuthenticationEntryPoint customServerAuthenticationEntryPoint) { this .authorizationManager = authorizationManager; this .customServerAccessDeniedHandler = customServerAccessDeniedHandler; this .customServerAuthenticationEntryPoint = customServerAuthenticationEntryPoint; * Web Security过滤器链配置 * @param http ServerHttpSecurity * @return SecurityWebFilterChain @Bean public SecurityWebFilterChain securityWebFilterChain (ServerHttpSecurity http) { // JWT处理 http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); // 自定义处理JWT请求头过期或签名错误的结果 http.oauth2ResourceServer().authenticationEntryPoint(customServerAuthenticationEntryPoint); // 添加鉴权过滤器 http.authorizeExchange() .anyExchange().access(authorizationManager) .and() // 鉴权异常处理 .exceptionHandling() .accessDeniedHandler(customServerAccessDeniedHandler) // 处理未授权 .authenticationEntryPoint(customServerAuthenticationEntryPoint) //处理未认证 .and() .csrf().disable() .cors(); return http.build(); * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication * 需要把jwt的Claim中的authorities加入 * 重新定义ReactiveAuthenticationManager权限管理器,添加默认转换器JwtGrantedAuthoritiesConverter * @return ReactiveJwtAuthenticationConverterAdapter @Bean public Converter<Jwt, ? extends Mono <? extends AbstractAuthenticationToken >> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter (); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(ResourceServerConstant.AUTHORITY_PREFIX); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(ResourceServerConstant.AUTHORITIES_CLAIM_NAME); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter (); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter (jwtAuthenticationConverter);

CustomServerAccessDeniedHandler

捕获AccessDeniedException并返回自定义响应结果。

* 描述:无权访问自定义响应 * @author xhsf * @create 2020/11/26 19:32 @Component public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle (ServerWebExchange exchange, AccessDeniedException e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.FORBIDDEN); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getHeaders().set( "Access-Control-Allow-Origin" , "*" ); response.getHeaders().set( "Cache-Control" , "no-cache" ); String body = JSON.toJSONString(Result.fail(ErrorCode.FORBIDDEN, e.getMessage())); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer));

CustomServerAuthenticationEntryPoint

捕获AuthenticationException并返回自定义响应结果。

* 描述:无效token/token过期 自定义响应 * @author xhsf * @create 2020/11/26 19:34 @Component public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @Override public Mono<Void> commence (ServerWebExchange exchange, AuthenticationException e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getHeaders().set( "Access-Control-Allow-Origin" , "*" ); response.getHeaders().set( "Cache-Control" , "no-cache" ); String body = JSON.toJSONString(Result.fail(ErrorCode.UNAUTHORIZED, e.getMessage())); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)); 复制代码
分类:
后端
标签: