How to use Spring WebSessionIdResolver with Spring Security 5 in a Spring webflux application? - spring

Currently I was experiencing the new Spring reactive stack, and want to use reactive features in Spring Session 2.0.
In traditional Servlet approach, Spring Session provides a HttpSessionStrategy to detect session in cookie or request headers. It is easy to use HeaderHttpSessionStrategy to implement a token like authentication(by default the he name is X-AUTH-TOKEN) for RESTful APIs.
Spring 5 core provides a WebSessionIdResolver to do the same thing for Reactive environment.
But when use it with Spring Security and wish it worked as traditional way, I can not get it work.
The SessionConfig file.
#EnableSpringWebSession
public class SessionConfig {
#Bean
public ReactorSessionRepository sessionRepository() {
return new MapReactorSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver headerWebSessionIdResolver() {
HeaderWebSessionIdResolver resolver = new HeaderWebSessionIdResolver();
resolver.setHeaderName("X-SESSION-ID");
return resolver;
}
}
The partial SecurityConfig.
#EnableWebFluxSecurity
class SecurityConfig {
#Bean
SecurityWebFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeExchange()
.pathMatchers(HttpMethod.GET, "/posts/**").permitAll()
.pathMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN")
//.pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
.anyExchange().authenticated()
.and()
.build();
}
A test rest controller file, it returns the current Session ID.
#RestController
public class SessionRestController {
#GetMapping("/sessionId")
public Map<String, String> sessionId(WebSession session){
Map<String, String> map = new HashMap<>();
map.put("id", session.getId());
return map ;
}
}
When I started up the application, and use curl to access the /sessionId, there is no session info the response header.
curl -v -u "user:password" http://localhost:8080/sessionId
And I got the session id in the query result and put it into request headers to access the protected resources and got 401.
curl -v -X POST -H "X-SESSION-ID:xxxx" http://localhost:8080/posts
Update: A working sample can be found here.

Spring Framework's spring-web module defaults to using it's CookieWebSessionIdResolver, which is based on cookies. If you create an alternative bean of type HeaderWebSessionIdResolver, it will get picked up automatically by Spring Session and switch to a header-based strategy.
In either strategy, it's geared to read the incoming ServerExchange headers, and look up the session id, whether that is reading the Cookie header or the SESSION http header.
These strategies also create response headers, whether that is a set-cookie directive for the client (web browser or your code) to populate the Cookie, or to give you the SESSION header (default name for the HeaderWebSessionIdResolver's header name).

Related

Spring Boot 3.0.2 adds "continue" query parameter to request URL after login

I just upgraded my project from Spring boot 2.7.7 to 3.0.2 and I'm seeing some weird behavior.
When I login to my application Spring adds "continue" query parameter to URL. It wasn't like this in 2.7.7. Is there something which I'm missing?
I use formLogin and have implemenatation of AuthenticationSuccessHandler, though it doesn't add any parameters.
This is covered in Spring Security's migration guide.
Basically, the query parameter was added to make so that Spring Security knows whether to query the session on that request or not. It is a performance optimization.
You can change the value by configuring the RequestCache like so:
#Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("mycustomparameter");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
Or, if you don't want this performance optimization, you can turn it off in the same way:
#Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName(null);
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}

Can I use both introspection server and local check for authorize token? Spring Boot - Security

I want to
introspect JWT token on remote server
and then check locally if scope/aud/iss/exp are correct
How can this be done most easily in Spring Boot?
As I understand first case is something similar to opauqeToken functionality (but I have normal JWT) and second case is more like using jwt
Spring Security only supports JWTs or Opaque Tokens, not both at the same time.
If I use opaqueToken, then validation on remote server is done without any effort (even if that's JWT)
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/api/**").hasAuthority("SCOPE_" + scope)
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)
));
return http.build();
I have scope verified. Now I want to check iss, aud, exp. Is that doable with opaqueToken?
Or should I use jwt auth instead?
IMHO opaqueToken can be JWT, so now the question is how to verify and inspect it locally after remote introspection?
It's kind of hybrid of two different approaches, but hopefully you know the simple way how to do it.
Ok, I think I have my answer. I created my own introspector which is implementing OpaqueTokenIntrospector
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector(
"introspect-url",
"client-id",
"client-secret"
);
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal introspected = this.delegate.introspect(token);
// SOME LOGIC
}
}
and I added it as a #Bean
#Bean
public OpaqueTokenIntrospector tokenIntrospector() {
return new JwtOpaqueTokenIntrospector();
}

Spring Cloud Gateway Oauth2Login Return JWT Token Instead of SESSION Cookie Upon Successful Login

sorry in advance if the question is previously asked, but I have not been able to find an answer.
I am trying to setup Spring Cloud Gateway to act as a OAuth2 client to authenticate/login users via a Keycloak Authentication server. I have been able to achieve this using the following code snipet:
Security Config:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
private final GatewayAuthenticationSuccessHandler gatewayAuthenticationSuccessHandler;
public SecurityConfig(GatewayAuthenticationSuccessHandler gatewayAuthenticationSuccessHandler) {
this.gatewayAuthenticationSuccessHandler = gatewayAuthenticationSuccessHandler;
}
#Bean
public SecurityWebFilterChain securityWebFilterChain(
ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
http
.authorizeExchange()
.pathMatchers("/ui/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2Login().authenticationSuccessHandler(gatewayAuthenticationSuccessHandler)
.and()
.oauth2ResourceServer().jwt();
http.logout(
logout ->
logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
http.logout().logoutUrl("/logout");
http.csrf().disable();
http.httpBasic().disable();
http.formLogin().disable();
return http.build();
}
}
Auth Success Handler:
#Component
public class GatewayAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
#Value("${my.frontend_url}")
private String DEFAULT_LOGIN_SUCCESS_URL;
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
URI url = URI.create(DEFAULT_LOGIN_SUCCESS_URL);
return this.redirectStrategy.sendRedirect(webFilterExchange.getExchange(), url);
}
}
With this setup, the gateway app can authenticate the users and obtain a JWT token from the authentication server on behalf of the caller (UI app). Based on my understanding, Spring security then uses spring session to create and feed back a SESSION cookie to the caller. This session cookie can be used for subsequent calls to authenticate the user. The gateway would use the SESSION cookie value to retrieve the associated JWT token from the cache and relays it to the downstream resource servers when proxying requests. I have also setup a token refresh filter to refresh the JWT token on the caller's behalf and a Redis ache to share this session cookie between multiple instances of the gateway.
What I would like to do now is to return the actual JWT token that was retrieved by the gateway back to the caller (instead of a SESSION cookie). In other words I am hoping to make my gateway a little more stateless by using JWT end-to-end (instead of using SESSION cookie for caller --> gateway and then JWT for gateway --> resource servers). Is this even possible with the current state of spring cloud gateway?
PS. I am using spring boot version 2.2.8 and spring cloud version HOXTON.SR6
Not sure this can help , but try to add a SessionPolicy as STATELESS to your webfilter chain as shown below , and it should work.
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Also you could try to override the sessionAuthenticationStrategy with a NullAuthenticatedSessionStrategy if you are extending your config class to WebSecurityConfigurerAdapter.
override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
return NullAuthenticatedSessionStrategy()
}

Spring Boot and JWT - JSESSIONID allows REST request without need for JWT?

I have implemented JWT authorization within my Spring Boot REST API using Auth0.
It is generally working as expected, however I have noticed a strange issue when testing in POSTMAN.
When I successfully authenticate any one request, e.g. a GET request using the Bearer JWT token from Auth0, I then get the following Cookie populated in all my other requests:
Now, with this JESSIONID cookie I am able to perform my other REST requests with no JWT Token?
Why is this? It does not seem secure, I would expect a JWT to need to be passed for every REST request?
My Spring SecurityConfig for reference:
/**
* Configures our application with Spring Security to restrict access to our API endpoints.
*/
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Override
public void configure(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http.cors().and().csrf().disable().authorizeRequests()
.mvcMatchers(HttpMethod.GET,"/users/**").authenticated()
.mvcMatchers(HttpMethod.POST,"/users/**").authenticated()
.mvcMatchers(HttpMethod.DELETE,"/users/**").authenticated()
.mvcMatchers(HttpMethod.PUT,"/users/**").authenticated()
.and()
.oauth2ResourceServer().jwt();
}
#Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
There are 4 ways to manage session in Spring Security,
always – a session will always be created if one doesn't already exist
ifRequired – a session will be created only if required (default)
never – the framework will never create a session itself but it will use one if it already exists
stateless – no session will be created or used by Spring Security
So looking at your configuration, it seems that your application is using 2nd option which is "ifRequired" and it creates the session and if request comes with existing sessionId, it allows the user to access the resource because that user is already authorized.
So if you want your application to be completely stateless and no session should be created, you should use the last option which is stateless.
So to make it stateless, change your configuration to,
http.cors().and().csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/users/**").authenticated()
.mvcMatchers(HttpMethod.POST, "/users/**").authenticated()
.mvcMatchers(HttpMethod.DELETE, "/users/**").authenticated()
.mvcMatchers(HttpMethod.PUT, "/users/**").authenticated().and().oauth2ResourceServer()
.jwt();
Hope this helps

Spring Boot 2.0 web flux custom authentication -- how to?

There are plenty of examples of minimal configurations of Spring Boot 2.0 security which compile or don't depending on which milestone or release candidate you try.
What is a minimal configuration that is not HTTP Basic, that will (1) let me access the HTTP request (headers, cookies, etc.) and also call my own authentication manager?
I would like to look at the headers and cookies, and decide from those who the user is, and whether or not the user is authenticated. How I do that should not matter to this answer -- the question is, what is the minimal Spring security config in order to allow me to hook in to the security infrastructure, so that my authentication is there in the reactive endpoints?
EDIT:
This works with Spring Boot 2.0.0.RC2, so my question could be, is this a correct way to introduce custom authentication into Spring Security?
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(authentication -> {
authentication.setAuthenticated(true);
return Mono.just(authentication);
});
authenticationFilter.setAuthenticationConverter(serverWebExchange ->
Mono.just(new AbstractAuthenticationToken(new ArrayList<>()) {
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getPrincipal() {
return "jim";
}
}));
return http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN)
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
}
You can imagine that in the converter, I am free to look into the request by way of serverWebExchange and inspect any headers or cookies I wish, and that later in the upper lambda (standing in for ReactiveAuthenticationManager) I can actually decide whether or not it should be authenticated.

Resources