|
@@ -1,5 +1,5 @@
|
|
|
/*
|
|
|
- * Copyright 2002-2021 the original author or authors.
|
|
|
+ * Copyright 2002-2022 the original author or authors.
|
|
|
*
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
* you may not use this file except in compliance with the License.
|
|
@@ -16,22 +16,21 @@
|
|
|
|
|
|
package org.springframework.security.oauth2.server.resource.authentication;
|
|
|
|
|
|
-import java.time.Instant;
|
|
|
-import java.util.Collection;
|
|
|
-
|
|
|
import reactor.core.publisher.Mono;
|
|
|
|
|
|
+import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
import org.springframework.security.core.AuthenticationException;
|
|
|
import org.springframework.security.core.GrantedAuthority;
|
|
|
-import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|
|
-import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
|
|
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
|
|
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
|
|
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
|
|
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
|
|
|
+import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
|
|
|
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
|
|
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
|
|
import org.springframework.util.Assert;
|
|
|
|
|
@@ -46,16 +45,16 @@ import org.springframework.util.Assert;
|
|
|
* verifying an opaque access token, returning its attributes set as part of the
|
|
|
* {@link Authentication} statement.
|
|
|
* <p>
|
|
|
- * Scopes are translated into {@link GrantedAuthority}s according to the following
|
|
|
- * algorithm:
|
|
|
- * <ol>
|
|
|
- * <li>If there is a "scope" attribute, then convert to a {@link Collection} of
|
|
|
- * {@link String}s.
|
|
|
- * <li>Take the resulting {@link Collection} and prepend the "SCOPE_" keyword to each
|
|
|
- * element, adding as {@link GrantedAuthority}s.
|
|
|
- * </ol>
|
|
|
+ * A {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token
|
|
|
+ * attributes from an authorization server.
|
|
|
+ * <p>
|
|
|
+ * A {@link ReactiveOpaqueTokenAuthenticationConverter} is responsible for turning a
|
|
|
+ * successful introspection result into an {@link Authentication} instance (which may
|
|
|
+ * include mapping {@link GrantedAuthority}s from token attributes or retrieving from
|
|
|
+ * another source).
|
|
|
*
|
|
|
* @author Josh Cummings
|
|
|
+ * @author Jerome Wacongne <ch4mp@c4-soft.com>
|
|
|
* @since 5.2
|
|
|
* @see ReactiveAuthenticationManager
|
|
|
*/
|
|
@@ -63,6 +62,8 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|
|
|
|
|
private final ReactiveOpaqueTokenIntrospector introspector;
|
|
|
|
|
|
+ private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenReactiveAuthenticationManager::convert;
|
|
|
+
|
|
|
/**
|
|
|
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
|
|
|
* parameters
|
|
@@ -73,6 +74,17 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|
|
this.introspector = introspector;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Introspect and validate the opaque
|
|
|
+ * <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
|
|
+ * Token</a> and then delegates {@link Authentication} instantiation to
|
|
|
+ * {@link ReactiveOpaqueTokenAuthenticationConverter}.
|
|
|
+ * <p>
|
|
|
+ * If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
|
|
+ * details are null, then introspection result details are used.
|
|
|
+ * @param authentication the authentication request object.
|
|
|
+ * @return A successful authentication
|
|
|
+ */
|
|
|
@Override
|
|
|
public Mono<Authentication> authenticate(Authentication authentication) {
|
|
|
// @formatter:off
|
|
@@ -80,21 +92,14 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|
|
.filter(BearerTokenAuthenticationToken.class::isInstance)
|
|
|
.cast(BearerTokenAuthenticationToken.class)
|
|
|
.map(BearerTokenAuthenticationToken::getToken)
|
|
|
- .flatMap(this::authenticate)
|
|
|
- .cast(Authentication.class);
|
|
|
+ .flatMap(this::authenticate);
|
|
|
// @formatter:on
|
|
|
}
|
|
|
|
|
|
- private Mono<BearerTokenAuthentication> authenticate(String token) {
|
|
|
+ private Mono<Authentication> authenticate(String token) {
|
|
|
// @formatter:off
|
|
|
return this.introspector.introspect(token)
|
|
|
- .map((principal) -> {
|
|
|
- Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
|
|
- Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
|
|
- // construct token
|
|
|
- OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
|
|
- return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
|
|
- })
|
|
|
+ .flatMap((principal) -> this.authenticationConverter.convert(token, principal))
|
|
|
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
|
|
|
// @formatter:on
|
|
|
}
|
|
@@ -106,4 +111,27 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|
|
return new AuthenticationServiceException(ex.getMessage(), ex);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Default {@link ReactiveOpaqueTokenAuthenticationConverter}.
|
|
|
+ * @param introspectedToken the bearer string that was successfully introspected
|
|
|
+ * @param authenticatedPrincipal the successful introspection output
|
|
|
+ * @return an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
|
|
|
+ * result
|
|
|
+ */
|
|
|
+ static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
|
|
+ return Mono.just(OpaqueTokenAuthenticationProvider.convert(introspectedToken, authenticatedPrincipal));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Provide with a custom bean to turn successful introspection result into an
|
|
|
+ * {@link Authentication} instance of your choice. By default,
|
|
|
+ * {@link BearerTokenAuthentication} will be built.
|
|
|
+ * @param authenticationConverter the converter to use
|
|
|
+ * @since 5.8
|
|
|
+ */
|
|
|
+ public void setAuthenticationConverter(ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
|
|
+ Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
|
|
+ this.authenticationConverter = authenticationConverter;
|
|
|
+ }
|
|
|
+
|
|
|
}
|