Spring Security OIDC + WebFlux Calls Keycloak Every Time - spring-security

I have configured spring webflux with Open Id Connect with Keycloak as an IDP.
The problem is that for every call to my application, the oauth2 client does a call to keycloak instead of using the security session.
I have configured my webflux security as follows:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ServerLogoutSuccessHandler handler) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.securityContextRepository(new WebSessionServerSecurityContextRepository())
.oauth2Login(withDefaults());
return http.build();
}
I have a security context repository saved in the web session.
I have configured my web session in memory like so:
#Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
In my logs I can see that a security context has been found:
WebSessionServerSecurityContextRepository : Found SecurityContext 'SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [alex], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{sub=bdc6b386-623f-4fe4-a013-2c694678797b, email_verified=true, name=Aleksandar KIRILOV, preferred_username=alex, given_name=John, family_name=Doe, email=mymail#mail.com}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]]' in WebSession: 'org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionWebSession#5a17f06f'
Please help on how to avoid calling keycloak if the security context is still valid in the websession.
Best Regards !

a colleague of mine found the solution:
It seems that there is a clock skew protection in spring oauth2 client and I had set my access_token to expire after only one minute.
The clock skew protection was also set to one minute meaning that spring will preemptively go and refresh the token since we are close to expiration time.

Related

Spring Authorization server - limit a client to a specific resource server

I have two RegisteredClients(and two resource servers):
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient spa1 = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client1")
...
.redirectUri("http://127.0.0.1:3000/authorized")
...
.build();
RegisteredClient spa2 = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client2")
...
.redirectUri("http://127.0.0.1:3001/authorized")
...
.build();
return new InMemoryRegisteredClientRepository(spa1, spa2);
}
When I obtain access_token as client1, I can access endpoints in both resource servers.
I need client1 to be able to access only resourceServer1, and client2 to access resourceServer2.
I went through the docs and source code, but I cannot find a way to configure the client to be able to access only certain resource.
I think this was possible in spring security by doing:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
ClientDetailsServiceBuilder.ClientBuilder clientBuilder = clients
.inMemory()
.withClient("client1")
.resourceIds("resourceServer1")
...
Is it doable?
This is usually done by means of the aud claim in the access token.
The client will send the intended audience to the authorization server when it requests an access token, and each resource server must make sure to validate that the aud claim in the access token contains the resource server's ID.
If you use Spring Boot's Resource Server starter I think you can configure the audience ID with the spring.security.oauth2.resourceserver.jwt.audiences property (read more about it here.)
Otherwise, you'll have to look into configuring your own JwtDecoder with its own JwtValidator (which is what validates the JWT claims.)

How to configure Spring OAuth2 in JHipster for stateless authentication against GitLab?

