How to read/write session attribute in Spring Cloud Gateway? - java

I'm trying to persist user in http session and verify authentication request inside Gateway by using a custom filter. I found a similar question too:
SecurityConfig:
#Configuration
public class SecurityConfig {
#Bean
public SecurityWebFilterChain securityWebFilterChain(
ServerHttpSecurity http,
MyAuthenticationFilter myAuthenticationFilter
) {
http
.csrf()
.disable()
.authorizeExchange()
.pathMatchers("/**")
.permitAll()
.and()
.addFilterAt(myAuthenticationFilter, SecurityWebFiltersOrder.FIRST); // custom filter
return http.build();
}
MyAuthenticationFilter:
#Component
public class MyAuthenticationFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getSession().map(
session -> {
session.getAttributes().put("userId", "id123");
// It does not print anything
System.out.println("userId in session: " + session.getAttribute("userId"));
return session;
}
);
return chain.filter(exchange);
}
}
By adding a custom filter, and attempting to read/write session attribute, as I observed in debug mode, the function inside map() never gets executed, and nothing gets print out in the terminal. (Unsurprisingly, downstream service cannot read userId from session even though both gateway and service share the same session).
Why is it not working? Here's a minimal reproduced version: Github repo, please take a look.

Found a workaround:
By setting up a servlet application as a downstream service that does authorization, and then reading/writing session inside the "authorization service" will be relatively easier (since the gateway and the direct downstream service will be sharing the same session).
As to the whole authentication/authorization part in this microservice architecture, I found out that using JWT is more preferable, and it's recommended that it should be stateless in between services, while gateway can be stateful, so as to share session with the said "authorization service".
A great answer explaining JWT implementation visually.

Related

How to specific different authentications in different urls in springboot in my situation?

i have backend urls for some service to access, and frontend urls for website login to access, my situation is:
/backend/**: HTTPS two-way authentication
/frontend/**: HTTPS one-way authentication and token authentication
I don't want to start two different springboot process.
I have found this answer but springboot not allow to disable client-auth for specific urls:
Spring Boot: Disable Client Auth for specific URL
server:
ssl:
client-auth: need
and this answer maybe helpful, but i don't know how to mix two authentication method in my situation.
How set up Spring Boot to run HTTPS / HTTP ports
please help.
For Spring-Boot 2.7.0 this should be as simple as defining 2 instances of SecurityFilterChain, idealy you'd want one of them to be the default (remove the http.mvcMatcher line) and give the other #Order(1).
Incase of older implementations i'm not 100% sure, for further research you probably find better results looking for a way to support 2 login method depending on endpoint than looking into how to disable certain elements.
#Configuration
public class WebSecurityConfig
{
#Bean
public SecurityFilterChain frontendFilterChain(HttpSecurity http) throws Exception
{
//#formatter:on
http
.mvcMatcher("/frontend/**")
.authorizeRequests(auth -> auth.anyRequest().permitAll());
//Extend with needed authentication
//#formatter:off
return http.build();
}
#Bean
#Order(1)
public SecurityFilterChain backendFilterChain(HttpSecurity http) throws Exception
{
//#formatter:on
http
.mvcMatcher("/backend/**")
.authorizeRequests(auth -> auth.anyRequest().permitAll());
//Extend with needed authentication
//#formatter:off
return http.build();
}
}
```

Spring custom security check in SecurityConfiguration

I'm currently working on an application with a lot of REST endpoints to manage all kinds of things related to a company (stock, sales, deliveries, orders, ...).
For almost every call to the API I pass a RequestHeader containing the company ID. Then I then need to check if the logged in user is a member of that company. I already store these companyIds in the SecurityContext, so a check isn't that hard per se.
My question is now, what is a good approach to implement this verification. Ideally I handle this in the SecurityConfigurationConfig with a custom check, like this:
.antMatchers("/api/**").authenticated().isMemberOfCompany()
But I'm not able to figure out how to do this. The check would need to read out a request header, and verify it with a companies array in the SecurityContext.
Thanks.
I think the best approach is to create a custom web filter with your authorization logic and add it to the http security configuration, like this :
.antMatchers("/api/**")
.authenticated()
.and()
.addFilterAfter(new CustomFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
The custom filter should implement the WebFilter interface :
public class CustomFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
final SecurityContext context = SecurityContextHolder.getContext();
// authorization logic
}
}

Multiple authentication types in Webflux

We have an API service which has multiple APIs exposed, and there are multiple personas who/which can access our service.
Users - Who needs to have an account in our system -> Needs to be
authenticated with our Identity Provider Service (Keycloak) with JWT
token.
Regulated System - Which needs to be authenticated with central
authority maintained by some party.
Internal service to service communication -> authentication with same
Keycloak.
Temporary JWT token issued by the same service before creating the user
account when the user digitally verified the mobile number.
I was trying to have AuthenticationWebFilter for each authentication type, and configure with Pathmatchers, though it was getting authenticated by the right authentication web filter, the request keeps flowing through the other authentication filter, and ends up resulting as unauthorized.
Snippet of configuration:
public class Configuration {
#Bean
#Order(1)
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity,
#Qualifier("userCreationFilter")
AuthenticationWebFilter userCreationFilter) {
final String[] WHITELISTED_URLS = {"/**.json",
"/users/verify",
"/users/permit",
"/sessions",
"/internal/xxxxx/**",
"/**.html",
"/**.js",
"/**.yaml",
"/**.css",
"/**.png"};
httpSecurity.authorizeExchange().pathMatchers(WHITELISTED_URLS).permitAll();
httpSecurity.addFilterBefore(userCreationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/users")
.authenticated();
httpSecurity.httpBasic().disable().formLogin().disable().csrf().disable().logout().disable();
return httpSecurity.build();
}
#Bean
#Order(2)
public SecurityWebFilterChain securityWebFilterChain2(ServerHttpSecurity httpSecurity,
#Qualifier("managerFilter")
AuthenticationWebFilter managerFilter) {
httpSecurity.addFilterBefore(managerFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/xxxxx/**",
"/providers",
"/xxxxx/**/approve",
"/xxxx/**/xxxxx").authenticated();
return httpSecurity.build();
}
}
Right now there are no roles as such we have.
I tried keeping all configuration in single SecurityWebFilterChain Bean, and tried addWebFilterAt, but no luck.
What am I missing? Should I do it different way?

