Skip to content

Please add a mechanism to maintain authentication in cluster environment during oauth-authorization-code flow #306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gth7754125 opened this issue May 26, 2021 · 13 comments
Assignees
Labels
status: invalid An issue that we don't feel is valid

Comments

@gth7754125
Copy link

Current Behavior
If the authorization server needs to deploy in cluster's environment, i need to remove session mechanism.However, when i set

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

spring security can not trace my authentication info between login and authorization-code grant behaviour because the info lost between two requests, so it will loop on the login behaviour forever. Is that any way to deal with it?

@gth7754125 gth7754125 added the type: enhancement A general enhancement label May 26, 2021
@marcthornton
Copy link

I am struggling with the same issue, wanting and needing to remain stateless in a clustered environment which cannot guarantee me stickiness. Authorization code handling is done in OAuth2AuthorizationEndpointFilter, which is part of the Spring Security filter chain (and quite early in that filter chain, by default). This seems to suggest either
a) the user was pre-authenticaticated and stored in a session
OR
b) that a stateless authentication filter was placed before the OAuth2AuthorizationEndpointFilter and actually performs the authentication, which seems to suggest that letting AuthenticationProvider's handle the tokens in the stream of things is out of the question

It seems that to work in a stateless environment, the production of the authorization code would need to act on the result of the authentication process, rather than being embedded very early in that process.

@pxzxj
Copy link

pxzxj commented May 27, 2021

I think the spring-session project can solve the authentication problem in the cluster environment. The key problem is that the OAuth2AuthorizationService interface has only one memory-based implementation InMemoryOAuth2AuthorizationService, which means that OAuth2Authorization cannot be obtained in the cluster.

@gth7754125
Copy link
Author

I think the spring-session project can solve the authentication problem in the cluster environment. The key problem is that the OAuth2AuthorizationService interface has only one memory-based implementation InMemoryOAuth2AuthorizationService, which means that OAuth2Authorization cannot be obtained in the cluster.

I have already written a simple RedisOAuth2AuthroizationService implementation, and I don't want to import a spring-session to the server project, so now I just custom a KwAuthenticationSuccessHandler , AuthenticationEntryPoint, and add a custom filter which added before OAuth2AuthorizationEndpointFilter to get back the redirectUrl. Although it seems not a good way to alternate session mechanism, I have no better idea to solve it.

@sjohnr
Copy link
Member

sjohnr commented Jun 18, 2021

Hi @gth7754125. I believe there are two main issues you're facing:

  1. Session management
  2. Shared access to authorization state

As @pxzxj pointed out, session management can easily be distributed using the spring-session project. This solves the problem of tying things like the state and redirect_uri parameters back to a particular browser in the authorization_code flow. There is state involved in the flow, and sessions are how that state is maintained. See spring-projects/spring-security#7889 for some background on clustering on the client side as it also pertains to how clustering would work on the authorization server side.

You should also check out recent and ongoing work for clients, authorizations and consents using JDBC instead of in-memory: #291, #304, #314. You can base additional implementations such as the redis one you mentioned on these.

I'm going to close this, as I'm not seeing a specific enhancement request. If you have a more specific request, feel free to open a new issue.

@sjohnr sjohnr closed this as completed Jun 18, 2021
@jgrandja jgrandja added status: invalid An issue that we don't feel is valid and removed type: enhancement A general enhancement labels Jun 18, 2021
@jacko9et
Copy link

I also have this problem. oauth2 solved the session problem, but now it has reintroduced the session problem. Can you consider abstracting the action of getting the code so that people with the same problem can solve the session problem through extension. At present, this action is integrated in the Filter and is not extensible.

@sjohnr
Copy link
Member

sjohnr commented Sep 15, 2021

@lu-cheng does this commit (06ad211) associated with #376 apply to your question?

@jacko9et
Copy link

jacko9et commented Sep 18, 2021

Can refer to this mechanism to separate Session?

.../oauth2/authorize?... --> /login?client_id=... --> authenticate and codeflow --> /oauth2/consent?redirect_uri=...?code=... --> redirect_uri?code=...

state parameters can guarantee safety ...
Even the generation of code can be RSA encrypted data, so that using code to exchange tokens can also become stateless, reducing a large number of database interactions and junk data.

@sjohnr
Copy link
Member

sjohnr commented Sep 20, 2021

Hi @lu-cheng, I'm not quite following your question. I believe the commit I referenced is what you're looking for, as it introduces the ability to plug in a custom authorization_code generator. If you have a different enhancement suggestion, please open another issue and we'll take a look. If you have a question, please open a Stack Overflow question. Feel free to update this issue with a link to the re-posted question (so that other people can find it).

@ChainFoxBot
Copy link

ChainFoxBot commented Sep 23, 2021

Can refer to this mechanism to separate Session?

.../oauth2/authorize?... --> /login?client_id=... --> authenticate and codeflow --> /oauth2/consent?redirect_uri=...?code=... --> redirect_uri?code=...

state parameters can guarantee safety ...
Even the generation of code can be RSA encrypted data, so that using code to exchange tokens can also become stateless, reducing a large number of database interactions and junk data.

Eh.... Actually, authorization code is granted after user login, session can help us find back user authentication before user grants authorization code.
On the other way, if you just use other OAuth2 Flow like client-credential, it's not necessary to maintain session.

@lijiajia3515
Copy link

lijiajia3515 commented Dec 15, 2021

