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

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

Related

Spring Boot securing same endpoint with CSRF token and BasicAuth

I have a Spring Boot REST application which has two main parts:
UI where I want to protect the ajax calls with a token
public endpoints where I want to have Basic Auth
As far as I understand I can't protect the public endpoints with CSRF tokens, as these need a session. The problem is, some endpoints need to be reachable by both, so how can I protect them with CSRF when it is used by the UI and disable CSRF for Basic Auth?
Here is what I currently have, where I disable csrf completely so basic works...
http.requestMatchers().antMatchers("/form/fill", "/form/fill/*", "/form/fillParams", "/form/fillParams/*").and()
.csrf().disable().authorizeRequests().anyRequest().hasAnyRole(SecurityConfiguration.ROLE_FORMS_AUTHOR,
SecurityConfiguration.ROLE_FORM_FILLER, SecurityConfiguration.ROLE_ADMIN)
.and().httpBasic();
EDIT: I found this old answer and I wonder if there is a way I can leverage this for my case, but I'm still not sure how to distinguish between a "local" user and one that is authenticated with httpBasic()
In your Spring Security java configuration file you can configure the HttpSecurity object as follows in order to enable the CSRF check only on some requests (by default is enabled on all the incoming requests, and disable will disable for all incoming request so request Mather can help here for path you want to enable or disable csrf.).
Make sure to replace /urls-with-csrf-check/** with your paths by end point or multiple paths..
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private RegexRequestMatcher requestMatcher =
new RegexRequestMatcher("/urls-with-csrf-check/**", null);
public boolean matches(HttpServletRequest httpServletRequest) {
if (requestMatcher.matches(httpServletRequest)) {
return true;
}
return false;
}
};
http.requestMatchers().antMatchers("/form/fill", "/form/fill/*", "/form/fillParams", "/form/fillParams/*").and()
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests().anyRequest().hasAnyRole(SecurityConfiguration.ROLE_FORMS_AUTHOR, SecurityConfiguration.ROLE_FORM_FILLER, SecurityConfiguration.ROLE_ADMIN)
.and().httpBasic();
}
With the input from #kj007, I was able to get this working.
I am using the requireCsrfProtectionMatcher and this is how my matcher looks like:
public class UIRequestMatcher implements RequestMatcher {
public static final List<GrantedAuthority> USER_ROLES = new ArrayList<>();
static {
USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_ADMIN));
USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_FILES_AUTHOR));
USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_FORMS_AUTHOR));
USER_ROLES.add(new SimpleGrantedAuthority(SecurityConfiguration.ROLE_TEMPLATES_AUTHOR));
}
#Override
public boolean matches(HttpServletRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "POST".equals(request.getMethod()) && auth.getAuthorities().stream().anyMatch(USER_ROLES::contains);
}
}
So I am checking if the Authentication has any of my user roles, as my basic auth should only be used for my technical users.

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.

How do I dynamically specify OAuth2 resource details in Spring Security?

I'm creating an application integrating with Shopify's API, which uses OAuth2 for authentication and authorization. Using the tutorial for Spring Security OAuth2, and the tutorial for Shopify, I've been able to get integration working with a single shop. The YAML configuration looks like this:
shopify:
shop: myshop
scopes: read_customers,read_orders
security:
oauth2:
client:
clientId: myclientid
clientSecret: mysecret
tokenName: access_token
authenticationScheme: query
clientAuthenticationScheme: form
accessTokenUri: https://${shopify.shop}.myshopify.com/admin/oauth/access_token
userAuthorizationUri: https://${shopify.shop}.myshopify.com/admin/oauth/authorize?scope=${shopify.scopes}&grant_options[]=
pre-established-redirect-uri: https://myapp/login
registered-redirect-uri: https://myapp/login
use-current-uri: false
resource:
userInfoUri: https://${shopify.shop}.myshopify.com/admin/shop.json
However, this static configuration won't work for an app published in Shopify's App Store because the redirect, access token, user info, and user authorization URIs depend on the shop name. There are examples of using more than one provider, but they still have to be static.
To allow these URI's to be dynamic, I've come up with a few possible options:
Use a parameter in the /login path to identify the shop, then create a filter that adds the shop name to a ThreadLocal that runs before everything else, then dynamically create the AuthorizationCodeResourceDetails that is needed by the OAuth2 filter via a Spring proxied factory bean.
Use a sort of "metafilter" that dynamically recreates the OAuth2ClientAuthenticationProcessingFilter per request along with all of the resources that it needs.
Override OAuth2ClientAuthenticationProcessingFilter so that it can handle recreating the RestTemplate it needs to obtain the access token.
All of these options seem pretty difficult. What's a good way to handle dynamically-generated URI's for access tokens and user information in Spring Security OAuth2?
Also, since I'm new to OAuth2 in general, do I need to enable a Resource Server in my Spring configuration to protect my app with the access token?
a bit late but I did return a dynamic url for an oauth resource by overriding the getter for the Oauth2ProtectedResource
#Bean(name = "googleOauthResource")
public BaseOAuth2ProtectedResourceDetails getGoogleOauthResource() {
final AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails() {
#Override
public String getPreEstablishedRedirectUri() {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
final HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
return request.getRequestURL() + "?" + request.getQueryString() + "&addStuff";
}
return super.getPreEstablishedRedirectUri();
}
};
details.setId("google-oauth-client");
details.setClientId("xxxxxxxxxxx");
details.setClientSecret("xxxxxxxx");
details.setAccessTokenUri("https://www.googleapis.com/oauth2/v4/token");
details.setUserAuthorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
details.setTokenName("authorization_code");
details.setScope(Arrays.asList("https://mail.google.com/,https://www.googleapis.com/auth/gmail.modify"));
details.setPreEstablishedRedirectUri("http://localhost:8080/xxx-api-web/v2/gmail"); //TODO
details.setUseCurrentUri(false);
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setGrantType("authorization_code");
return details;
}
I'm having the same problem you are and I'm leaning toward your first theory of using ThreadLocal storage. Here is how I'll probably go about my solution:
Set values from ServletRequest in LocalThread storage by overriding methods in OAuth2ClientAuthenticationProcessingFilter:
attemptAuthentication
successfulAuthentication
unsuccessfulAuthentication
requiresAuthentication
Then translate the URI's within the OAuth2RestTemplate by overriding the followign methods:
createRequest
doExecute
appendQueryParameter
I'll probably have to make my own #Bean for the RestTemplate that has an injected #Service that will lookup the dynamic Shopify domain.
I'll post my solution if it works.

Spring Session and Spring Security

I have questions on the following areas: spring-session and spring-security.
Spring Session
I have a application protected with Spring Security through basic in-memory authentication as provided in the example sample.
I see spring is creating session id's even the authentication is not successful, meaning I am seeing x-auth-token in my response header as well in the Redis DB even if I don't supply basic authentication credential details.
How do we avoid creating sessions for authentication failures?
Spring Security
Want to use spring security to protect resources assuming spring session creates session only for the protected resources.
Assuming a Signin API (/signin - HTTP Post) validates (username & password) credentials against a third-party REST API .
Once the external API validates the credentials, how do I update the spring security context on the successful authentication?
Access to other secured resources with the x-auth-token needs to be validated and based on the information access to the secured resource should be provided.
Do we need to have Spring Security in this case or shall I use a basic filter and spring session? What is recommended?
Typically it would be best to break your questions into multiple StackOverflow questions since you are more likely to find someone that knows the answer to a single question than both.
How do we avoid creating sessions for authentication failures ?
By default Spring Security will save the last unauthenticated request to session so that after you authenticate it can automatically make the request again. For example, in a browser if you request example.com/a/b/c and are not authenticated, it will save example.com/a/b/c to the HttpSession and then have the user authenticate. After you are authenticated, it will automatically give you the result of example.com/a/b/c. This provides a nice user experience so that your users do not need to type the URL again.
In the case of a REST service this is not necessary since the client would remember which URL needs to be re-requested. You can prevent the saving by modifying the configuration to use a NullRequestCache as shown below:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
You can provide custom authentication by providing your own AuthenticationProvider. For example:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = (String) token.getCredentials();
// validate making REST call
boolean success = true;
// likely your REST call will return the roles for the user
String[] roles = new String[] { "ROLE_USER" };
if(!success) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(roles));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
You can then configure your RestAuthenticationProvider using something like this:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
public RestAuthenticationProvider restAuthenticationProvider() {
return new RestAuthenticationProvider();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, AuthenticationProvider provider) throws Exception {
auth
.authenticationProvider(provider);
}
}
Session IDs are getting stored in Redis even when authentication fails.
Rob's answer of setting NullRequestCache didn't work for me. That is, there were redis entries even after setting request cache to NullRequestCache. To make it work, I had to use an authentication failure handler.
http.formLogin().failureHandler(authenticationFailureHandler()).permitAll();
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler();
}
public class AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
}
Note that the failure handler does nothing but extend the default handler explicitly. It returns a 401 in case of failure. If you were doing redirects, you can configure it in the handler easily.

Change Bearer-Token-Type in Spring Cloud OAuth

I'm using Spring Cloud OAuth, I'm using the official sample from https://github.com/spring-cloud-samples/sso/ with GitLab (https://about.gitlab.com/) as OAuth Provider.
The Problem is that GitLab sends a Token of the Type "bearer" and Spring Cloud SSO retrieves the token and sends a Header in the form of
Authorization bearer xxxxxxx
which is rejected by the GitLab Server cause according to the documentation it only accepts Tokens in the form of
Authorization Bearer xxxxxxx.
This is probably a bug in the GitLab Server but is there any way to work around this problem with Spring Cloud SSO.
Update 19.03.:
This is what I've tried in SsoApplication.java of the SpringCloud-sample.
#Autowired
private OAuth2RestTemplate oAuth2RestTemplate;
#PostConstruct
private void modifyOAuthRestTemplate() {
this.oAuth2RestTemplate.setAuthenticator(new OAuth2RequestAuthenticator() { // this line gets called
#Override
public void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext, ClientHttpRequest request) {
// this line is never called
}
});
}
instead of the newly Injected OAuth2ProtectedResourceDetails the "original" one gets called. Everytime I try to authenticate in the sample app
UPDATE: there is a callback in Spring Cloud if you define a bean of type UserInfoRestTemplateCustomizer you will get an instance of OAuth2RestTemplate during startup where you can apply customizations (e.g. an OAuth2RequestAuthenticator to change "Bearer" to "bearer").
The workaround I have seen others use is to #Autowired the OAuth2RestTemplate and modify its OAuth2RequestAuthenticator in a #PostConstruct (before it is used anyway).

Resources