Spring Boot - how to conditionally enable/disable sessions

I've built a REST API service using Spring where I've enabled sessions using MongoDB:
#Configuration
#EnableMongoHttpSession(maxInactiveIntervalInSeconds = Globals.SESSION_MAX_INTERVAL)
public class SessionConfig {
#Bean
public AbstractMongoSessionConverter createSessionConverterBean() {
return new JacksonMongoSessionConverter(Collections.singletonList(new GeoModule()));
}
}
I would however, like to have control over which connections should be issued a session. Currently, every HTTP request has a session generated for it, but there are scenarios where the session is not needed, and I'd prefer not to clutter up the session storage with session objects that will never be used.
One such scenario is a standalone desktop application that acts as a content management system. This application has no need for HTTP sessions because authentication is done via the application side via a custom authorization header. This application also only accesses endpoints from a certain root route mapping:
Public traffic routes to api.domain.com/pub and the CMS traffic routes through api.domain.com/cpi.
It would be nice to be able to tell Spring that it does not need to create a session for any requests coming to /cpi. The desktop application also provides a unique Origin that I can match as well if that is more easily done.
My Web security looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.cors()
.and()
.httpBasic();
http.csrf().disable(); // Self-implemented
}
I've searched all over and haven't found a thing. Can anyone point me in the right direction?
Thanks!
You could add multiple security configuration in the following scheme. Where one is explicitly matching for the all /cpi requests and the other one handling the remaining requests.
You could also configure different authentication methods this way.
#Order(1)
#Configuration
public static class Custom1WebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
http
.antMatcher("/cpi/**")
.authorizeRequests()
...
http.sessionManagement() // dont create a session for this configuration
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Order(2)
#Configuration
public static class Custom2WebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
http
.authorizeRequests()
...
}
You could try below in application.yml file.
server:
servlet:
session:
persistent: false
timeout: 0

Spring session creation policy per-request?

