Maurice Müller
Maurice Müller

Reputation: 1500

Spring Security + Keycloak: Accept Bearer Token

Somehow I'm lost with Spring Security and Keycloak.

In an application I successfully receive an access token from my Keycloak instance. I then use this token for a request against my Spring Security server (which uses the same Keycloak instance).

But all I get are 403 errors.

Here are code excerpts (written in kotlin):

Security Config:

@KeycloakConfiguration
abstract class MyConfig : KeycloakWebSecurityConfigurerAdapter() {

   @Autowired
   lateinit var keycloakClientRequestFactory: KeycloakClientRequestFactory

   @Bean
   @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   fun keycloakRestTemplate(): KeycloakRestTemplate {
      return KeycloakRestTemplate(keycloakClientRequestFactory)
   }

   @Autowired
   @Throws(Exception::class)
   fun configureGlobal(auth: AuthenticationManagerBuilder) {
      val keycloakAuthenticationProvider = keycloakAuthenticationProvider()
      keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
      auth.authenticationProvider(keycloakAuthenticationProvider)
   }

   @Bean
   fun KeycloakConfigResolver(): KeycloakConfigResolver {
      return KeycloakSpringBootConfigResolver()
   }

   @Throws(Exception::class)
   override fun configure(http: HttpSecurity) {
      http.csrf().disable()
        .cors().and()
        .authorizeRequests()
        .anyRequest().authenticated()
      http.requiresChannel().anyRequest().requiresSecure()
   }

   @Bean
   override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy =
      RegisterSessionAuthenticationStrategy(SessionRegistryImpl())

   @Bean
   fun keycloakAuthenticationProcessingFilterRegistrationBean(
      filter: KeycloakAuthenticationProcessingFilter): FilterRegistrationBean {
      val registrationBean = FilterRegistrationBean(filter)
      registrationBean.isEnabled = false
      return registrationBean
   }

   @Bean
   fun keycloakPreAuthActionsFilterRegistrationBean(
      filter: KeycloakPreAuthActionsFilter): FilterRegistrationBean {
      val registrationBean = FilterRegistrationBean(filter)
      registrationBean.isEnabled = false
      return registrationBean
   }

   @Bean
   fun corsConfigurationSource(): CorsConfigurationSource {
      val configuration = CorsConfiguration()
      configuration.allowedOrigins = arrayListOf("*").toMutableList()
      configuration.allowedMethods = arrayListOf("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH","OPTIONS")
      configuration.allowCredentials = true
      configuration.allowedHeaders = arrayListOf("Authorization", "Cache-Control", "Content-Type")
      val source = UrlBasedCorsConfigurationSource()
      source.registerCorsConfiguration("/**", configuration)
      return source
   }
}

In my controller:

   @RequestMapping("/test")
   @ResponseBody
   fun test(): String {
      return "success"
   }

In my call to the server I can verify that the authorization header is set as follows: Authorization: Bearer [Token]

What am I missing? I'm glad for any help!

Edit:

Spring Security Debug Log:

2018-02-13 15:37:37.594 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /maintenance/secure-test; Attributes: [authenticated]
2018-02-13 15:37:37.594 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055286a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@59b2: RemoteIpAddress: 192.168.1.4; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2018-02-13 15:37:37.595 DEBUG 13245 --- [io-10010-exec-3] o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
2018-02-13 15:37:37.595 DEBUG 13245 --- [io-10010-exec-3] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2018-02-13 15:37:37.595 DEBUG 13245 --- [io-10010-exec-1] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6b79755c, returned: -1
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied
        at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124) ~[spring-security-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) ~[spring-security-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
[...skipping full stack trace...]

2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.util.matcher.AndRequestMatcher   : Trying to match using NegatedRequestMatcher [requestMatcher=Ant [pattern='/**/favicon.ico']]
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/maintenance/secure-test'; against '/**/favicon.ico'
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.matcher.NegatedRequestMatcher  : matches = true
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.util.matcher.AndRequestMatcher   : Trying to match using NegatedRequestMatcher [requestMatcher=MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@40c8c1fa, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]]
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.m.MediaTypeRequestMatcher      : httpRequestMediaTypes=[application/json, text/plain, */*]
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.m.MediaTypeRequestMatcher      : Processing application/json
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.m.MediaTypeRequestMatcher      : application/json .isCompatibleWith application/json = true
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.u.matcher.NegatedRequestMatcher  : matches = false
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.util.matcher.AndRequestMatcher   : Did not match
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.s.HttpSessionRequestCache        : Request not saved as configured RequestMatcher did not match
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.a.ExceptionTranslationFilter     : Calling Authentication entry point.
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] o.s.s.w.a.Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access
2018-02-13 15:37:37.596 DEBUG 13245 --- [io-10010-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2018-02-13 15:37:37.597 DEBUG 13245 --- [io-10010-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

Keycloak debug (on Spring server):

2018-02-13 17:29:46.455 DEBUG 14194 --- [io-10010-exec-8] o.k.adapters.PreAuthActionsHandler       : adminRequest [URI]/maintenance/secure-test
2018-02-13 17:29:46.455 DEBUG 14194 --- [io-10010-exec-8] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /maintenance/secure-test
2018-02-13 17:29:46.455 DEBUG 14194 --- [io-10010-exec-8] o.k.a.AuthenticatedActionsHandler        : AuthenticatedActionsValve.invoke [URI]/maintenance/secure-test
2018-02-13 17:29:46.455 DEBUG 14194 --- [io-10010-exec-8] o.k.a.AuthenticatedActionsHandler        : Policy enforcement is disabled.
2018-02-13 17:29:46.461 DEBUG 14194 --- [io-10010-exec-9] o.k.adapters.PreAuthActionsHandler       : adminRequest [URI]/maintenance/secure-test
2018-02-13 17:29:46.462 DEBUG 14194 --- [io-10010-exec-9] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /maintenance/secure-test
2018-02-13 17:29:46.462 DEBUG 14194 --- [io-10010-exec-9] o.k.a.AuthenticatedActionsHandler        : AuthenticatedActionsValve.invoke [URI]/maintenance/secure-test
2018-02-13 17:29:46.462 DEBUG 14194 --- [io-10010-exec-9] o.k.a.AuthenticatedActionsHandler        : Policy enforcement is disabled.
2018-02-13 17:29:46.463 DEBUG 14194 --- [io-10010-exec-9] o.k.adapters.PreAuthActionsHandler       : adminRequest [URI]/maintenance/secure-test
2018-02-13 17:29:46.463 DEBUG 14194 --- [io-10010-exec-9] f.KeycloakAuthenticationProcessingFilter : Request is to process authentication
2018-02-13 17:29:46.463 DEBUG 14194 --- [io-10010-exec-9] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2018-02-13 17:29:46.467 DEBUG 14194 --- [io-10010-exec-9] o.k.a.BearerTokenRequestAuthenticator    : Verifying access_token
2018-02-13 17:29:46.572 DEBUG 14194 --- [io-10010-exec-9] o.k.a.rotation.JWKPublicKeyLocator       : Realm public keys successfully retrieved for client service-api. New kids: [omitted KID]
2018-02-13 17:29:46.573 DEBUG 14194 --- [io-10010-exec-9] o.k.a.BearerTokenRequestAuthenticator    : successful authorized
2018-02-13 17:29:46.577 DEBUG 14194 --- [io-10010-exec-9] a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: []
2018-02-13 17:29:46.578 DEBUG 14194 --- [io-10010-exec-9] o.k.adapters.RequestAuthenticator        : User ’test' invoking ‚[URI]/maintenance/secure-test' on client 'service-api'
2018-02-13 17:29:46.578 DEBUG 14194 --- [io-10010-exec-9] o.k.adapters.RequestAuthenticator        : Bearer AUTHENTICATED
2018-02-13 17:29:46.578 DEBUG 14194 --- [io-10010-exec-9] f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
2018-02-13 17:29:46.586 DEBUG 14194 --- [io-10010-exec-9] o.k.a.s.management.HttpSessionManager    : Session created: [omitted sessione ID]
2018-02-13 17:29:46.588 DEBUG 14194 --- [io-10010-exec-9] f.KeycloakAuthenticationProcessingFilter : Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken@bb340ce7: Principal: test; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount@68bb9634; Not granted any authorities
2018-02-13 17:29:46.588 DEBUG 14194 --- [io-10010-exec-9] o.k.a.AuthenticatedActionsHandler        : AuthenticatedActionsValve.invoke [URI]/maintenance/secure-test
2018-02-13 17:29:46.588 DEBUG 14194 --- [io-10010-exec-9] o.k.a.AuthenticatedActionsHandler        : Policy enforcement is disabled.

Upvotes: 2

Views: 8570

Answers (1)

Maurice Müller
Maurice Müller

Reputation: 1500

For the sake of completion I answer this question:

Like stated in a comment on the question, the problem was the call to super.configure(http) in KeycloakWebSecurityConfigurerAdapter.

So, if you encounter a similar error check if you make this call. The config should look something like this:

@KeycloakConfiguration
class MyConfig : KeycloakWebSecurityConfigurerAdapter() {
   // [...]
   @Throws(Exception::class)
   override fun configure(http: HttpSecurity) {
      super.configure(http) // this call was missing
      // [...]
   }
}

Upvotes: 3

Related Questions