spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.82k stars 5.9k forks source link

Session Creation Policy with Webflux Security #6552

Closed tine2k closed 5 years ago

tine2k commented 5 years ago

I am developing a reactive Spring Boot application with Spring Cloud Gateway and Spring Security using only Webflux and no Spring MVC (SB 2.1.3 and Greenwich.RELEASE).

I want my application NOT to create any session cookies. In a Spring MVC application this is achievable with httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

Is there an equivalent for Webflux Security? ServerHttpSecurity does not seem to offer this.

Xarvalus commented 5 years ago

While seeking for currently possible solution there are some workarounds posted on StackOverflow: Disable WebSession creation when using spring-security with spring-webflux.

Involving WebSessionManager methods overriding & manual SecurityWebFilterChain functionality disabling.

Would be very useful to provide the similar solution based on Spring MVC's one.

rwinch commented 5 years ago

Can you describe in more detail what you are trying to do (i.e. what is creating the session, if it is authentication how are you authenticating, etc) and why (i.e. why disable sessions)?

Xarvalus commented 5 years ago

@rwinch you have detailed information on the request from @tine2k comment & from mentioned SO question link

Common scenario in Spring is to disable session creation with SessionCreationPolicy.STATELESS when the app is using JWT.

We would love to see that possibility in Spring Webflux too, without unnecessary workarounds.

rwinch commented 5 years ago

WebFlux works differently that the imperative equivalent. This means that JWT does not create a session on authentication by default.

Xarvalus commented 5 years ago

According to OP of the SO question linkebon ~ Disable WebSession creation when using spring-security with spring-webflux it seems to not be disabled by default. Feel free to post your solution up there.

rwinch commented 5 years ago

Can you please provide a sample that reproduces the issue?

spring-projects-issues commented 5 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

spring-projects-issues commented 5 years ago

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

msassak commented 5 years ago

I believe I just ran into the same issue. I was able to work around it with the info in the SO post linked from here, but I've also created a sample demonstrating what I am seeing in the hopes that we can get a fix for this (or just some enlightenment about a better solution).

The problem seems to occur when Spring Security is enabled in Spring Cloud Gateway, and a client sends a cookie named SESSION to a service behind Cloud Gateway. The gateway forwards the session cookie as expected but Spring Security also adds a session cookie expiration header to the response, presumably because the cookie on the request is not recognized by it.

Implementing a null WebSessionManager fixes this, but it does seem like a setting is missing, perhaps in ServerHttpSecurity.

I've attached a zip file containing a minimal configuration for reproducing. It contains Spring Cloud Gateway with a single route that forwards requests to httpbin.org.

$ unzip gateway-webflux-session.zip
$ cd gateway-webflux-session
$ ./mvnw spring-boot:run

In another terminal, first send a request directly to httpbin:

$ curl -v -b 'SESSION=sessionid' https://httpbin.org/get?foo=bar

Note the absence of a set-cookie header in the response. Now send the identical request through the gateway:

$ curl -v -b 'SESSION=sessionid' http://localhost:8080/get?foo=bar

The response from the gateway will include this header:

set-cookie: SESSION=; Max-Age=0; Expires=Tue, 23 Jul 2019 21:55:28 GMT; Path=/; HTTPOnly

Which causes the browser to incorrectly expire the cookie.

So far as I can tell, this only occurs when the cookie is named SESSION. gateway-webflux-session.zip

rwinch commented 5 years ago

What makes you believe this is a Spring Security issue vs WebSessionManager (Spring issue) or a Gateway issue?

msassak commented 5 years ago

Good question. I realized when you asked that I wasn't sure myself, so I took the example from my original response and removed all the dependencies on Spring Security. Sure enough, the erroneous set-cookie header is no longer present when Spring Security has been removed. That's not proof, but it is evidence that this is caused, at least in part, by Spring Security. Of course if you believe there is a better project to file this against I will be happy to do so, and I would appreciate any pointers in that regard.

gateway-webflux-session-2.zip

rwinch commented 5 years ago

Thanks for the additional details.

The reason is that enabling Spring Security causes the WebSession to be read. When Spring WebFlux tries to resolve the WebSession it looks in the SESSION cookie for the id to resolve and finds that the session id is invalid. Since the session id is invalid, Spring WebFlux invalidates the SESSION cookie.

