Reputation: 961
I am trying to configure Spring Authorization Server (version 1.1.0, Boot 3.1.0), but it's not working the way I think the docs suggest. When making a request to the authorization endpoint, the OAuth2AuthorizationEndpointFilter
is denying authorization because it does not have a provider for the OAuth2AuthorizationCodeRequestAuthenticationToken
. The token does have an authenticated UsernamePasswordAuthenticationToken
inside of it with the correct GrantedAuthorities
, so the upstream part of authentication seems to be working. But the OAuth2AuthorizationCodeRequestAuthenticationConverter
turns it into the OAuth specific token.
The AuthenticationManager
being used is the main one for the rest of the app, which has only Dao and RememberMe providers. I try to add the OAuth2AuthorizationCodeRequestAuthenticationProvider
via the configurer, as seen in the code below, but it's not there when I set a breakpoint.
Here is my security config (the comments are the XML configuration for the previous Spring Security OAuth 2 which was working fine - but not in Boot 3.1.0).
/*
<http pattern="/oauth/**" auto-config="true" entry-point-ref="oauthLoginEntryPoint" use-expressions="true" create-session="always">
<csrf disabled="false"/>
<intercept-url pattern="/oauth/authorize" access="hasRole('ROLE_MEMBER')" />
<intercept-url pattern="/oauth/**" access="permitAll"/>
<!-- UsernamePasswordAuthenticationFilter -->
<form-login default-target-url="/oauth/authorize"
login-processing-url="/oauth/do_login"
authentication-failure-url="/oauth/login?failure=true"
authentication-success-handler-ref="oauthAuthenticationSuccessHandler"
username-parameter="username"
password-parameter="password" />
</http>
*/
@Bean
@Order(103)
public SecurityFilterChain oauthFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager,
JdbcRegisteredClientRepository registeredClientRepository,
OAuth2TokenGenerator<?> tokenGenerator,
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider)
throws Exception
{
http
.securityMatcher("/oauth/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/oauth/authorize").hasRole("MEMBER")
.requestMatchers("/oauth/**").permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.sessionFixation(c -> c.none()) // FIXME turn it back on
)
.authenticationManager(authenticationManager)
.formLogin(form -> form
.successForwardUrl("/oauth/authorize")
.loginProcessingUrl("/oauth/do_login")
.failureUrl("/oauth/login?failure=true")
.successHandler(oauthAuthenticationSuccessHandler())
.usernameParameter("username")
.passwordParameter("password")
.loginPage(OAUTH_LOGIN_VIEW_NAME)
)
.exceptionHandling(exception -> exception.authenticationEntryPoint(oauthLoginEntryPoint()))
.csrf(withDefaults())
;
//OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository)
.tokenGenerator(tokenGenerator)
.clientAuthentication(clientAuthentication -> clientAuthentication
.authenticationProvider(authorizationCodeRequestAuthenticationProvider)
)
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.authenticationProvider(authorizationCodeRequestAuthenticationProvider)
)
.tokenEndpoint(tokenEndpoint -> { })
;
return http.build();
}
/*
<!-- this forces the redirect back to /oauth/authorize to be https -->
<beans:bean id="oauthAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="redirectStrategy">
<beans:bean class="com.hatchbaby.service.security.ForceHttpsRedirectStrategy">
<beans:property name="forceHttps" value="${oauth.https.login}"/>
</beans:bean>
</beans:property>
</beans:bean>
*/
@Bean
public SavedRequestAwareAuthenticationSuccessHandler oauthAuthenticationSuccessHandler()
{
SavedRequestAwareAuthenticationSuccessHandler ash = new SavedRequestAwareAuthenticationSuccessHandler();
ForceHttpsRedirectStrategy rs = new ForceHttpsRedirectStrategy();
rs.setForceHttps(forceHttps);
ash.setRedirectStrategy(rs);
return ash;
}
@Bean
public LoginUrlAuthenticationEntryPoint oauthLoginEntryPoint()
{
LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint(OAUTH_LOGIN_VIEW_NAME);
ep.setForceHttps(forceHttps);
return ep;
}
@Bean
public JdbcRegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations)
{
JdbcRegisteredClientRepository repo = new JdbcRegisteredClientRepository(jdbcOperations);
return repo;
}
@Bean
public OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource)
{
JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public AuthorizationServerSettings authorizationServerSettings()
{
AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder()
.authorizationEndpoint("/oauth/authorize")
.tokenEndpoint("/oauth/token")
;
return builder.build();
}
@Bean
public OAuth2AuthorizationCodeRequestAuthenticationProvider oAuth2AuthorizationCodeRequestAuthenticationProvider(
RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationService authorizationService,
OAuth2AuthorizationConsentService authorizationConsentService)
{
return new OAuth2AuthorizationCodeRequestAuthenticationProvider(registeredClientRepository, authorizationService, authorizationConsentService);
}
@Bean
public OAuth2AuthorizationService oauth2AuthorizationService()
{
// FIXME use jdbc
//JdbcOAuth2AuthorizationService auth = new JdbcOAuth2AuthorizationService();
var auth = new InMemoryOAuth2AuthorizationService();
return auth;
}
@Bean
public OAuth2AuthorizationConsentService oauth2AuthorizationConsentService()
{
// FIXME not in memory
var auth = new InMemoryOAuth2AuthorizationConsentService();
return auth;
}
Any help on getting that authorizationCodeRequestAuthenticationProvider
to show up in the AuthenicationManager
?
Upvotes: 0
Views: 403
Reputation: 961
In the end I was unable to get the DSL to add the authentication providers that I wanted. I kept adding them to the DSL because I felt like I "should".
@Bean
@Order(103)
public SecurityFilterChain oauthFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager,
JdbcRegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider,
OAuth2AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider,
OAuth2RefreshTokenAuthenticationProvider oauth2RefreshTokenAuthenticationProvider,
OAuth2ClientCredentialsAuthenticationProvider oauth2ClientCredentialsAuthenticationProvider,
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider
) throws Exception
{
http
.securityMatcher("/oauth/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/oauth/authorize").hasRole("MEMBER")
.requestMatchers("/oauth/**").permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.sessionFixation(c -> c.none()) // FIXME turn it back on
)
.authenticationManager(authenticationManager)
.formLogin(form -> form
.successForwardUrl("/oauth/authorize")
.loginProcessingUrl("/oauth/do_login")
.failureUrl("/oauth/login?failure=true")
.successHandler(oauthAuthenticationSuccessHandler())
.usernameParameter("username")
.passwordParameter("password")
.loginPage(OAUTH_LOGIN_VIEW_NAME)
)
.exceptionHandling(exception -> exception.authenticationEntryPoint(oauthLoginEntryPoint()))
.csrf(c -> c.ignoringRequestMatchers("/oauth/token"))
;
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository)
.tokenGenerator(tokenGenerator())
.clientAuthentication(clientAuthentication -> clientAuthentication
.authenticationProvider(authorizationCodeRequestAuthenticationProvider)
)
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.authenticationProvider(authorizationCodeRequestAuthenticationProvider)
)
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.authenticationProvider(oauth2AuthorizationCodeAuthenticationProvider)
.authenticationProvider(oauth2RefreshTokenAuthenticationProvider)
.authenticationProvider(oauth2ClientCredentialsAuthenticationProvider)
.authenticationProvider(clientSecretAuthenticationProvider)
)
;
return http.build();
}
But in order to make it work, I had to go back to the main AuthenticationManager
and update it:
@Bean(name = {BeanIds.AUTHENTICATION_MANAGER, "authenticationManager"})
@Primary
public AuthenticationManager authenticationManager(
OAuth2AuthorizationCodeRequestAuthenticationProvider oauth2AuthorizationCodeRequestAuthenticationProvider,
OAuth2AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider,
OAuth2RefreshTokenAuthenticationProvider oauth2RefreshTokenAuthenticationProvider,
OAuth2ClientCredentialsAuthenticationProvider oauth2ClientCredentialsAuthenticationProvider,
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider,
OauthSecretKeySource keySource,
LegacyOauthAuthenticationProvider legacyOauthAuthenticationProvider
)
{
// FIXME I don't like having to add the whole Oauth guys here
List<AuthenticationProvider> aps = List.of(
authProvider(),
rememberMeAuthenticationProvider(),
oauth2AuthorizationCodeRequestAuthenticationProvider,
oauth2AuthorizationCodeAuthenticationProvider,
oauth2RefreshTokenAuthenticationProvider,
oauth2ClientCredentialsAuthenticationProvider,
clientSecretAuthenticationProvider,
legacyOauthAuthenticationProvider,
jwtAuthenticationProvider(keySource)
);
ProviderManager pm = new ProviderManager(aps);
return pm;
}
So the Oauth guys are added to the AuthenticationManager
for everyone, while I was hoping they'd be limited to that SecurityFilterChain
. Not ideal, but it's working.
Upvotes: 0