摘要
依赖
springboot官方提供了支持1 2 3 4 5 6 7 implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
ReactiveSecurityConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package com.example.oauth2resourceserverwebfluxdemo.config;import com.example.oauth2resourceserverwebfluxdemo.security.CustomReactiveAuthorizationManager;import com.example.oauth2resourceserverwebfluxdemo.security.CustomServerAccessDeniedHandler;import com.example.oauth2resourceserverwebfluxdemo.security.CustomServerAuthenticationEntryPoint;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;import org.springframework.security.config.web.server.ServerHttpSecurity;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;import org.springframework.security.web.server.SecurityWebFilterChain;import reactor.core.publisher.Mono;import java.util.Collection;import java.util.stream.Collectors;@Configuration @EnableReactiveMethodSecurity public class ReactiveSecurityConfig { @Autowired private CustomServerAccessDeniedHandler customServerAccessDeniedHandler; @Autowired private CustomServerAuthenticationEntryPoint customServerAuthenticationEntryPoint; @Autowired private CustomReactiveAuthorizationManager customReactiveAuthorizationManager; @Bean SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) { return http.authorizeExchange() .pathMatchers("/res/**" , "/userInfo/**" ).authenticated() .pathMatchers("/user/**" ).hasAnyRole("admin" , "user" ) .pathMatchers("/swagger-ui/**" , "/v3/api-docs**" ).permitAll() .anyExchange().access(customReactiveAuthorizationManager) .and() .csrf().disable() .httpBasic().disable() .formLogin().disable() .cors() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwt -> { Collection<SimpleGrantedAuthority> authorities = ((Collection<String>) jwt.getClaims() .get("authorities" )).stream() .map(SimpleGrantedAuthority::new ) .collect(Collectors.toSet()); Collection<SimpleGrantedAuthority> scopes = ((Collection<String>) jwt.getClaims() .get("scope" )).stream().map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .collect(Collectors.toSet()); authorities.addAll(scopes); return Mono.just(new JwtAuthenticationToken(jwt, authorities)); }) .and() .accessDeniedHandler(customServerAccessDeniedHandler) .authenticationEntryPoint(customServerAuthenticationEntryPoint) .and().build(); } }
CustomReactiveAuthorizationManager 基于RBAC权限认证管理模型的认证方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.example.oauth2resourceserverwebfluxdemo.security;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.authorization.AuthorizationDecision;import org.springframework.security.authorization.ReactiveAuthorizationManager;import org.springframework.security.core.Authentication;import org.springframework.security.oauth2.jwt.Jwt;import org.springframework.security.web.server.authorization.AuthorizationContext;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import org.springframework.util.StringUtils;import reactor.core.publisher.Mono;import java.util.HashSet;import java.util.Set;@Component public class CustomReactiveAuthorizationManager implements ReactiveAuthorizationManager <AuthorizationContext > { @Override public Mono<AuthorizationDecision> check (Mono<Authentication> authentication, AuthorizationContext object) { return authentication.map(auth -> { ServerHttpRequest request = object.getExchange().getRequest(); Object principal = auth.getPrincipal(); String username; if (principal instanceof Jwt) { username = ((Jwt) principal).getClaimAsString("user_name" ); } else { username = principal.toString(); } boolean hasPerssion = false ; if (StringUtils.hasText(username) && !"anonymousUser" .equals(username)) { Set<String> uris = new HashSet<>(); for (String uri : uris) { if (new AntPathMatcher().match(uri, request.getURI().toString())) { hasPerssion = true ; break ; } } } hasPerssion = true ; return new AuthorizationDecision(hasPerssion); }); } }
CustomServerAccessDeniedHandler 没有权限时的处理方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.example.oauth2resourceserverwebfluxdemo.security;import com.example.oauth2resourceserverwebfluxdemo.exception.AjaxResponse;import com.example.oauth2resourceserverwebfluxdemo.exception.CustomException;import com.example.oauth2resourceserverwebfluxdemo.exception.CustomExceptionType;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.SneakyThrows;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;@Component public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler { @SneakyThrows @Override public Mono<Void> handle (ServerWebExchange serverWebExchange, AccessDeniedException e) { return setErrorResponse(serverWebExchange.getResponse(),e.getMessage()); } protected Mono<Void> setErrorResponse (ServerHttpResponse response, String message) throws JsonProcessingException { response.getHeaders().setContentType(MediaType.APPLICATION_JSON); ObjectMapper objectMapper = new ObjectMapper(); AjaxResponse ajaxResponse = AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, message)); return response.writeWith(Mono.just(response.bufferFactory().wrap(objectMapper.writeValueAsBytes(ajaxResponse)))); } }
CustomServerAuthenticationEntryPoint token格式错误或过期时的处理方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.example.oauth2resourceserverwebfluxdemo.security;import com.example.oauth2resourceserverwebfluxdemo.exception.AjaxResponse;import com.example.oauth2resourceserverwebfluxdemo.exception.CustomException;import com.example.oauth2resourceserverwebfluxdemo.exception.CustomExceptionType;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.SneakyThrows;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;@Component public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @SneakyThrows @Override public Mono<Void> commence (ServerWebExchange serverWebExchange, AuthenticationException e) { return setErrorResponse(serverWebExchange.getResponse(), e.getMessage()); } protected Mono<Void> setErrorResponse (ServerHttpResponse response, String message) throws JsonProcessingException { response.getHeaders().setContentType(MediaType.APPLICATION_JSON); ObjectMapper objectMapper = new ObjectMapper(); AjaxResponse ajaxResponse = AjaxResponse.error(new CustomException(CustomExceptionType.USER_INPUT_ERROR, message)); return response.writeWith(Mono.just(response.bufferFactory().wrap(objectMapper.writeValueAsBytes(ajaxResponse)))); } }
WebFluxConfig 配置跨域1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.oauth2resourceserverwebfluxdemo.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.reactive.config.CorsRegistry;import org.springframework.web.reactive.config.WebFluxConfigurer;@Configuration public class WebFluxConfig implements WebFluxConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/**" ) .allowedMethods("GET" ,"POST" , "PUT" , "DELETE" ) .allowedHeaders("*" ) .allowedOriginPatterns("*" ) .allowCredentials(true ); } }
application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server: port: 8083 spring: application: name: oauth2-resource-server-webflux security: oauth2: resourceserver: jwt: jwk-set-uri: http://localhost:8080/.well-known/jwks.json
资源接口示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.example.oauth2resourceserverwebfluxdemo.controller;import com.example.oauth2resourceserverwebfluxdemo.exception.AjaxResponse;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.core.Authentication;import org.springframework.security.oauth2.jwt.Jwt;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Mono;import java.security.Principal;import java.util.HashMap;import java.util.Map;@RestController public class UserController { @PreAuthorize("hasRole('admin') or hasRole('user')") @RequestMapping(value = "/user") public Mono<AjaxResponse> user (Principal principal) { return Mono.just(AjaxResponse.success(principal)); } @PreAuthorize("hasAuthority('SCOPE_any')") @RequestMapping(value = "/user2") public Mono<AjaxResponse> user2 (Principal principal) { return Mono.just(AjaxResponse.success(principal)); } @RequestMapping("/userInfo") public Mono<Map<String, Object>> userInfo(Authentication authentication){ Map<String,Object> map = new HashMap<>(); Object principal = authentication.getPrincipal(); if (principal instanceof Jwt){ map.put("username" , ((Jwt) principal).getClaim("user_name" )); map.putAll(((Jwt) principal).getClaims()); } return Mono.just(map); } }
---------------- The End ----------------
分享到: