I'm migrating from spring-boot 2.7.18
to 3.3.3
and moving from
spring-security-oauth2
to spring-boot-starter-oauth2-authorization-server
My use-case is a bit different from the samples in the new oauth2-authorization-server
. Resource owners are not authenticating with a basic auth login form, but are coming to my OAuth2 server with a Bearer token, which is generated by a different service. I need to authenticate them via a JWT Bearer token.
my oauth2-server
had this defaultSecurityFilterChain
config
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/oauth/authorize")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterAfter(new CustomJwtAuthenticationFilter(secret), BasicAuthenticationFilter.class)
.addFilterBefore(new CustomHSTSFilter(), CustomJwtAuthenticationFilter.class)
which worked fine with old spring-security-oauth2
library
Now since a lot of things changed in the new spring security and oauth2-authorization-server
I have changed defaultSecurityFilterChain
config to this
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/oauth2/authorize","/hello").authenticated())
.addFilterAfter(new CustomJwtAuthenticationFilter(secret), BasicAuthenticationFilter.class)
.addFilterBefore(new CustomHSTSFilter(), CustomJwtAuthenticationFilter.class);
Unfortunately this doesn't work, and I would appreciate if someone can explain how should I configure security filter chain to work.
My /hello
endpoint which I defined is working fine, but when I send request to GET /oauth2/authorize?client_id=....
I get 403 Pre-authenticated entry point called. Rejecting access
I can see im my logs that my CustomJwtAuthenticationFilter
is invoked and GET /oauth2/authorize?client_id=....
gets secured BUT then it tries to secure GET /error?client_id=...
and it fails in AuthorizationFilter
I get Access Denied with message Pre-authenticated entry point called. Rejecting access
.
Does anyone have an idea what is wrong in my config, and am I missing?
I have tried to add .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
in my defaultSecurityFilterChain
then /error
gets Secured but I get 404 without any message.
I have also tried to add .requestMatchers("/error").permitAll()
with same 404 result
Also I have tried changing my authorizationServerSecurityFilterChain
with adding custom authorizationRequestConverter
on authorizationEndpoint
but I found myself writing more complex code than it should be and figured out that this is probably not a way to implement this.
Section 3.1 of the OAuth 2 specification requires for the authorization endpoint:
The authorization server MUST first authenticate the resource owner. The way in which the authorization server authenticates the resource owner (e.g., username and password login, passkey, federated login, or by using an established session) is beyond the scope of this specification.
The example in the Spring Authorization Server documentation redirects to a login form to submit a username and password, but you want to authenticate by a bearer token instead. The Spring Authorization Server developers suggest you can configure a custom AuthenticationConverter in the authorizationRequestConverter of the authorizationEndpoint:
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.authorizationEndpoint(endpoint -> endpoint
.authorizationRequestConverter(customAuthorizationRequestConverter))
The custom AuthenticationConverter verifies the bearer token and gets the principal from the bearer token:
@RequiredArgsConstructor
public class CustomAuthorizationRequestConverter implements AuthenticationConverter {
private static final OAuth2AuthorizationCodeRequestAuthenticationConverter DELEGATE =
new OAuth2AuthorizationCodeRequestAuthenticationConverter();
private static final DefaultBearerTokenResolver BEARER_TOKEN_RESOLVER =
new DefaultBearerTokenResolver();
private static final JwtGrantedAuthoritiesConverter GRANTED_AUTHORITIES_CONVERTER =
new JwtGrantedAuthoritiesConverter();
private final JwtDecoder jwtDecoder;
private Jwt getJwt(String encoded) {
try {
return jwtDecoder.decode(encoded);
} catch (JwtException e) {
throw new OAuth2AuthorizationCodeRequestAuthenticationException(
new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, e.getMessage(), null), e, null);
}
}
@Override
public Authentication convert(HttpServletRequest request) {
String accessTokenValue = BEARER_TOKEN_RESOLVER.resolve(request);
if (accessTokenValue == null) {
return null;
}
var authentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) DELEGATE.convert(request);
if (authentication == null) {
return null;
}
Jwt jwt = getJwt(accessTokenValue);
String username = jwt.getClaimAsString(JwtClaimNames.SUB);
Collection<GrantedAuthority> authorities = GRANTED_AUTHORITIES_CONVERTER.convert(jwt);
var principal = new UsernamePasswordAuthenticationToken(username, null, authorities);
return new OAuth2AuthorizationCodeRequestAuthenticationToken(
authentication.getAuthorizationUri(),
authentication.getClientId(),
principal,
authentication.getRedirectUri(),
authentication.getState(),
authentication.getScopes(),
authentication.getAdditionalParameters());
}
}