Keycloak says 403 Forbidden for HTTP Methods other than GET - spring-boot

I`m setting up a Keycloak instance to work with spring boot app with spring security included. I use postman to test the service. I start with getting new access token and that works fine. When I do a HTTP GET call to secured endpoint - everything goes ok, staff is returned. But when I do a HTTP POST/PUT/DELETE call to secured endpoint Keycloak says Error 403 Forbidden. Please take a look and tell me what goes wrong.
I have already tested http.csrf().disable() option, and then works fine, but it is not the solution for production.
SecurityConfig.java
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers( "/api/**").hasRole("my_admin")
.anyRequest().permitAll();
}
application.yml:
keycloak:
auth-server-url: http://localhost:11080/auth
realm: myrealm
resource: myclient
public-client: true
principal-attribute: preferred_username
ssl-required: external
use-resource-role-mappings: true
Some logs from application (keycloak trace log level):
2019-04-01 14:04:54.741 DEBUG 2952 --- [io-1080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:1080/api/my-endpoint
2019-04-01 14:04:54.741 DEBUG 2952 --- [io-1080-exec-10] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /api/my-endpoint
2019-04-01 14:04:54.741 DEBUG 2952 --- [io-1080-exec-10] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:1080/api/my-endpoint
2019-04-01 14:04:54.741 DEBUG 2952 --- [io-1080-exec-10] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
2019-04-01 14:04:54.742 DEBUG 2952 --- [io-1080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:1080/error
2019-04-01 14:04:54.743 DEBUG 2952 --- [io-1080-exec-10] f.KeycloakAuthenticationProcessingFilter : Request is to process authentication
2019-04-01 14:04:54.743 DEBUG 2952 --- [io-1080-exec-10] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2019-04-01 14:04:54.743 TRACE 2952 --- [io-1080-exec-10] o.k.adapters.RequestAuthenticator : --> authenticate()
2019-04-01 14:04:54.743 TRACE 2952 --- [io-1080-exec-10] o.k.adapters.RequestAuthenticator : try bearer
2019-04-01 14:04:54.743 DEBUG 2952 --- [io-1080-exec-10] o.k.a.BearerTokenRequestAuthenticator : Found [1] values in authorization header, selecting the first value for Bearer.
2019-04-01 14:04:54.743 DEBUG 2952 --- [io-1080-exec-10] o.k.a.BearerTokenRequestAuthenticator : Verifying access_token
2019-04-01 14:04:54.743 TRACE 2952 --- [io-1080-exec-10] o.k.a.BearerTokenRequestAuthenticator : access_token: eyJhbs...blablab....signature
2019-04-01 14:04:54.744 DEBUG 2952 --- [io-1080-exec-10] o.k.a.BearerTokenRequestAuthenticator : successful authorized
2019-04-01 14:04:54.744 TRACE 2952 --- [io-1080-exec-10] o.k.a.RefreshableKeycloakSecurityContext : checking whether to refresh.
2019-04-01 14:04:54.744 TRACE 2952 --- [io-1080-exec-10] org.keycloak.adapters.AdapterUtils : useResourceRoleMappings
2019-04-01 14:04:54.744 TRACE 2952 --- [io-1080-exec-10] org.keycloak.adapters.AdapterUtils : Setting roles:
2019-04-01 14:04:54.744 TRACE 2952 --- [io-1080-exec-10] org.keycloak.adapters.AdapterUtils : role: my_admin
2019-04-01 14:04:54.744 DEBUG 2952 --- [io-1080-exec-10] a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: [my_admin]
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] o.k.adapters.RequestAuthenticator : User 'my_user' invoking 'http://localhost:1080/error' on client 'myclient'
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] o.k.adapters.RequestAuthenticator : Bearer AUTHENTICATED
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] f.KeycloakAuthenticationProcessingFilter : Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken#fb0506b7: Principal: my_user; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount#ecf147d; Granted Authorities: ROLE_my_admin
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:1080/error
2019-04-01 14:04:54.745 DEBUG 2952 --- [io-1080-exec-10] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.

you should do two followings config:
disable csrf in spring config http.csrf().disable()
define role in client instead of realm and assign user a client level role

I found out that I have not used CSRF token in ajax requests. Spring Security automatically enables CSRF protection. CSRF token is automatically generated for first call of web service and it has session scope. You need to save that token in meta tag. It is required to include CSRF token to every non-GET request (GET requests are not needed to be protected with CSRF token, beacuse they are designed to non-modifying API calls).
Solution: Just need to include that line in header of html page (thymeleaf):
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
and use it in ajax call in header:
headers: {
'X-CSRF-TOKEN': $('#_csrf').attr('content')
},

It is a CSRF protection provided by Spring Security. Do you really need this protection with Keycloak token? The token is only included in the cookie or it is also present in the Authorization header?

Related

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.

Keycloak integration with SpringDoc and spring boot

App stack: Spring boot / Spring Doc / Keycloak
I am trying to integrate the above stack together, everything works well till I am using keycloak policy enforcement.
At the bringing the app is running and I can access the Swagger UI served thanks to spring doc, but after like 20 or 30 seconds, I got HTTP ERROR 401 while I am trying to access the exact link of swagger and keycloak refuse all access to any URL or rest API
Console Error:
2022-05-22 23:31:55.650 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.650 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : checkCorsPreflight http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : Origin: null uri: http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : cors validation not needed as we are not a secure session or origin header was null: http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Policy enforcement is enabled. Enforcing policy decisions for path [http://localhost:8080/swagger-ui/index.html].
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Policy enforcement result for path [http://localhost:8080/swagger-ui/index.html] is : DENIED
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Returning authorization context with permissions:
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : Securing GET /error
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/error
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : checkCorsPreflight http://localhost:8080/error
2022-05-22 23:31:55.917 TRACE 92263 --- [io-8080-exec-10] f.KeycloakAuthenticationProcessingFilter : Did not match request to Or [Ant [pattern='/sso/login'], RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null], org.keycloak.adapters.springsecurity.filter.QueryParamPresenceRequestMatcher#1eab2ee7, org.keycloak.adapters.springsecurity.filter.AdapterStateCookieRequestMatcher#2ad76210]
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /error] with attributes [authenticated]
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
My policy enforcement to swagger links here is my keycloak YAML file
keycloak:
realm: phelix
auth-server-url: https://keycloak-server-url/auth
ssl-required: none
resource: orders
use-resource-role-mappings: true
bearer-only: true
cors-exposed-headers: X-Total-Count
cors: true
principal-attribute: preferred_username
credentials:
secret: 111-111-111.....
policy-enforcer-config:
enforcement-mode: ENFORCING
lazy-load-paths: true
paths:
- path: /swagger-ui/*.html
enforcement-mode: DISABLED
- path: /actuator/*
enforcement-mode: DISABLED
- path: /swagger-ui/*
enforcement-mode: DISABLED
- path: /swagger-ui.html
enforcement-mode: DISABLED
- path: /v3/api-docs/*
enforcement-mode: DISABLED
Security config class which extends keycloak adapter
#KeycloakConfiguration
#ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true)
#ConfigurationProperties(prefix = "keycloak")
#PropertySource(value = "classpath:keycloak-configs.yml", factory = YamlPropertySourceFactory.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private static final String[] WHITELIST_URLS = {
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/actuator/**",
};
public KeycloakClientRequestFactory keycloakClientRequestFactory;
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable()
.authorizeRequests()
.antMatchers(WHITELIST_URLS).permitAll()
.anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setPrefix("");
authenticationProvider.setGrantedAuthoritiesMapper(mapper);
auth.authenticationProvider(authenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy(); //for bearer-only services
}
#Bean
public KeycloakDeployment keycloakDeploymentBuilder(KeycloakSpringBootProperties configuration) {
return KeycloakDeploymentBuilder.build(configuration);
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken getAccessToken() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof KeycloakPrincipal) {
return ((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken();
} else {
return new AccessToken();
}
}
}
Dependencies:
'springboot': '2.7.0'
'keycloakVersion': '16.1.0'
'springdoc-openapi-ui': '1.6.8'
Any thoughts ?

Can't see keycloak login form

I'm trying to use Keycloak as SSO for Camunda and Spring app. I'm using this code:
https://github.com/camunda-consulting/code/tree/master/snippets/springboot-keycloak-sso/spring-security-and-springboot-adapter
I've made only two changes to this project:
First: Changed dependency of camunda keycloak plugin to:
<dependency>
<groupId>org.camunda.bpm.extension</groupId>
<artifactId>camunda-bpm-identity-keycloak</artifactId>
<version>2.0.0</version>
</dependency>
Second: Change of application properties (spring app is running 8081, keycloak 8080 for now):
keycloak:
realm: Workflow
auth-server-url: http://localhost:8080/auth
resource: camunda-identity-service
ssl-required: none
credentials.secret : f0fee400-1b19-4f41-a018-cdc5cc351b80
plugin.identity.keycloak:
keycloakIssuerUrl: https://localhost:8080/auth/realms/Workflow
keycloakAdminUrl: https://localhost:8080/auth/admin/realms/Workflow
clientId: camunda-identity-service
clientSecret: f0fee400-1b19-4f41-a018-cdc5cc351b80
useUsernameAsCamundaUserId: true
disableSSLCertificateValidation: true
After trying to get to spring app I'm redirected to login page but I'm getting "This connection has ben lost" in browser. I've enabled keycloak logs:
2021-01-26 11:44:38.686 DEBUG 19200 --- [nio-8081-exec-3] o.k.a.s.management.HttpSessionManager : Session created: EFE6D48E75B809FF544F1E051D8C18CD
2021-01-26 11:44:38.686 DEBUG 19200 --- [nio-8081-exec-3] k.a.s.a.KeycloakAuthenticationEntryPoint : Redirecting to login URI /sso/login
2021-01-26 11:44:38.695 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8081/sso/login
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /sso/login
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8081/sso/login
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8081/sso/login
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] f.KeycloakAuthenticationProcessingFilter : Request is to process authentication
2021-01-26 11:44:38.697 DEBUG 19200 --- [nio-8081-exec-2] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2021-01-26 11:44:38.701 DEBUG 19200 --- [nio-8081-exec-2] o.k.a.s.token.SpringSecurityTokenStore : Checking if org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator#4bfbad9a is cached
2021-01-26 11:44:38.702 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.OAuthRequestAuthenticator : there was no code
2021-01-26 11:44:38.702 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.OAuthRequestAuthenticator : redirecting to auth server
2021-01-26 11:44:38.703 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.OAuthRequestAuthenticator : callback uri: http://localhost:8081/sso/login
2021-01-26 11:44:38.704 DEBUG 19200 --- [nio-8081-exec-2] f.KeycloakAuthenticationProcessingFilter : Auth outcome: NOT_ATTEMPTED
2021-01-26 11:44:38.704 DEBUG 19200 --- [nio-8081-exec-2] o.k.adapters.OAuthRequestAuthenticator : Sending redirect to login page: http://localhost:8080/auth/realms/Workflow/protocol/openid-connect/auth?response_type=code&client_id=camunda-identity-service&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fsso%2Flogin&state=a1e3b3be-422f-48e4-98a4-262817ff4349&login=true&scope=openid
What am I'm doing wrong that I can't see login page?
EDIT 1:
I changed localhost to 127.0.0.1 and now I'm redirected to form but getting this message: Invalid parameter: redirect_uri . To solve this problem I used this thread -> keycloak Invalid parameter: redirect_uri

Why am I getting an invalid token response when exchanging an authorization code for an access token? (spring boot, oauth2, azure)

I am adding oauth into an application and I'm running into the following error:
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]
The project has a Spring Boot backend and an Eclipse rcp frontend. I'm attempting to authenticate using azure active directory as an authorization server. So far, I'm able to launch a browser widget in on startup of the eclipse application and successfully complete the authorization code request by pointing the browser to http://localhost:8080/oauth2/authorization/azure. After completing the authorization code request, the browser gets redirected to http://localhost:8080/login?error and displays the error above.
Dependencies from pom.xml
Built using spring boot with the following relevant dependencies:
spring-boot-starter-web v2.2.4
azure-active-directory-spring-boot-starter v2.2.1
spring-security-oauth2-client v5.2.1
spring-security-oauth2-jose v5.2.1
spring-security-oauth2-resource-server v5.2.1
Config from application.yml
We support multiple authorization servers, here is the fully configured azure client:
spring:
security:
oauth2:
client:
azure:
client-id: XXX
client-secret: XXX
client-name: Microsoft
scope: openid, https://graph.microsoft.com/user.read, profile
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/azure
client-authentication-method: basic
authentication-method: post
provider:
authorization-uri: https://login.microsoftonline.com/XXX/oauth2/v2.0/authorize
token-uri: https://login.microsoftonline.com/XXX/oauth2/v2.0/token
user-info-uri: https://graph.microsoft.com/oidc/userinfo
jwt-set-uri: https://login.microsoftonline.com/dXXX/discovery/v2.0/keys
azure:
activedirectory:
tenant-id: XXX
active-directory-groups: XXX
allow-telemetry: false
websecurityconfig.java
#Configuration
#EnableConfigurationProperties
#EnableWebSecurity
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
[...]
.anyRequest().authenticated()
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.oauth2Login();
}
[...]
}
Spring logs
Here is the complete stack trace from the moment I attempt to authenticate with my azure AD user credentials (shortened to fit body length requirements & with the authorization code censored ofc):
2020-02-19 16:10:33.925 DEBUG 19564 --- [qtp148813381-16] 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#2dd6e039
2020-02-19 16:10:33.925 DEBUG 19564 --- [qtp148813381-16] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2020-02-19 16:10:33.925 DEBUG 19564 --- [qtp148813381-16] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2020-02-19 16:10:33.928 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 1 of 16 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 2 of 16 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] w.c.HttpSessionSecurityContextRepository : HttpSession returned null object for SPRING_SECURITY_CONTEXT
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: Session#3a690b15{id=node01xqnw7l82ne041bil2flqsn3vr0,x=node01xqnw7l82ne041bil2flqsn3vr0.node0,req=1,res=true}. A new one will be created.
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 3 of 16 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 4 of 16 in additional filter chain; firing Filter: 'LogoutFilter'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', GET]
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login/oauth2/code/azure'; against '/logout'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', POST]
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /login/oauth2/code/azure' doesn't match 'POST /logout'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', PUT]
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /login/oauth2/code/azure' doesn't match 'PUT /logout'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', DELETE]
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /login/oauth2/code/azure' doesn't match 'DELETE /logout'
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.util.matcher.OrRequestMatcher : No matches found
2020-02-19 16:10:33.929 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 5 of 16 in additional filter chain; firing Filter: 'OAuth2AuthorizationRequestRedirectFilter'
2020-02-19 16:10:33.930 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login/oauth2/code/azure'; against '/oauth2/authorization/{registrationId}'
2020-02-19 16:10:33.930 DEBUG 19564 --- [qtp148813381-20] o.s.security.web.FilterChainProxy : /login/oauth2/code/azure?code=CODE&state=nqsFqxkkNzHJE5knQVdqFLjoPxg1MT_bcn7KzjKSFfU%3d&session_state=3ebe517e-d450-4d49-b8db-8afafe1fa37e at position 6 of 16 in additional filter chain; firing Filter: 'OAuth2LoginAuthenticationFilter'
2020-02-19 16:10:33.930 DEBUG 19564 --- [qtp148813381-20] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login/oauth2/code/azure'; against '/login/oauth2/code/*'
2020-02-19 16:10:33.930 DEBUG 19564 --- [qtp148813381-20] .s.o.c.w.OAuth2LoginAuthenticationFilter : Request is to process authentication
2020-02-19 16:10:33.930 DEBUG 19564 --- [qtp148813381-20] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider
2020-02-19 16:10:33.931 DEBUG 19564 --- [qtp148813381-20] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
2020-02-19 16:10:34.273 DEBUG 19564 --- [qtp148813381-20] .s.a.DefaultAuthenticationEventPublisher : No event was found for the exception org.springframework.security.oauth2.core.OAuth2AuthenticationException
2020-02-19 16:10:34.275 DEBUG 19564 --- [qtp148813381-20] .s.o.c.w.OAuth2LoginAuthenticationFilter : Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:148) ~[spring-security-o
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] .s.o.c.w.OAuth2LoginAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] .s.o.c.w.OAuth2LoginAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#2b0d857b
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] .a.SimpleUrlAuthenticationFailureHandler : Redirecting to /login?error
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] o.s.s.web.DefaultRedirectStrategy : Redirecting to '/login?error'
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] 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#2dd6e039
2020-02-19 16:10:34.277 DEBUG 19564 --- [qtp148813381-20] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2020-02-19 16:10:34.278 DEBUG 19564 --- [qtp148813381-20] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
[...]
Attemps to fix this error
I have tried all the solutions from this open issue: https://github.com/microsoft/azure-spring-boot/issues/526, including enabling oauth2AllowImplicitFlow in the azure portal manifest, to no avail.
If I print the authorization code on from the eclipse browser and create a token request to azure AD (with the azure postman collection) I get a successfull response with an bearer token.
So why am I getting a 401 Unauthorized when making my token request?
I would appreciate any suggestions on how to approach this problem. I'm desperatly looking for a solution and my next step is to try log the spring token request or inspect it with wireshark (going to have to decrypt the TLS connection since the endpoint for azure is https)
Thanks if you've read this far :)
As mentionned in a comment on #Jim Xu's answer, I resolved this issue by changing the azure endpoints from v2 to v1. This is done by changing the endpoints e.g.
http://login.microsoft.com/common/oauth2/v2.0/authorize becomes http://login.microsoft.com/common/oauth2/authorize as indicated in the v1 & v2 comparison.
For more info about v1 have a look at the docs
How to get oauth2 V2 to work
First, note that microsoft V1 login does not work with private accounts! Thus, downgrading may not be an option for everyone.
For successful V2 configuration:
I suggest using the discovery document - even if you dont use automated discovery, you can copy-paste the values:
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
In your case, the jwk-set-uri is set different than the json from above suggests, and should instead be:
https://login.microsoftonline.com/common/discovery/v2.0/keys
Below is the full spring provider.azure configuration key which I use:
authorization-uri: "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize"
token-uri: "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token"
user-info-uri: "https://graph.microsoft.com/oidc/userinfo"
jwk-set-uri: "https://login.microsoftonline.com/<tenant>/v2.0/keys"
user-name-attribute: "name"
user-info-authentication-method: "header"
Tested with spring-boot v2.1.2 and spring security 5.1.3.
According to my test, we can use the following code
My configuration file
spring:
security:
oauth2:
client:
registration:
azure:
client-id: xxx
client-secret: xxx
client-name: Azure
client-authentication-method: basic
provider: azure-oauth-provider
scope: openid, https://graph.microsoft.com/user.read, profile
redirect-uri: http://localhost:8080/login/oauth2/code/azure
authorization-grant-type: authorization_code
provider:
azure-oauth-provider:
authorization-uri: https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/authorize
user-info-uri: https://graph.microsoft.com/oidc/userinfo
token-uri: https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token
jwk-set-uri: https://login.microsoftonline.com/<tenant id>/v2.0/keys
user-name-attribute: name
azure:
activedirectory:
tenant-id: xxx
active-directory-groups: ***
WebSecurityConfig.java
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
}
}

Spring security login error page, access denied

When I login into my oauth2 protected form with invalid credentials, the redirect to the default login error page 'login?error' does not work. In my logs I can see:
2018-02-01 10:58:35.935 DEBUG 17600 --- [http-nio-8899-exec-8] w.a.UsernamePasswordAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
2018-02-01 10:58:35.935 DEBUG 17600 --- [http-nio-8899-exec-8] w.a.UsernamePasswordAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#49d1bcfd
2018-02-01 10:58:35.935 DEBUG 17600 --- [http-nio-8899-exec-8] .a.SimpleUrlAuthenticationFailureHandler : Redirecting to /login?error
2018-02-01 10:58:35.935 DEBUG 17600 --- [http-nio-8899-exec-8] o.s.s.web.DefaultRedirectStrategy : Redirecting to '/uaa/login?error'
But after the redirect there is an 'Access denied' exception:
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /login?error=; Attributes: [authenticated]
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#6fa90ed4: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#fffc7f0c: RemoteIpAddress: 127.0.0.1; SessionId: 03550F34462ABD6D42B5E224A4C478F9; Granted Authorities: ROLE_ANONYMOUS
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#337e3785, returned: -1
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] 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
Followed by a redirect to the login page again '/login'
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using Ant [pattern='/**', GET]
2018-02-01 10:58:35.943 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Request '/login' matched by universal pattern '/**'
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using NegatedRequestMatcher [requestMatcher=Ant [pattern='/**/favicon.ico']]
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/**/favicon.ico'
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = true
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using NegatedRequestMatcher [requestMatcher=MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager#27122376, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]]
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : httpRequestMediaTypes=[text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8]
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing text/html
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith text/html = false
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing application/xhtml+xml
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith application/xhtml+xml = false
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing image/webp
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith image/webp = false
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing image/apng
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith image/apng = false
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing application/xml;q=0.9
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith application/xml;q=0.9 = false
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing */*;q=0.8
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Ignoring
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Did not match any media types
2018-02-01 10:58:35.944 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = true
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = true
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : All requestMatchers returned true
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.s.HttpSessionRequestCache : DefaultSavedRequest added to Session: DefaultSavedRequest[http://localhost:8765/uaa/login?error=]
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
2018-02-01 10:58:35.948 DEBUG 17600 --- [http-nio-8899-exec-10] o.s.s.web.DefaultRedirectStrategy : Redirecting to 'http://localhost:8765/uaa/login'
Does someone have an idea whats going wrong?
Edit: Add security configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(-20)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
...
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**", "/images/**", "/fonts/**", "/health", "/info");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/console/**", "/reset").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access", "/reset")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
// #formatter:on
}
}
Another edit: When I access the oauth2 server directly without going trough Zuul, the redirect to the login error page 'login?error' page works.
Zuul's security configuration is
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.logout()
.permitAll()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
.and()
.authorizeRequests()
.antMatchers("/uaa/**", "/login", "/xxx/view3/**", "/*/view404", "/*/view403").permitAll()
.and()
.authorizeRequests()
.antMatchers("/xxx/**/*").hasAnyRole("USER", "ADMIN")
.antMatchers("/yyy/**/*").hasRole("ADMIN")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf().requireCsrfProtectionMatcher(csrfRequestMatcher()).csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
// #formatter:on
}
Edit: Added trace of requests
HAR-Export from Chrome DEV-Tools: Just share it
Just paste it in here to visualize: HAR viewer
The solution is to add
.antMatchers("/console/**", "/reset", "/login").permitAll()
to the WebSecurityConfigurerAdapter of the uaa-service. Final working configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/console/**", "/reset", "/login").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access", "/reset")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
// #formatter:on
}
If someone could explain why this is necessary, this would be nice.

Resources