Calling spring authorization server OAuth2 REST endpoints - spring

Trying to implement the OAuth2 protocol using Spring Authorization Server. Created a simple application with the following configuration.
#SpringBootApplication
class AuthorizationServerApplication
fun main(args: Array<String>) {
runApplication<AuthorizationServerApplication>(*args)
}
#Configuration
#Import(OAuth2AuthorizationServerConfiguration::class)
class AuthorizationServerConfig {
#Bean
fun registeredClientRepository(): RegisteredClientRepository? {
val registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client")
.clientSecret("{noop}client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.redirectUri("http://example-host:9002/test/admin")
.redirectUri("http://example-host:9002/test")
.scope(OidcScopes.OPENID)
.scope("read")
.build()
return InMemoryRegisteredClientRepository(registeredClient)
}
...
#Bean
fun providerSettings(): ProviderSettings? {
return ProviderSettings.builder()
.issuer("http://example-host:9000")
.build()
}
}
#EnableWebSecurity
class DefaultSecurityConfig {
#Bean
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http
.authorizeRequests { authorizeRequests ->
authorizeRequests
.anyRequest().permitAll()
}
// these are disabled so that I won't get any additional issue this needs to be changed
.formLogin().disable()
.csrf().disable()
return http.build()
}
#Bean
fun users(): UserDetailsService? {
val admin: UserDetails = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.authorities("read", "write")
.build()
val user: UserDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.authorities("read")
.build()
return InMemoryUserDetailsManager(admin, user)
}
}
When calling the following endpoint: GET http://example-host:9000/.well-known/oauth-authorization-server I get back these:
{
"issuer": "http://example-host:9000",
"authorization_endpoint": "http://example-host:9000/oauth2/authorize",
"token_endpoint": "http://example-host:9000/oauth2/token",
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"jwks_uri": "http://example-host:9000/oauth2/jwks",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token"
],
"revocation_endpoint": "http://example-host:9000/oauth2/revoke",
"revocation_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"introspection_endpoint": "http://example-host:9000/oauth2/introspect",
"introspection_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"code_challenge_methods_supported": [
"S256"
]
}
I'm trying to go past authentication and trying to follow this documentation. I tried multiple calls one of these is:
curl --location --request POST 'example-host:9000/oauth2/token' \
--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret'
I get back 401 most of the time with different messages. Can't really figure out where I can find some documentation with examples as the examples that I was able to find are not really helpful for my usecase. I don't fully get how I would be able to authenticate and use the resource servers' endpoints in case of the client being a front-end application. Maybe I misunderstood something?
Edit:
Adding trace logs from authorization server when requesting token endpoint:
curl --location --request POST 'example-host:9000/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=user' \
--data-urlencode 'password=password'
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda$746/0x000000080105b608#7a764446, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter#841f2ce, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#38eb32b, org.springframework.security.web.context.SecurityContextPersistenceFilter#4232bd1e, org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter#1c463b0b, org.springframework.security.web.header.HeaderWriterFilter#6e87e57e, org.springframework.security.web.csrf.CsrfFilter#3657ca3e, org.springframework.security.web.authentication.logout.LogoutFilter#36c9161d, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter#c85053, org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter#827dabb, org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter#3fce33c2, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter#5f5e39a5, org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter#6f53bec6, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#1be28be5, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#12322dee, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#4aba4a37, org.springframework.security.web.session.SessionManagementFilter#3c29c1e5, org.springframework.security.web.access.ExceptionTranslationFilter#5e60456e, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#5771f1b4, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter#1ec08e27, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter#548d0a8d, org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter#6c15398a, org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter#6bcb4915]] (1/2)
2022-06-14 16:41:47.154 DEBUG 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Securing POST /oauth2/token
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking SecurityContextPersistenceFilter (3/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Created SecurityContextImpl [Null authentication]
2022-06-14 16:41:47.154 DEBUG 34744 --- [nio-9000-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking ProviderContextFilter (4/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (5/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (6/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.csrf.CsrfFilter : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer$$Lambda$746/0x000000080105b608#7a764446]]]
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking LogoutFilter (7/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.s.w.a.logout.LogoutFilter : Did not match request to Ant [pattern='/logout', POST]
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking OAuth2AuthorizationEndpointFilter (8/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking OidcProviderConfigurationEndpointFilter (9/22)
2022-06-14 16:41:47.154 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking NimbusJwkSetEndpointFilter (10/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking OAuth2AuthorizationServerMetadataEndpointFilter (11/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Invoking OAuth2ClientAuthenticationFilter (12/22)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager : Authenticating request with JwtClientAssertionAuthenticationProvider (1/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager : Authenticating request with ClientSecretAuthenticationProvider (2/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.authentication.ProviderManager : Authenticating request with PublicClientAuthenticationProvider (3/11)
2022-06-14 16:41:47.155 TRACE 34744 --- [nio-9000-exec-2] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
2022-06-14 16:41:47.155 DEBUG 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-06-14 16:41:47.156 DEBUG 34744 --- [nio-9000-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-06-14 16:41:47.156 DEBUG 34744 --- [nio-9000-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

First, in your case you don't need the Authorization header in your request for token since you explicitly allowed all requests to pass through via authorizeRequests.anyRequest().permitAll().
Second, in your curl request you didn't specify at least a desired grant type and its parameters.
For example, for the password grant type the request might look something like this:
curl -L -X POST 'example-host:9000/oauth2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client' \
--data-urlencode 'client_secret=client-secret' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=user' \
--data-urlencode 'password=password'
UPDATE:
Spring authorization server 0.3.0 doesn't support the password grant type, exactly as it shows in the grant_types_supported section of the .well-known/oauth-authorization-server endpoint output. There's just no such authentication provider in the org.springframework.security.oauth2.server.authorization.authentication package.
To make at least the client_credentials token request work, add
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
and (in case you'd like to pass the client_id and client_secret inside the POST body)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
to your RegisteredClient in the registeredClientRepository.
Then this can be tested with
curl -L -X POST 'http://example-host:9000/oauth2/token' -H 'Content-Type: application/x-www-form-urlencoded' -d 'grant_type=client_credentials&client_id=client&client_secret=client-secret'
(be sure to pass the actual client_id and client_secret of a RegisteredClient)
Also, if you import OAuth2AuthorizationServerConfiguration a default SecurityFilterChain for the auth server endpoints is created and there's no need to define it manually. On the other hand a SecurityFilterChain for your app authentication is likely still needed.
To debug the OAuth authentication process and see the exact exceptions, if any, set some breakpoints in the org.springframework.security.authentication.ProviderManager#authenticate() method

your client id and client secret must be on Base64 format.
String hashed_keys = Base64.encode("client_id:client_secret");
and you must pass it as http header
curl -L -X POST 'http://example-host:9000/oauth2/token' -H "Authorisation: hashed_keys" -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=client_credentials'

Related

Spring endpoint missed no errors

I am successful in hitting other endpoints in my project except one. Yes incredulous. Below will be the Javascript, the Controller endpoint, the Spring Security trace that shows authorization, but a breakpoint fails to capture anything. There are no errors. This is the same technique I use successfully elsewhere in the same project. How do I begin to debug why/how to fix?
<div class="col-md-4" style="padding: 0px 0px 3px;">
<form class="form-inline justify-content-center" th:action="#{/search}" method="POST"
enctype="application/x-www-form-urlencoded" th:id="searchForm" th:object="${EDIType}">
#RequestMapping(value={"/search"}, method = RequestMethod.POST)
public RedirectView showReportsSearch(#RequestBody EDIType ediType, HttpServletRequest request, Model model,
RedirectAttributes redirectAttributes){
redirectAttributes.addFlashAttribute("EDIType", ediType);
return new RedirectView("clientViewGUI");
}
2022-12-02 13:45:19.876 DEBUG 15128 --- [nio-8080-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
2022-12-02 13:45:19.876 DEBUG 15128 --- [nio-8080-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
2022-12-02 13:45:19.877 DEBUG 15128 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy : /search reached end of additional filter chain; proceeding with original chain
2022-12-02 13:45:19.949 DEBUG 15128 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2022-12-02 13:45:19.952 DEBUG 15128 --- [nio-8080-exec-6] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#20b51d04
2022-12-02 13:45:19.954 DEBUG 15128 --- [nio-8080-exec-6] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

403 Forbidden when introducing authorization on spring boot rest

I am with my first spring-boot project. I did succesfully configure it to check for authentication; if the user/password was wrong the method was not invoked (status 401 unauthorized), if it was right it succeeded.
Now I have added authorization with JSR250 and I am only getting 403 Access denied.
The WS:
#RestController
#RequestMapping("/password")
public class ServicioPassword {
#GetMapping(path = "ldap")
public ResponseEntity<String> getLdap() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("EN LDAP " + authentication.getPrincipal() + " - " + authentication.isAuthenticated());
for (var authority : authentication.getAuthorities()) {
System.out.println("Authority= " + authority);
}
return ResponseEntity.ok("DE LDAP");
}
When invoked, I get this on console:
EN LDAP LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]] - true
Authority= AGNI_OIMIVR
Yet, if I add #RolesAllowed("AGNI_OIMIVR"), when I invoke it I get a 403 Forbidden.
The MethodSecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
public class MethodSecurityConfig
extends GlobalMethodSecurityConfiguration{
}
I have kept the WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Autowired
private Environment environment;
#Bean
BindAuthenticator bindAuthenticator(
final BaseLdapPathContextSource contextSource) {
var bindAuthenticator = new BindAuthenticator(contextSource);
bindAuthenticator.setUserDnPatterns(new String[]{environment.getRequiredProperty("spring.ldap.userdnpattern")});
return bindAuthenticator;
}
#Bean
AuthenticationProvider ldapAuthenticationProvider(
final LdapAuthenticator ldapAuthenticator) {
var ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapAuthenticator);
var ldapUserDetailsMapper = new CustomUserDetailsMapper();
var ldapMemberRoles = environment.getRequiredProperty("spring.ldap.roleattributes");
ldapUserDetailsMapper.setRoleAttributes(ldapMemberRoles.split(","));
ldapUserDetailsMapper.setRolePrefix("");
ldapAuthenticationProvider.setUserDetailsContextMapper(ldapUserDetailsMapper);
return ldapAuthenticationProvider;
}
#Bean
SecurityFilterChain filterChain(
final HttpSecurity http)
throws Exception {
http.csrf().disable()
.cors().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and()
.authorizeRequests()
.anyRequest().authenticated().and()
.httpBasic();
return http.build();
}
UPDATE: Adding log after setting logging.level.org.springframework.security=TRACE:
Note that the line: 2022-07-07 13:04:27.464 WARN 81968 --- [nio-8080-exec-2] e.s.d.o.s.ws.CustomUserDetailsMapper : createAuthority agni_oimivr comes from a log from one of my custom classes.
2022-07-07 13:04:27.441 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Found username 'ivr_apl_user' in Basic Authorization header
2022-07-07 13:04:27.442 TRACE 81968 --- [nio-8080-exec-2] o.s.s.authentication.ProviderManager : Authenticating request with LdapAuthenticationProvider (1/1)
2022-07-07 13:04:27.444 TRACE 81968 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator : Attempting to bind as cn=ivr_apl_user,ou=[REDACTED]
2022-07-07 13:04:27.444 TRACE 81968 --- [nio-8080-exec-2] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user cn=ivr_apl_user,ou=[REDACTED]
2022-07-07 13:04:27.463 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator : Bound cn=ivr_apl_user,ou=[REDACTED]
2022-07-07 13:04:27.463 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.l.u.LdapUserDetailsMapper : Mapping user details from context with DN cn=ivr_apl_user,ou=[REDACTED]
2022-07-07 13:04:27.464 WARN 81968 --- [nio-8080-exec-2] e.s.d.o.s.ws.CustomUserDetailsMapper : createAuthority agni_oimivr
2022-07-07 13:04:27.464 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.l.a.LdapAuthenticationProvider : Authenticated user
2022-07-07 13:04:27.465 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]]
2022-07-07 13:04:27.465 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (7/12)
2022-07-07 13:04:27.465 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.s.HttpSessionRequestCache : No saved request
2022-07-07 13:04:27.465 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (8/12)
2022-07-07 13:04:27.466 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (9/12)
2022-07-07 13:04:27.466 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]]
2022-07-07 13:04:27.466 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking SessionManagementFilter (10/12)
2022-07-07 13:04:27.467 TRACE 81968 --- [nio-8080-exec-2] s.CompositeSessionAuthenticationStrategy : Preparing session with ChangeSessionIdAuthenticationStrategy (1/1)
2022-07-07 13:04:27.467 DEBUG 81968 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : The HttpSession is currently null, and the HttpSessionSecurityContextRepository is prohibited from creating an HttpSession (because the allowSessionCreation property is false) - SecurityContext thus not stored for next request
2022-07-07 13:04:27.467 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (11/12)
2022-07-07 13:04:27.467 TRACE 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (12/12)
2022-07-07 13:04:27.468 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]] before authorizing
2022-07-07 13:04:27.468 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /password/ldap] with attributes [authenticated]
2022-07-07 13:04:27.469 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /password/ldap] with attributes [authenticated]
2022-07-07 13:04:27.470 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Did not switch RunAs authentication since RunAsManager returned null
2022-07-07 13:04:27.470 DEBUG 81968 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Secured GET /password/ldap
2022-07-07 13:04:27.471 TRACE 81968 --- [nio-8080-exec-2] o.s.s.a.i.a.MethodSecurityInterceptor : Did not re-authenticate UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]] before authorizing
2022-07-07 13:04:27.472 TRACE 81968 --- [nio-8080-exec-2] o.s.s.a.i.a.MethodSecurityInterceptor : Authorizing ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity es.ssib.dtic.oimivr.service.ws.v1.ServicioPassword.getLdap(); target is of class [es.ssib.dtic.oimivr.service.ws.v1.ServicioPassword] with attributes [ROLE_AGNI_OIMIVR]
2022-07-07 13:04:27.475 TRACE 81968 --- [nio-8080-exec-2] o.s.s.a.i.a.MethodSecurityInterceptor : Failed to authorize ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity es.ssib.dtic.oimivr.service.ws.v1.ServicioPassword.getLdap(); target is of class [es.ssib.dtic.oimivr.service.ws.v1.ServicioPassword] with attributes [ROLE_AGNI_OIMIVR] using AffirmativeBased [DecisionVoters=[org.springframework.security.access.annotation.Jsr250Voter#6797e2e2, org.springframework.security.access.vote.RoleVoter#2ab76862, org.springframework.security.access.vote.AuthenticatedVoter#152f6a2e], AllowIfAllAbstainDecisions=false]
2022-07-07 13:04:27.484 TRACE 81968 --- [nio-8080-exec-2] o.s.s.w.a.ExceptionTranslationFilter : Sending UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]] to access denied handler since access is denied
org.springframework.security.access.AccessDeniedException: Acceso denegado
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.7.1.jar:5.7.1]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:239) ~[spring-security-core-5.7.1.jar:5.7.1]
[...]
2022-07-07 13:04:27.497 DEBUG 81968 --- [nio-8080-exec-2] o.s.s.w.access.AccessDeniedHandlerImpl : Responding with 403 status code
What am I doing wrong?
The Authentication object of your authenticated user is:
UsernamePasswordAuthenticationToken [Principal=LdapUserDetailsImpl [Dn=cn=ivr_apl_user,ou=IVR,ou=Aplicaciones,dc=pre,dc=aplssib; Username=ivr_apl_user; Password=[PROTECTED]; Enabled=true; AccountNonExpired=true; CredentialsNonExpired=true; AccountNonLocked=true; Granted Authorities=[AGNI_OIMIVR]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[AGNI_OIMIVR]]
Note that the GrantedAuthorities is Granted Authorities=[AGNI_OIMIVR], there is no ROLE_ prefix there. When you add #RolesAllowed("AGNI_OIMIVR") to the method, the ROLE_ prefix will be added automatically to the authority that you passed as an argument to the annotation, becoming ROLE_AGNI_OIMIVR.
Spring Security will try to match ROLE_AGNI_OIMIVR that is in the annotation with AGNI_OIMIVR that is in the granted authorities' property, but they do not match.
You have three options:
Change the role in LDAP to have the ROLE_ prefix
Expose a Bean of GrantedAuthorityDefaults removing the rolePrefix, like so:
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
Use #PreAuthorize("hasAuthority('AGNI_OIMIVR')")
Another tip would be to use the new #EnableMethodSecurity(jsr250Enabled = true) which uses the simplified AuthorizationManager API, improve logging, amongst others.

Why does a Keycloak bearer token appear to be truncated during security filter chain processing

I am working with Keycloak 16.1.0, spring boot 2.6.2 and an external application client that sends a bearer token in to my server application to the endpoint http://romanmed-host:8888/actuator/health.
By cranking the debugging level up to maximum, I can see the access token before its processed. I can verify that its accurate by using the JWT Debug site JSON Web Tokens to verify that the signature is correct.
Yet several lines later in the output log the same bearer token appears to be somewhat truncated, its listed with an error saying that it failed to verify. When checked by using the JWT site indicates a signature error, but the token content is correct.
Naturally I would like to know why it appears to be truncated and what I can do about it.
I can match the output from the client program to the server and its not been changed, so truncation must occur within the server program.
The program is accepting the request by a get request, since the token can be checked by JWT as valid at this point, its not truncated by the get request input method.
I have not inserted a filter in the security filter chain, so I can see how any of my code could be doing anything to invalid the token.
Other than the Failed to verify token no other error messages are generated, suggesting that until this point everything is correct.
I can see that the WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter,KeycloakPreAuthActionsFilter and KeycloakAuthenticationProcessingFilter have all been invoked.
I am assuming that the problem is somewhere within the KeycloakAuthenticationProcessingFilter, but I don't understand why the token appears to have been truncated at this point.
The received bearer token is
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.fwQPLiSIrUSjnRnTBrd1vvGic49OSf7aGDemc0TdmTshZzJ-eYhiEqnAh9-QU2rxDayPIhoIzA9CgBXmGPCnl1Qu4CujDddpBcLpnjszBoBdzwjDgpShgwFpGk0fGCM0fxtSZgMWRfeS_sRjBpRzZ42GelCYZ2E1kZX_E7o_LB3thpiv5oYqgTNucusNmzpm0-iFcEUe5rfnu2ZOHI_hLQvIYKlGURnNld4jov-KDLf2QTh2h3XqjbsGHG9PDq4MbFPhKY_9yF0jQkhF6F3oYrw9MIH4SbemrR-CHw6-aWqGmgucjJ7iKMY5o86HxLPu2tzM06NdaurQZX4ImLCBlQ
Its truncated format is
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9
The debug log is
servletPath:/actuator/health
pathInfo:null
headers:
accept-encoding: gzip
user-agent: ReactorNetty/1.0.13
host: romanmed-host:8888
authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.fwQPLiSIrUSjnRnTBrd1vvGic49OSf7aGDemc0TdmTshZzJ-eYhiEqnAh9-QU2rxDayPIhoIzA9CgBXmGPCnl1Qu4CujDddpBcLpnjszBoBdzwjDgpShgwFpGk0fGCM0fxtSZgMWRfeS_sRjBpRzZ42GelCYZ2E1kZX_E7o_LB3thpiv5oYqgTNucusNmzpm0-iFcEUe5rfnu2ZOHI_hLQvIYKlGURnNld4jov-KDLf2QTh2h3XqjbsGHG9PDq4MbFPhKY_9yF0jQkhF6F3oYrw9MIH4SbemrR-CHw6-aWqGmgucjJ7iKMY5o86HxLPu2tzM06NdaurQZX4ImLCBlQ
accept: application/vnd.spring-boot.actuator.v2+json, application/vnd.spring-
boot.actuator.v1+json, application/json
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
KeycloakPreAuthActionsFilter
KeycloakAuthenticationProcessingFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
KeycloakSecurityContextRequestFilter
KeycloakAuthenticatedActionsFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2022-03-04 14:03:30.088 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#2caa9666, org.springframework.security.web.context.SecurityContextPersistenceFilter#67683210, org.springframework.security.web.header.HeaderWriterFilter#58a9e64d, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter#3fecb076, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter#41d84abb, org.springframework.security.web.authentication.logout.LogoutFilter#3e563293, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#25511895, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#21202507, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter#62159fd, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter#28e8dee7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#263f6e96, org.springframework.security.web.session.SessionManagementFilter#d3b0397, org.springframework.security.web.access.ExceptionTranslationFilter#75d0cac6, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#2267b0bb]] (1/1)
2022-03-04 14:03:30.088 DEBUG 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Securing GET /actuator/health
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (1/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking SecurityContextPersistenceFilter (2/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] w.c.HttpSessionSecurityContextRepository : Created SecurityContextImpl [Null authentication]
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (3/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking KeycloakPreAuthActionsFilter (4/14)
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://romanmed-host:8888/actuator/health
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking KeycloakAuthenticationProcessingFilter (5/14)
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : --> authenticate()
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : try bearer
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Found [1] values in authorization header, selecting the first value for Bearer.
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Verifying access_token
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : access_token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.signature
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Failed to verify token
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : Bearer FAILED
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Auth outcome: FAILED
2022-03-04 14:03:30.092 TRACE 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Failed to process authentication request
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for detailsr code here
The code is taken from an example by Thomas Darimont at Securing Spring Boot Admin & actuator endpoints with Keycloak and assumed to be correct.
The code is as follows
import lombok.extern.slf4j.Slf4j;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import java.security.Principal;
#KeycloakConfiguration
#Slf4j
#EnableConfigurationProperties(KeycloakSpringBootProperties.class)
class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.authorizeRequests()
.requestMatchers(EndpointRequest.to(
InfoEndpoint.class,
HealthEndpoint.class
)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint())
.hasRole("ACTUATOR")
.anyRequest().permitAll()
;
}
/**
* Use {#link KeycloakAuthenticationProvider}
*
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
grantedAuthorityMapper.setConvertToUpperCase(true);
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
#Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
/**
* Allows to inject requests scoped wrapper for {#link KeycloakSecurityContext}.
*
* Returns the {#link KeycloakSecurityContext} from the Spring
* {#link ServletRequestAttributes}'s {#link Principal}.
* <p>
* The principal must support retrieval of the KeycloakSecurityContext, so at
* this point, only {#link KeycloakPrincipal} values and
* {#link KeycloakAuthenticationToken} are supported.
*
* #return the current <code>KeycloakSecurityContext</code>
*/
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public KeycloakSecurityContext provideKeycloakSecurityContext() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Principal principal = attributes.getRequest().getUserPrincipal();
if (principal == null) {
return null;
}
if (principal instanceof KeycloakAuthenticationToken) {
principal = Principal.class.cast(KeycloakAuthenticationToken.class.cast(principal).getPrincipal());
}
if (principal instanceof KeycloakPrincipal) {
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
}
return null;
}
The problem is the set up of the client and server programs.
The server used the value
auth-server-url: http://localhost:8085/auth
in its application.yml file to define the location of the Keycloak server, the client used the value
auth-server-url: http://romanmed-host:8085/auth
to define the location of Keycloak where the machine name romanmed-host is an alias for localhost. Having changed these values to be the same value, everything works as expected.
The diagnostics generated by the debugging/trace code are confusing, string described as truncated header seems to be truncated the bearer token, with out the signature. What the 'Keycloak` diagnostic is attempting to print is the part of the token which defines the tokens permissions and not the signature section.
Running the entire bearer token through the JWT site does show that the token is valid, because its a correctly encoded token and is legitimate.
The problem is not the token, but the way the token is being used! The client was expecting a legal signed token generated by them instance of Keycloak that it knew about, what it got was a legal signed token generated by Keycloak with a different address, which it correctly objected to.
The problem being the nature of the generated error message, it just claimed that the token signature was invalid, had it said something about an invalid/unexpected hostname, the nature of the problem would have been rather more obvious and resolved much faster. Keycloak is design to be flexible, so error messages tend to more vague to cover all situations, hence the message there is something wrong with your bearer token signature which is correct, but vague.
There seems to several schools of thought on how to resolve issues like this, one is to use an raw ip address which will always resolve to the same value. Thus avoid problems like this. This suffers from if the Keycloak server is moved to another machine there are lots of values to change.
My solution is to define an alias value in the hosts/dns server for the address of the eycloak server and always use that value in the support files. Hence if the Keycloak server is ever moved to another address, there is only one value to change.

How to handle custom exceptions thrown by a filter in Spring Security

I am new to Spring Security.
I have a piece of code where I check if an Authorization header is passed in a request and I throw an exception if it's missing.
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String BEARER = "Bearer";
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String username = request.getParameter("username");
String authorization = request.getHeader("AUTHORIZATION");
if (!request.getRequestURI().equals(UniversalConstants.LOGIN_PATH)) {
if (authorization == null || authorization.length() == 0 || !authorization.startsWith(BEARER)) {
throw new InvalidCredentialsException("Missing authentication token"); //<-----------------
}
}
String password = request.getParameter("password");
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
I am targeting to handle all exceptions globally so I'm using #ControllerAdvice.
Note: I know that #ControllerAdvice will not work for exceptions thrown outside of Controllers from this and this, so I have also followed the suggestions in these links.
RestAuthenticationEntryPoint.java
#Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
public RestAuthenticationEntryPoint() {
System.out.println("RestAuthenticationEntryPoint");
}
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
resolver.resolveException(request, response, null, authException);
}
}
This is how I configure the authenticationEntryPoint:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint()).and().cors().and().csrf().disable().exceptionHandling().defaultAuthenticationEntryPointFor(new RestAuthenticationEntryPoint(), PROTECTED_URLS)
.and().authenticationProvider(customAuthenticationProvider())
.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class).authorizeRequests()
.requestMatchers(PROTECTED_URLS).authenticated().and().formLogin().disable().httpBasic().disable();
}
CustomExceptionHandler.java
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({InvalidCredentialsException.class, AuthenticationException.class})
public ResponseEntity<ErrorResponse> handleUnauthorizedError(InvalidCredentialsException e, WebRequest request) {
String errorMessage = e.getLocalizedMessage();
ErrorResponse errorResponse = new ErrorResponse(errorMessage, null);
return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
}
}
InvalidCredentialsException.java
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public class InvalidCredentialsException extends RuntimeException {
public InvalidCredentialsException(String errorMessage) {
super(errorMessage);
}
}
Upon debugging, I've found that the resolver.resolveException(...) in RestAuthenticationEntryPoint and the handleUnauthorizedError(..) in CustomExceptionHandler never get called.
I wish to handle throw new InvalidCredentialsException("Missing authentication token") in an elegant way and show a decent JSON output in the response.
Any help would be appreciated.
Edit: The stack trace
2021-05-20 17:41:29.985 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user/hello'; against '/public/**'
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/error**']
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user/hello'; against '/error**'
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy : /user/hello?username=user&password=user at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /user/hello' doesn't match 'DELETE /logout'
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy : /user/hello?username=user&password=user at position 6 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.s.HttpSessionRequestCache : saved request doesn't match
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy : /user/hello?username=user&password=user at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy : /user/hello?username=user&password=user at position 8 of 12 in additional filter chain; firing Filter: 'TokenAuthenticationFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user/hello'; against '/public/**'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = true
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#7fb6b4e0
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2021-05-20 17:41:38.033 ERROR 24808 --- [nio-8181-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
com.spring.fieldSecurity.Exceptions.InvalidCredentialsException: Missing authentication token
at com.spring.fieldSecurity.Service.TokenAuthenticationFilter.attemptAuthentication(TokenAuthenticationFilter.java:44) ~[classes/:na]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
.
. // more error trace here
.
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/public/**'
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/error**']
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/error**'
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher : matched
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy : /error?username=user&password=user has an empty filter list
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error?username=user&password=user", parameters={masked}
2021-05-20 17:41:38.035 DEBUG 24808 --- [nio-8181-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2021-05-20 17:41:38.035 DEBUG 24808 --- [nio-8181-exec-3] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-05-20 17:41:38.724 DEBUG 24808 --- [nio-8181-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [application/json] and supported [application/json, application/*+json, application/json, application/*+json]
2021-05-20 17:41:38.724 DEBUG 24808 --- [nio-8181-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Thu May 20 17:41:38 IST 2021, status=500, error=Internal Server Error, message=, path=/us (truncated)...]
2021-05-20 17:41:38.726 DEBUG 24808 --- [nio-8181-exec-3] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-05-20 17:41:38.727 DEBUG 24808 --- [nio-8181-exec-3] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500
Spring security has a filter which is called the ExceptionTranslationFilter which translates AccessDeniedException and AuthenticationException into responses. This filter catches these thrown exceptions in the spring security filter chain.
So if you want to return a custom exception, you could instead inherit from one of these classes instead of RuntimeException and add a custom message.
I just want to emphasis and it can never be said too many times:
Providing friendly error messages in production applications when it comes to authentication/authorization is in general bad practice from a security standpoint. These types of messages can benefit malicious actors, when trying out things so that they realize what they have done wrong and guide them in their hacking attempts.
Providing friendly messages in test environments may be okey, but make sure that they are disabled in production. In production all failed authentication attempts a recommendation is to return a 401 with no additional information. And in graphical clients, generalized error messages should be displayed for instance "failed to authenticate" with no given specifics.
Also:
Writing custom security as you have done is also in general bad practice. Spring security is battle tested with 100000 of applications running it in production environments. Writing a custom filter to handle token and passwords, is in general not needed. Spring security already has implemented filters to handle security and authentication using standards like BASIC authentication and TOKEN/JWT. If you implement a non standard login, one bug might expose your application to a huge risk.
Username and password authentication in spring
Oauth2 authentication in spring

How to resolve 403 on spring security JWT authentication for public resources

I'm trying to implement a JWT based authentication using spring security in a spring boot API, but I don't know what I'm doing wrong. On my implementation of WebSecurityConfigurerAdapter I permit access to auth/** resource, but when I make a request to, for example, /auth/login, I get a 403. It seems that it is ignoring the "public" resources.
The csrf() is disabled.
This is the repository: https://github.com/wallysoncarvalho/jwt-auth-spring-security
I enabled DEBUG mode and that's what I get:
Request received for POST '/auth/login?username=wally&password=wally':
org.apache.catalina.connector.RequestFacade#585d8cc6
servletPath:/auth/login
pathInfo:null
headers:
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 91c2a071-a353-4d77-9c7c-b04a43b94081
host: localhost:8091
accept-encoding: gzip, deflate, br
connection: keep-alive
content-length: 0
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
JwtFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2020-12-22 20:21:15.919 DEBUG 6288 --- [nio-8091-exec-2] o.s.security.web.FilterChainProxy : Securing POST /auth/login?username=wally&password=wally
2020-12-22 20:21:15.937 DEBUG 6288 --- [nio-8091-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2020-12-22 20:21:15.948 DEBUG 6288 --- [nio-8091-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2020-12-22 20:21:15.960 INFO 6288 --- [nio-8091-exec-2] Spring Security Debugger :
************************************************************
Request received for POST '/error?username=wally&password=wally':
org.apache.catalina.core.ApplicationHttpRequest#6d88bc8c
servletPath:/error
pathInfo:null
headers:
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 91c2a071-a353-4d77-9c7c-b04a43b94081
host: localhost:8091
accept-encoding: gzip, deflate, br
connection: keep-alive
content-length: 0
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
JwtFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2020-12-22 20:21:15.961 DEBUG 6288 --- [nio-8091-exec-2] o.s.security.web.FilterChainProxy : Securing POST /error?username=wally&password=wally
2020-12-22 20:21:15.961 DEBUG 6288 --- [nio-8091-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2020-12-22 20:21:15.967 DEBUG 6288 --- [nio-8091-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2020-12-22 20:21:15.998 DEBUG 6288 --- [nio-8091-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [POST /error?username=wally&password=wally] with attributes [authenticated]
2020-12-22 20:21:16.022 DEBUG 6288 --- [nio-8091-exec-2] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
2020-12-22 20:21:16.025 DEBUG 6288 --- [nio-8091-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/assets/**",);
}
And your #Configuration class must implements WebMvcConfigurer
Edit
Also enable WebSecurity in your config class by annotating it with #EnableWebSecurity

Resources