Spring Security 6 CustomAuthenticationFilter(intend to replace the UsernamePasswordAuthenticationFilter) does not work - spring

By reference https://www.baeldung.com/spring-security-extra-login-fields
I intend to customize the the functionality of Spring security Authentication UsernamePasswordAuthenticationFilter to get the additional customized "loginForm" fields.
I created one customized filter
CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter
and one customized authentication token
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken
By using the below configuration:
#EnableWebSecurity
#Configuration(proxyBeanMethods = false)
public class AuthenticationSecurityConfig extends AbstractHttpConfigurer<AuthenticationSecurityConfig, HttpSecurity> {
#Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http.addFilterBefore(authenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
}
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.requestMatchers("/css/**", "/login")
.permitAll()
.requestMatchers("/resources/**")
.permitAll()
.and()
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout()
.logoutUrl("/logout")
.and()
.authorizeHttpRequests(
auth -> auth.anyRequest().authenticated()
)
.apply(securityConfig());
return http.build();
}
}
I can successfully make the my AuthenticationProvider's
authenticate
method to be executed, and It can successfully return one "authenticated"
Authentication object.
However it will not redirect to the OAuth2 client's redirect page(I setup my project as one OIDC authorization server), it will stay at login page, the log will be like below:
.HttpSessionRequestCache : Saved request http://192.168.0.107:9000/oauth2/authorize?client_id=messaging-client&redirect_uri=http%3A%2F%2F192.168.0.107%3A8080%2Fauth%2Fsigninwin%2Fmain&response_type=code&scope=openid%20profile%20message.read&state=802af0dcd82a483eb726c1dffff0867d&code_challenge=t_tfBjZPRd228uEZuQJ56clfXokGYqiwkudQqKhWQqo&code_challenge_method=S256&prompt=login&response_mode=query&continue&continue to session
2022-11-30T19:48:05.063+08:00 DEBUG 63801 --- [nio-9000-exec-5] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://192.168.0.107:9000/login
2022-11-30T19:48:05.078+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.security.web.FilterChainProxy : Securing GET /login
2022-11-30T19:48:05.078+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.security.web.FilterChainProxy : Secured GET /login
2022-11-30T19:48:05.084+08:00 DEBUG 63801 --- [nio-9000-exec-6] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
If I will not use the customized filter CustomAuthenticationFilter, every thing will be fine(But of couse I can't get the additonal LoginForm fileds).
My build.gradle
plugins {
id "org.springframework.boot" version "3.0.0-RC2"
id "io.spring.dependency-management" version "1.0.11.RELEASE"
id "java"
}
implementation "org.springframework.security:spring-security-oauth2-authorization-server:1.0.0"
Any ideas?
I suspected that the attributes of CustomAuthenticationFilter(use new to create the object) is not the same with attributes of the UsernamePasswordAuthenticationFilter that spring security framework will initialize, need further check the disparity.

There are two solutions,
the difference with previous versions is due to this change: https://github.com/spring-projects/spring-security/issues/11110
*** First solution ***
You can use the old strategy (deprecated) with automatic saving of the SecurityContextHolder setting:
http.securityContext((securityContext) -> securityContext.requireExplicitSave(false))
*** Second solution ***
Define an HttpSessionSecurityContextRepository:
#Bean
public HttpSessionSecurityContextRepository securityContextRepository() {
return new HttpSessionSecurityContextRepository();
}
Set to http configuration:
http.securityContext().securityContextRepository(securityContextRepository())
finally set the context repository to your custom filter:
filter.setSecurityContextRepository(securityContextRepository());

Related

How do i allow access certain request path to every user so that he/she can register user in spring security 6?

In spring security 5.7.5, I had the following security filter chain that allows unauthenticated users to easily register accounts.
Code in spring Security 5.7.5
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests(
authorize ->
authorize
.mvcMatchers("/user/**").permitAll()
.mvcMatchers("/**").authenticated()
).formLogin(Customizer.withDefaults());
return http.build();
But now, The previous way code is not working. How should I configure a filter chain that allows anyone to register accounts?
Present Code
#EnableWebSecurity
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
//when unauthenticated user tries to login the resource server
// redirect him/her to login page
http
.exceptionHandling(
exception ->
exception.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login")
)
);
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.disable()
.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers("/user/registerUser",
"/user/getAllUser").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
}
I'm getting 401 unauthorized in postman when i request for /user/registerUser and /user/getAllUser url. What i'm trying is, registering user account by unauthenticated users. I belive my security filter chain is sending me to /login page to authenticate which i don't want for register url.

Spring Security Context Authentication is null

