Сервер ресурсов Spring Webflux OAuth 2 - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть сервер Spring OAuth 2 на основе Spring Boot 1.5 (Spring Security v4), который генерирует настроенные токены и несколько серверов ресурсов, которые взаимодействуют с этим сервером авторизации, используя конечную точку /oauth/check_token с конфигурацией RemoteTokenServices.Вся логика, связанная с хранением / извлечением токенов на стороне сервера авторизации, выполняется с помощью JdbcTokenStore.

. Я создаю новое приложение Spring Boot 2, которое собирается с модулем Spring webflux, и пытаюсь реализовать поток client_credentials.с существующим сервером авторизации с использованием Spring Security 5.1.1.Я обнаружил, что поддержка серверов ресурсов была добавлена ​​в 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.

Возможно, я здесь не совсем согласен с концепциями, но мне нужно больше информации и способ настроить все эти компоненты вместе.

1 Ответ

0 голосов
/ 30 ноября 2018

Я нахожусь в той же ситуации, что и вы. Я решаю эту проблему следующим образом, может быть, она поможет вам:

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

* 1016Реализация * 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));
    }
...