【问题标题】:Spring Webflux OAuth 2 resource serverSpring Webflux OAuth 2 资源服务器
【发布时间】:2019-04-29 18:30:39
【问题描述】:

我有一个基于 Spring Boot 1.5 (Spring Security v4) 的 Spring OAuth 2 服务器,它生成自定义令牌和一些与此授权服务器通信的资源服务器,通过 RemoteTokenServices 的配置使用 /oauth/check_token 端点。 所有与在授权服务器端存储/检索令牌相关的逻辑都使用JdbcTokenStore 完成。

我正在构建一个新的 Spring Boot 2 应用程序,该应用程序使用 Spring webflux 模块构建,并尝试使用 Spring Security 5.1.1 与现有授权服务器实现client_credentials 流。 我发现在 5.1.0.RC1 (https://spring.io/blog/2018/08/21/spring-security-5-1-0-rc1-released#oauth2-resource-servers) 中添加了对资源服务器的支持,并在 5.1.0.RC2 (https://spring.io/blog/2018/09/10/spring-security-5-1-0-rc2-released#oauth2-resource-server) 中进行了更新,但看起来只能使用 JWT 支持来配置它。

我可能在这里搞乱了一些概念,但我正在寻找更多信息以及将所有这些组件配置在一起的方法。

【问题讨论】:

    标签: spring spring-boot spring-security jwt spring-security-oauth2


    【解决方案1】:

    我的情况和你一样。我用下一个方法解决这个问题,也许它可以帮助你:

    spring-boot-starter-parent.version:2.1.1

    spring-cloud-dependencies.version:Greenwich.R1

    安全配置

    @EnableWebFluxSecurity
    public class SecurityConfig {
    
        @Autowired
        private ReactiveAuthenticationManager manager; //custom implementation
    
        @Bean
        SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            return http
                    .authorizeExchange()
                    .pathMatchers("/role").hasRole("ADMIN")
                    .pathMatchers("/test").access(new HasScope("server")) //custom implementation
                    .anyExchange().authenticated()
                    .and()
                    .httpBasic().disable()
                    .oauth2ResourceServer()
                        .jwt()
                        .authenticationManager(manager)
                    .and().and()
                    .build();
        }
    }
    

    ReactiveAuthorizationManager (HasScope) 实现: 允许在身份验证对象中搜索范围的助手

    public class HasScope implements ReactiveAuthorizationManager<AuthorizationContext> {
    
        public HasScope(String...scopes) {
            this.scopes = Arrays.asList(scopes);
        }
    
        private final Collection<String> scopes;
    
        @Override
        public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
            return authentication
                    .flatMap(it -> {
                        OAuth2Authentication auth = (OAuth2Authentication) it;
                        Set<String> requestScopes = auth.getOAuth2Request().getScope();
                        boolean allow = requestScopes.containsAll(scopes);
                        return Mono.just(new AuthorizationDecision(allow));
                    });
        }
    }
    

    ReactiveAuthenticationManager 实现:

    这是配置中创建OAuth2Authentication的主要组件。错误access_token的响应有问题,它只返回状态码而没有正文响应。

    @Component
    public class ReactiveAuthenticationManagerImpl implements ReactiveAuthenticationManager {
    
        private final ResourceServerProperties sso;
        private final WebClient.Builder webClient;
        private final ObjectMapper objectMapper;
        private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
    
        public ReactiveAuthenticationManagerImpl(ResourceServerProperties sso,
                @Qualifier("loadBalancedWebClient") WebClient.Builder webClient, ObjectMapper objectMapper) {
            this.sso = sso;
            this.webClient = webClient;
            this.objectMapper = objectMapper;
        }
    
        @Override
        public Mono<Authentication> authenticate(Authentication authentication) {
            return Mono.just(authentication)
                    .cast(BearerTokenAuthenticationToken.class)
                    .flatMap(it -> getMap(it.getToken()))
                    .flatMap(result -> Mono.just(extractAuthentication(result)));
        }
    
        private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
            Object principal = getPrincipal(map);
            OAuth2Request request = getRequest(map);
            List<GrantedAuthority> authorities = authoritiesExtractor.extractAuthorities(map);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
            token.setDetails(map);
            return new OAuth2Authentication(request, token);
        }
    
        private Object getPrincipal(Map<String, Object> map) {
            if (map.containsKey("principal")) {
                try {
                    //that is the case for user authentication
                    return objectMapper.convertValue(map.get("principal"), UserPrincipal.class);
                } catch (IllegalArgumentException ex) {
                    //that is the case for client authentication
                    return objectMapper.convertValue(map.get("principal"), String.class);
                }
            }
            return null;
        }
    
        @SuppressWarnings({"unchecked"})
        private OAuth2Request getRequest(Map<String, Object> map) {
            Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
    
            String clientId = (String) request.get("clientId");
            Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
                    (Collection<String>) request.get("scope") : Collections.emptySet());
    
            return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
                    null, null, null, null);
        }
    
        private Mono<Map<String, Object>> getMap(String accessToken) {
            String uri = sso.getUserInfoUri();
            return webClient.build().get()
                    .uri(uri)
                    .accept(MediaType.APPLICATION_JSON)
                    .header("Authorization", "Bearer " + accessToken)
                    .exchange()
                    .flatMap(it -> it.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}))
                    .onErrorMap(InvalidTokenException.class, mapper -> new InvalidTokenException("Invalid token: " + accessToken));
        }
    

    【讨论】:

    • 我认为这个实现是不完整的。资源服务器还应使用所需的签名密钥验证令牌。还有令牌到期等。
    • 此实现的任何 gitlab url ?
    猜你喜欢
    • 2018-07-26
    • 2019-04-05
    • 2013-04-20
    • 1970-01-01
    • 2020-03-23
    • 2021-05-19
    • 2015-05-14
    • 2018-05-09
    • 2018-03-02
    相关资源
    最近更新 更多