I have set up an OAuth2-based installation (spring-security-oauth2-core 5.2.4) of JHipster (3.9.0) against a gitlab repository. Mainly following https://www.jhipster.tech/security/#oauth2 but using gitlab instead of keycloak.
This is an excerpt from the application.yml:
spring: ...
security:
oauth2:
client:
provider:
oidc:
issuer-uri: https://gitlab.mysite.com
registration:
oidc:
client-id: 1234123412322312312312312312321312321312
client-secret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Indeed the oauth2-authentication works well, until the session internally expires. When the client then returns the jsession-Id cookie, the underlying undertow-infrastructure throws a java.lang.IllegalStateException: UT000010: Session is invalid.
It seems that org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository.saveAuthorizationRequest stores the authorizationRequest in the user session not caring about session management configuration: (see last line below)
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request,
HttpServletResponse response) {
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
if (authorizationRequest == null) {
this.removeAuthorizationRequest(request, response);
return;
}
String state = authorizationRequest.getState();
Assert.hasText(state, "authorizationRequest.state cannot be empty");
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
authorizationRequests.put(state, authorizationRequest);
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
}
This approach seems to contradict the stateless JWT-based authentication approach of JHipster (see e.g. https://github.com/jhipster/generator-jhipster/issues/8627).
Anyway the spring2 OAuth2 management seems to counterfeit classical load balancing approaches.
Is there a way to configure OAuth2 management in spring to work stateless with GitLab?

Understanding Keycloak Adapter (Spring-Security & Spring Boot) session requirement

For a software in active development we are using Spring Boot (with Spring Security) and the Keycloak Adapter.
The goal is to:
require valid authentication for all endpoints except those annotated with #Public (see the code snippet) (this works)
the authentication must be via OAuth - the client gets a token directly from Keycloak and the Spring Security + Keycloak adapter make sure it is valid
optionally, Basic Auth is also supported (the Keycloak adapter can be configured to perform a login and make it appear like a regular token auth for the rest of the code) (this works as well)
Everything is working fine as it stands, but I have some problems understanding a few details:
The KeycloakWebSecurityConfigurerAdapter enables CSRF protection. I think this is only done so it can register its own Matcher to allow requests from Keycloak
It enables session management and requires some according beans
Even though requests are made with token authentication, a JSESSIONID cookie is returned
According to my understanding:
sessions should not be needed since stateless token authentication is used (so why is the KeycloakWebSecurityConfigurerAdapter enabling it). Is this only for the BASIC Auth part?
since sessions are enabled, CSRF protection is indeed needed - but I don't want the sessions in the first place and then the API would not need CSRF protection, right?
even if I set http.sessionManagement().disable() after the super.configure(http) call the JSESSIONID cookie is set (so where is this coming from?)
As stated in the code snippet, SessionAuthenticationStrategy is not set to the null once since we use the Authorization part of Keycloak and the application is a Service Account Manager (thus managing those resource records).
Would be great if someone can clear things up. Thanks in advance!
#KeycloakConfiguration
public class WebSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Inject private RequestMappingHandlerMapping requestMappingHandlerMapping;
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.requestMatchers(new PublicHandlerMethodMatcher(requestMappingHandlerMapping))
.permitAll()
.anyRequest()
.authenticated();
}
// ~~~~~~~~~~ Keycloak ~~~~~~~~~~
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
#Bean protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* {#link NullAuthenticatedSessionStrategy} is not used since we initiate logins
* from our application and this would not be possible with {#code bearer-only}
* clients (for which the null strategy is recommended).
*/
#Override
#Bean protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* HTTP session {#link ApplicationEvent} publisher needed for the
* {#link SessionRegistryImpl} of {#link #sessionAuthenticationStrategy()}
* to work properly.
*/
#Bean public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Override
#Bean public KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
return super.keycloakAuthenticationProvider();
}
}
You may fall into excessive JWT token usage. Look at this article for example https://blog.logrocket.com/jwt-authentication-best-practices/. Especially look at the references at the end of the article about JWT as a session token.
For your web-application UI you are using sessions in most of the cases. It doesn't matter what type of token is used for authentication. Keycloak does everything correctly - it gives back httpOnly secure cookie for session management and tracks user status at backend. For better understanding of how it works you may look at the example code here: examples
For better separation of stateless backend (micro-)services and user UI session keycloak documentation suggest to use 2 different authentication stratagies: RegisterSessionAuthenticationStrategy for sessions and NullAuthenticatedSessionStrategy for bearer-only services

Spring Security with oidc: refresh the tokens