Your question might be...why is Spring Security trying to read the WebSession? First it can be helpful to understand the request cache. The request cache:

The problem is that the request cache is being invoked for every request to see if there is a value saved to replay and thus the WebSession is being looked up for every request. Since the WebSession is being looked up with an invalid session id, Spring WebFlux invalidates the SESSION cookie as described above.

I created gh-7157 to limit when the request cache is being accessed (and thus the WebSession). In the meantime if you don't need the request cache, you can disable it using:

http
    .requestCache()
        .requestCache(NoOpServerRequestCache.getInstance());
msassak commented 5 years ago

Thanks @rwinch! This solves the issue for me.

DarrenJiang1990 commented 5 years ago

a final solution for session stateless (spring security with webflux) : .and().securityContextRepository(NoOpServerSecurityContextRepository.getInstance())

explanation: The security context in a WebFlux application is stored in a ServerSecurityContextRepository. Its WebSessionServerSecurityContextRepository implementation, which is used by default, stores the context in session. Configuring a NoOpServerSecurityContextRepository instead would make our application stateless

lyoumi commented 5 years ago

If I'm correct in this case spring using InMemoryWebSessionStore by default and no-one suggested solution is not fix it. I tried to apply each suggested solution, but I still have exception when sessions are more than 1000. And 1000 is hardcoded in the InMemoryWebSessionStore and you have to manually set max count instead of changing corresponding property (in the application.yaml for example). I'd don't like to write custom implementation for the WebSessionStore or WebSessionManager, so I believe that spring-team will reopen this issue and add the possibility to disable sessions.

sanjayobsidiam commented 5 years ago

Is this the reason behind my all requests are getting unauthorized response only the first request is fine?

cash1981 commented 5 years ago

@spring-issuemaster Enough information has been issued imo to reopen this

borisgalitsky commented 4 years ago

Issue is still actual. Once you handle "java.lang.IllegalStateException: Max sessions limit reached: 10000", you want to switch off sessions at all. How to do it?

rwinch commented 4 years ago

These issues are different from the originally posted problem. Please create new tickets for new issues.

NOTE: If you need to scale up the number of sessions, you need to either reduce the size of your sessions or persist them in an eternal data store (i.e. using Spring Session).

dgempiuc commented 4 years ago

i have same issue now. i delegate authentication to oauth2 server in spring cloud gateway (which uses webflux). when i login at oauth2, it returns jwt token to gateway and it is stored as session in there. but gateway must be stateless. browser request to gateway with session and gateway finds jwt according to session. it is anormal behavior.

luqmanulkhair commented 3 years ago

@DarrenJiang1990 Does this mechanism also works with oauth2login()?

DarrenJiang1990 commented 3 years ago

@DarrenJiang1990 Does this mechanism also works with oauth2login()?

no, token must be stored in oauth2 server

svankamamidi commented 7 months ago

Thanks for the additional details.

The reason is that enabling Spring Security causes the WebSession to be read. When Spring WebFlux tries to resolve the WebSession it looks in the SESSION cookie for the id to resolve and finds that the session id is invalid. Since the session id is invalid, Spring WebFlux invalidates the SESSION cookie.

Your question might be...why is Spring Security trying to read the WebSession? First it can be helpful to understand the request cache. The request cache:

  • When an unauthenticated user requests a page that requires authentication, the request cache saves the request (URL, HTTP Method, Headers, etc) in session
  • After the user is authenticated the cache is looked up and then they are redirected to the original URL
  • Every request that comes in Spring Security inspects the request cache to see if there is a value in the request cache and if the URL matches the original URL if so it replays that request (URL, HTTP Method, Headers, etc)

The problem is that the request cache is being invoked for every request to see if there is a value saved to replay and thus the WebSession is being looked up for every request. Since the WebSession is being looked up with an invalid session id, Spring WebFlux invalidates the SESSION cookie as described above.

I created gh-7157 to limit when the request cache is being accessed (and thus the WebSession). In the meantime if you don't need the request cache, you can disable it using:

http
  .requestCache()
      .requestCache(NoOpServerRequestCache.getInstance());

@rwinch above given is not working with Spring boot 3.2.3 (Spring 6) I am trying with below code. Still I see session entry created in redis cache. Am I missing anything.

public SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception { http .requestCache().disable()
.oauth2ResourceServer().jwt();

    return http.build();
}