i am trying to add couple of filters in my request processing in spring boot security config.
Below is my code
#EnableWebSecurity
#Configuration
public class JwtSecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(this::configureEndpoints)
return http.build();
}
private void configureEndpoints(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizationManagerRequestMatcherRegistry){
authorizationManagerRequestMatcherRegistry.mvcMatchers("/permit")
.permitAll()
.mvcMatchers("/block")
.denyAll()
.and()
.mvcMatcher("/api")
.addFilterBefore(new Filter1(), SecurityContextHolderAwareRequestFilter.class)
// register TenantFilter in the chain after the SecurityContext is made available by the respective filter
.mvcMatcher("/api")
.addFilterAfter(new Filter2(), SecurityContextHolderAwareRequestFilter.class)
.authorizeHttpRequests()
.mvcMatchers("/api")
.authenticated()
.and();
}
}
It seems the authentication does not happen and filters are never hit.
If i try to access the authentication in my runtime code i get SecurityContextHolder.getContext().getAuthentication() as null.
Seems to some problem in the security configuration only.

Why two formLogin configured in Spring Authorization Server Sample code

I'm checking latest Spring Authorization Server v0.2.0 and found two formLogin() configured on the provided sample authorizationserver.
One is AuthorizationServerConfig.java:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
Another one is DefaultSecurityConfig.java:
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
My question is:
why there are two formLogin()s configured
If I wanted to customize formLogin() which one should I change?
The reason for the formLogin() configuration in AuthorizationServerConfig is purely a "convenience configuration", as it will setup the LoginUrlAuthenticationEntryPoint and perform the redirect to /login when the current request is not authenticated.
For example, when the client is redirected to /oauth2/authorize and the user is not authenticated, the user will be redirected to /login, which will match on the SecurityFilterChain defined by DefaultSecurityConfig NOT AuthorizationServerConfig.
Basically, the formLogin() in AuthorizationServerConfig serves the sole purpose of performing the redirect to /login, which is ultimately matched on the DefaultSecurityConfig SecurityFilterChain.

Spring Security OAuth2: '#oauth2.xxx' expressions not evaluted with multiple RequestMatchers mapped to the same FilterChain