Spring Boot 2 with Spring Security 5 can be configured to use an openID connect ID provider for authentication.
I managed to setup up my project just by configuring Spring Security - that works fine with all kinds of perfectly preconfigured security mechanisms like mitigation of session fixation.
But it seems that Spring Security does not refresh the tokens (which are stored in the session) by itself when they are expired.
Is there a setting for that or do I have to care for the refresh myself?
Update: Spring Boot 2.1 has been released, so it is time to revisit this problem. I still have no clue if the accessToken can now be automatically refreshed or if I have to write code for doing so...
According to the documentation,
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#webclient
When using a WebClient configured correctly, as given in the documentation it will automatically be refreshed.
Spring Security will automatically refresh expired tokens (if a refresh token is present)
This is also supported by the features matrix that refresh tokens are supported.
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix
There was an older blog on Spring Security 5 that gives you access to beans that you could do this manually,
Authentication authentication =
SecurityContextHolder
.getContext()
.getAuthentication();
OAuth2AuthenticationToken oauthToken =
(OAuth2AuthenticationToken) authentication;
There will be an OAuth2AuthorizedClientService automatically configured as a bean in the Spring application context, so you’ll only need to inject it into wherever you’ll use it.
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String refreshToken = client.getRefreshToken();
And, failing to find it right now, but I assume as part of the OAuth2AuthorizedClientExchangeFilterFunction has the calls to do a refresh.
According to https://github.com/spring-projects/spring-security/issues/6742 it seems that the token is intentionally not refreshed:
An ID Token typically comes with an expiration date. The RP MAY
rely on it to expire the RP session.
Spring does not. There are two enhancements mentioned at the end which should solve some of the refresh issues - both are still open.
As a workaround, I implemented a GenericFilterBean which checks the token and clears the authentication in the current security context. Thus a new token is needed.
#Configuration
public class RefreshTokenFilterConfig {
#Bean
GenericFilterBean refreshTokenFilter(OAuth2AuthorizedClientService clientService) {
return new GenericFilterBean() {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
token.getName());
OAuth2AccessToken accessToken = client.getAccessToken();
if (accessToken.getExpiresAt().isBefore(Instant.now())) {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
};
}
}
Additionally I had to add the filter to the security config:
#Bean
public WebSecurityConfigurerAdapter webSecurityConfigurer(GenericFilterBean refreshTokenFilter) {
return new WebSecurityConfigurerAdapter() {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(refreshTokenFilter, AnonymousAuthenticationFilter.class)
Implemented with spring-boot-starter-parent and dependencies in version 2.2.7.RELEASE:
spring-boot-starter-web
spring-boot-starter-security
spring-boot-starter-oauth2-client
I appreciate opinions about this workaround since I'm still not sure if such an overhead is really needed in Spring Boot.
even a bounty of 100 rep points did not yield an answer. So I guess there is currently no mechanism implemented to automatically refresh the access token with Spring Security.
A valid alternative seems to use the spring boot keycloak adapter which is capable of refreshing the token.

ProviderNotFoundException: No AuthenticationProvider found for ExpiringUsernameAuthenticationToken

We have developed an application using Spring Security and Spring SAML that works in our development environment where we use SSOCircle as our IDP. When we move into our customer's environment using their IDP, we are able to authenticate and navigate through the application without a problem as long as we do not pause. If the user pauses on a page for more than minute before submitting it, the application will redirect to the original landing page and the submitted data is lost.
The logs show:
o.s.s.w.a.ExceptionTranslationFilter
- Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.providers.ExpiringUsernameAuthenticationToken
Prior to this you the logs show at about every minute something similar to:
SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.providers.ExpiringUsernameAuthenticationToken#e6313ceb: Principal: REDACTED
We have been told by the customer that their IDP has a timeout of 60 seconds with a +-30sec skew time.
We asked them to temporarily adjust the IDP timeout to 30 minutes and our problem went away. When we go to production we must have the original setting of 60 seconds.
Our application is using the SAMLAuthenticationProvider:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(samlAuthenticationProvider());
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setForcePrincipalAsString(false);
samlAuthenticationProvider.setUserDetails(samlUserDetailsService);
return samlAuthenticationProvider;
}
How do we configure ExpiringUsernameAuthenticationToken to use this? And why does the original authentication work if it is not set?
And why is the application trying to re-authenticate when the IDP's session expires?
WebSSOProfileConsumerImpl and SingleLogoutProfileImpl both provide ways to set the responseSkew. Should this be set equal to, less than, or greater than the IDP's skew time?
Spring SAML by default observes the SessionNotOnOrAfter field provided by IDP in its SAML Response. This field tells that that once time hits the provided value user must be re-authenticated.
Spring SAML tries to re-authenticate user by sending the current Authentication object to AuthenticationManager, which tries to find an AuhenticationProvier which supports Authentiction object of such type (ExpiringUsernameAuthenticationToken in case of Spring SAML). In your case there is no such provider - that's why you see the ProviderNotFoundException exception. After this error Spring Security probably invokes the default EntryPoint, which redirects your user to the login page.
In order to ignore the SessionNotOnOrAfter value simply extend class SAMLAuthenticationProvider, override method getExpirationDate and make it return null. Then use your new class in the securityContext.xml.
But the correct solution is for your IDP to return SessionNotOnOrAfter value with a sensible session length - I wonder why they insist on using 60 seconds there.

Resources