TL;DR
Is it possible to control the session creation policy in Spring (Security) on a per request basis?
Long version...
I have been using normal login form user authentication for our application.
Some of the controllers are #RestControllers and up to now, the default user session tracked by cookie has allowed it to work fine.
(I.e. when an XHR request comes from a page, the request is authenticated to the previously logged in user as the browser sends the JSESSIONID cookie as usual)
I now want to allow some of the #RestController end points to be called from a rest client, rather than browser, so I have created an API token authentication scheme - this works fine.
One of the last bits of cleanup is that the REST calls generate a session, which I'd like to avoid if possible.
I can't set the session policy to NEVER (because i'm still relying on sessions for my web users).
I have tried IF_REQUIRED to no avail.
I have looked at the HttpSessionSecurityContextRepository but it wraps the request, and a session is created whenever the response is flushed.
(See stacktrace below)
Is it possible elsewhere to hook into the session management on a per-request basis?
I can distinguish the type of request easily enough based on the class type of the Authentication object.
at myapp.cfg.WebConfig$1.sessionCreated(WebConfig.java:74)
at io.undertow.servlet.core.ApplicationListeners.sessionCreated(ApplicationListeners.java:300)
at io.undertow.servlet.core.SessionListenerBridge.sessionCreated(SessionListenerBridge.java:56)
at io.undertow.server.session.SessionListeners.sessionCreated(SessionListeners.java:52)
at io.undertow.server.session.InMemorySessionManager.createSession(InMemorySessionManager.java:187)
at io.undertow.servlet.spec.ServletContextImpl.getSession(ServletContextImpl.java:741)
at io.undertow.servlet.spec.HttpServletRequestImpl.getSession(HttpServletRequestImpl.java:370)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:270)
at org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper.createNewSessionIfAllowed(HttpSessionSecurityContextRepository.java:427)
at org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper.saveContext(HttpSessionSecurityContextRepository.java:364)
at org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper.onResponseCommitted(SaveContextOnUpdateOrErrorResponseWrapper.java:85)
at org.springframework.security.web.util.OnCommittedResponseWrapper.doOnResponseCommitted(OnCommittedResponseWrapper.java:245)
at org.springframework.security.web.util.OnCommittedResponseWrapper.access$000(OnCommittedResponseWrapper.java:33)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:512)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:513)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1050)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:953)
Split your security configuration into separate sections for a form login (session based API access) and a stateless API token authentication scheme.
Example:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Order(1)
#Configuration
class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic().realmName("API") // your API token authentication scheme
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.exceptionHandling().authenticationEntryPoint(new Http401AuthenticationEntryPoint("Form realm=\"API\"")); // prevent basic authentication popup in browser
}
}
#Order(2)
#Configuration
class DefaultSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login").permitAll();
}
}
}
Replace .httpBasic().realmName("API") with you own authentication scheme.
Call your API with e.g. curl -v ... and verify that there is no Set-Cookie header in the response. Otherwise your code somewhere creates an http session on its own.
You should try create-session policy as "stateless" for your API end points.
If "stateless" is used, this implies that the
application guarantees that it will not create a session. This differs from the use of
"never" which mans that Spring Security will not create a session, but will make use of
one if the application does.
I had the exact same problem and could not find a clean solution. In the absence of better options, I'll post a semi working hack.
DISCLAIMER: I have not used this solution (I fell back to sessions, at least for now), try it at your own risk.
Override the default SecurityContextRepository:
#Component
public class CustomSecurityContextRepository extends HttpSessionSecurityContextRepository {
#Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext securityContext = super.loadContext(requestResponseHolder);
// disable automatic saving of security context on response committed
// WARNING: not sure how safe this is
SaveContextOnUpdateOrErrorResponseWrapper response =
(SaveContextOnUpdateOrErrorResponseWrapper)requestResponseHolder.getResponse();
response.disableSaveOnResponseCommitted();
return securityContext;
}
#Override
public void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response) {
Authentication authentication = context.getAuthentication();
// call super.saveContext according to your use case
}
}
Finally, register this class in the WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.securityContext().securityContextRepository(customSecurityContextRepository);
}
If anyone has a better solution I would be interested in hearing it.

Categories