In a multi modules app I've defined 5 RequestMatchers mapped to the same FilterChain, like below:
#Configuration
public class Module1SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module1/**")
.and()
.authorizeRequests()
.antMatchers( "/module1/resource").authenticated()
.antMatchers( "/module1/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And module2:
#Configuration
public class Module2SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module2/**")
.and()
.authorizeRequests()
.antMatchers( "/module2/resource").authenticated()
.antMatchers( "/module2/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And enabled method security:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
The problem is that all #oauth2.xx expressions are evaluated only for the 1st module requestmatcher /module1/** and ignored in others. When I authenticate a user and try to access to /module1/test the access is denied as expected whereas when accessing to /module2/test access is granted (it should also be denied).
Could someone explains me why and how to solve this? I know Spring Security isn't easy at all...
Thanks again.
EDIT
#Darren Forsythe (thanks for your comment)
The filter chains created are:
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#f55a810b, org.springframework.security.web.context.SecurityContextPersistenceFilter#85021903, org.springframework.security.web.header.HeaderWriterFilter#1d0744d1, org.springframework.security.web.authentication.logout.LogoutFilter#2d15146a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#c38f3266, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#8f9bf85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#74a71be5, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#e4eb6cc, org.springframework.security.web.session.SessionManagementFilter#22f6b39a, org.springframework.security.web.access.ExceptionTranslationFilter#960c464f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#f7a19dc5]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/module1/**'], Ant [pattern='/module2/**'], Ant [pattern='/module3/**'], Ant [pattern='/module4/**'], Ant [pattern='/module5/**'], Ant [pattern='/module6/**']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#38ef2427, org.springframework.security.web.context.SecurityContextPersistenceFilter#a26ff7af, org.springframework.security.web.header.HeaderWriterFilter#5344e710, org.springframework.security.web.authentication.logout.LogoutFilter#da0534c8, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#2956c7ab, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5682f610, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#f4cbf7a4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#d1b1395a, org.springframework.security.web.session.SessionManagementFilter#d352f8ab, org.springframework.security.web.access.ExceptionTranslationFilter#9bb1d86, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#73c7a695]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#1cc2056f, org.springframework.security.web.context.SecurityContextPersistenceFilter#259d95db, org.springframework.security.web.header.HeaderWriterFilter#de089e0b, org.springframework.security.web.authentication.logout.LogoutFilter#8b86b4c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#96304ca8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1d5b7e4b, org.springframework.security.web.session.SessionManagementFilter#bd586b4d, org.springframework.security.web.access.ExceptionTranslationFilter#7cff2571]
As you can see, all module's urls are mapped to the same filter chain with this list of filters:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
What I don't understand is why for the other modules the #oauth2.xx expression is not evaluated since the FilterChain is the same?
Request matchers (specified in antMatchers, anyRequest, etc.) are processed by the filter chain in the order they are specified. Because multiple ResourceServiceConfiguredAdapter instances simply configure off of the same instance of HttpSecurity, the matchers are processed something like this for each one of your requests:
if (uri == "/module1/resource") {
// ...
} else if (uri == "/module1/test") {
// ...
} else if (true) { // anyRequest
// ...
} else if (uri = "/module2/resource") {
// ...
} else if (uri = "/module2/test") {
// ...
}
As you can see, the last two if conditions would never get hit.
Here are two things you can consider:
Replace anyRequest()
anyRequest is usually very handy; however, in this case, you don't actually mean "any request" since you are trying to narrow the scope to certain module paths. You might instead do:
http
.requestMatchers()
.antMatchers("/module2/**")
.and()
.authorizeRequests()
.antMatchers("/module2/resource").authenticated()
.antMatchers("/module2/test").access( "#oauth2.isClient()")
.antMatchers("/module2/**").access( "#oauth2.hasScope('webclient')" );
That way, the module doesn't overreach and try and specify behavior that maybe it doesn't know about.
Truthfully, it is typically harmless to call anyRequest since you are already narrowing the scope of the filter chain already with requestMatchers. But, because you are composing a single HttpSecurity with multiple adapters, there is this hidden complexity.
oauth2ResourceServer() - Spring Security 5.1+
If you are on Spring Security 5.1, then there is actually support built into WebSecurityConfigurerAdapter natively, so you don't need to use ResourceServerConfigurerAdapter anymore, at least for JWT-encoded tokens at this point. This is also nice because two WebSecurityConfigurerAdapters are treated as separate filter chains.
Depending on the OAuth 2.0 features you need, you may be able to do that instead:
#EnableWebSecurity
public class Module1Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/module1/**")
.and()
.authorizeRequests()
.antMatchers("/module1/resource").authenticated()
.anyRequest.hasRole("SCOPE_webclient")
.oauth2ResourceServer()
.jwt();
}
}
This is an active area of development for Spring Security right now - porting features over into WebSecurityConfigurerAdapter; so definitely reach out with your specific use case to make sure that it gets prioritized if it isn't already in place.

Spring Boot Not Using UserDetailsService

I am trying to configure a Spring Boot 1.2.5 application for JPA authentication using annotations and it appears to be always using the in-memory provider.
The application:
#EnableWebMvc
#ComponentScan
#EnableAutoConfiguration
#SpringBootApplication
public class ClubBooksApplication {
protected final Logger logger = LoggerFactory.getLogger(getClass());
public static void main(String[] args) {
SpringApplication.run(ClubBooksApplication.class, args);
}
}
The WebSecurityConfigurerAdapter. I have played around with the order but it always seems to configure the in-memory provider. I feel like I could be missing a piece of configuration but this pattern matches the samples I found in my searches.
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
//#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) // After in memory
//#Order(SecurityProperties.IGNORED_ORDER) // Before in memory
//#Order(SecurityProperties.BASIC_AUTH_ORDER) // Not unique
#Order(SecurityProperties.BASIC_AUTH_ORDER - 50) // Before in memory
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private PasswordEncoder passwordEncoder;
protected final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
logger.info(String.format("configure AuthenticationManagerBuilder: %s", userDetailsService));
super.configure(auth);
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
Here is the log output. You can see it is configuring the UserDetailsService before displaying the generated password. Based on my digging into the code, it appears to only configure the in-memory provider if no other provider is configured but setting the UserDetailsService configures a DAO provider.
2015-08-20 11:19:24.187 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : configure AuthenticationManagerBuilder: com.wstrater.server.clubBooks.server.service.impl.UserLoginDetailServiceImpl#46f0f40a
2015-08-20 11:19:24.226 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : passwordEncoder
2015-08-20 11:19:24.410 INFO 42332 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration :
Using default security password: 838a7ab0-3bd0-4e87-94ca-de2dfd34b965
2015-08-20 11:19:24.526 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : configure HttpSecurity
I have included Actuator in the app and when I try to access http://localhost:8080/mappings, I am prompted with BasicAuth despite configuring form based authentication. The user/generated password works for BasicAuth. My UserDetailsService implementation is not called.
Configuring HttpSecurity. This method is called after the in-memory provider is created and the generated password is displayed so I doubt it impacts the provider configuration. The one thing I find interesting is that I get prompted for BasicAuth despite specifying formLogin(). I bring this up since I am also having issues with mapping my controllers. I think it is unrelated but what do I know.
#Override
protected void configure(HttpSecurity http) throws Exception {
logger.info("configure HttpSecurity");
super.configure(http);
http.authorizeRequests()
.antMatchers("/", "/public/**")
.permitAll()
.antMatchers("/rest/**")
.authenticated()
.antMatchers("/web/**")
.authenticated()
.anyRequest()
.fullyAuthenticated();
http.formLogin()
.loginPage("/login")
.usernameParameter("userName")
.passwordParameter("password")
.failureUrl("/login?error")
.defaultSuccessUrl("/web/")
.permitAll()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
.and().rememberMe();
}
I can see my controllers are being loaded by Spring since they are listed using http://localhost:8080/beans but I do not see the mappings in http://localhost:8080/mappings.
My login controller is rather simple.
#Controller
#Path("/login")
public class LoginWebController {
#GET
public ModelAndView getLoginPage(#RequestParam(required = false) String error) {
return new ModelAndView("login", "error", error);
}
}
Thanks, Wes.
I eventually solved this after working around and/or avoiding the problem so I may have done several things to solve the issue but I believe it is as simple as removing the call to super.configure(http);. This sets up basic auth.
Wes.

Resources