I am struggling with the same issue.

  1. /oauth2/authorize -> SessionCreationPolicy.IF_REQUIRED
  2. /oauth2/token /oauth2/jwks /oauth2/revoke /oauth2/introspect /.well-known/oauth-authorization-server SessionCreationPolicy.STATELESS

When configuring OAuth2AuthorizationServerConfigurer, the above two points cannot be configured, and SessionCreationPolicy.IF_REQUIRED is not required except for /oauth2/authorize.

I can only use multiple configurations to solve this problem.

Below is my code

  1. oauth2 stateless configurer
@Bean
@Order(200)
SecurityFilterChain cairoAuthorizationCustomSecurity(HttpSecurity http, ProviderSettings providerSettings) throws Exception {
	OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServer = new OAuth2AuthorizationServerConfigurer<>();
	http
		.requestMatcher(
			new OrRequestMatcher(
				new AntPathRequestMatcher("/.well-known/oauth-authorization-server", HttpMethod.GET.name()),
				new AntPathRequestMatcher(providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name()),
				new AntPathRequestMatcher(providerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name()),
				new AntPathRequestMatcher(providerSettings.getTokenEndpoint(), HttpMethod.POST.name()),
				new AntPathRequestMatcher("/oauth2/password_code", HttpMethod.POST.name())
			)
		)
		.authorizeRequests(authorizeRequests ->
			authorizeRequests.anyRequest().permitAll()
		)
		// .csrf(c -> c.ignoringAntMatchers("/**"))
		.cors(c -> {
		})
		.apply(authorizationServer)
		.and()
		.sessionManagement(c -> c
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
		.exceptionHandling(c -> {
		});
	return http.build();
}

@Bean
@Order(300)
SecurityFilterChain cairoAuthorizationSecurity(HttpSecurity http, CairoUserDetailsService userServ
	AccessDeniedHandlerImpl captchaAccessDeniedHandler = new AccessDeniedHandlerImpl();
	captchaAccessDeniedHandler.setErrorPage("/error/captcha");
	CaptchaConfigurer<HttpSecurity> captchaConfigurer = new CaptchaConfigurer<>();
	captchaConfigurer
		.verifyCaptchaAccessDeniedHandler(captchaAccessDeniedHandler)
		.verifyCaptchaRequestMatcher(
			new OrRequestMatcher(
				new AntPathRequestMatcher("/login/password", HttpMethod.POST.name())
			))
		;
	OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServer = new OAuth2Authorizatio
	http.setSharedObject(UserDetailsService.class, userService);
	http.setSharedObject(PersistentTokenRepository.class, persistentTokenRepository);
	http
		.antMatcher("/**")
		.authorizeRequests(c -> c
			.mvcMatchers("/error/**").permitAll()
			.requestMatchers(captchaConfigurer.getCaptchaRequestMatcher(), captchaConfigurer.getCh
			.mvcMatchers("/**").authenticated()
		)
		//.csrf().disable()
		.csrf(csrf -> csrf
			.ignoringRequestMatchers(
				new OrRequestMatcher(
					authorizationServer.getEndpointsMatcher(),
					captchaConfigurer.getCheckCaptchaRequestMatcher()
				)
			)
		)
		.cors().and()
		// 图形验证码
		.apply(captchaConfigurer)
		.and()
		.logout(c -> c
			.logoutUrl("/logout").permitAll()
			.deleteCookies(REMEMBER_ME_COOKIE_NAME)
		)
		.formLogin(c -> c
			.loginProcessingUrl("/login/password").permitAll()
			.loginPage("/login").permitAll()
			.failureHandler(new CairoSimpleUrlAuthenticationFailureHandler("/login?error"))
			.defaultSuccessUrl("/")
		)
		.oauth2Login(x -> x
			.loginProcessingUrl("/login/oauth2/code/*").permitAll()
			.loginPage("/login").permitAll()
			.tokenEndpoint(c -> c
				.accessTokenResponseClient(new CommonAuthorizationCodeTokenResponseClient()))
		)
		// 授权服务器
		.apply(authorizationServer)
		.and()
		.rememberMe(c -> c.
				rememberMeParameter(REMEMBER_ME_PARAMETER_NAME)
				.rememberMeCookieName(REMEMBER_ME_COOKIE_NAME)
			//.tokenRepository(persistentTokenRepository)
			//.userDetailsService(userService)
		)
		.sessionManagement(c -> c
			.invalidSessionUrl("/error/invalid_session")
			.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) 
		)
		.exceptionHandling(c -> {
		});
	return http.build();
}

( /oauth2/authorize /login /logout ) is configured after ( OAuth2AuthorizationServerConfigurer (except /oauth2/authorize) )

My suggestion is to configured configurer separately from /oauth2/authorize and other oauth2 endpoints

@lijiajia3515
Copy link

When the authorization server and the resource server exist at the same time, the resource server generally does not need a cookie method, but is stateless, which will cause the cookie to be rewritten as stateful and stored in the browser(in the resource server request)

@sjohnr
Copy link
Member

sjohnr commented Dec 16, 2021

@lijiajia3515, apologies but I'm having a slightly difficult time understanding your comment. Are you asking a question, or simply pointing out an example of how to accomplish your particular goal?

@jacko9et
Copy link

If the client does not support cookies, bootstrapping authentication is still easy to solve, but how to correlate previous authorization code requests after authentication has been bugging me. According to the existing design, actions and states are coupled. I've always wanted to introduce a temporary process ID to solve this problem, but the existing code logic is not friendly to such extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

8 participants