Spring Security without the WebSecurityConfigurerAdapter and two AuthenticationProviders - spring-boot

I have two AuthenticationProviders and don't know how to translate this to the new way of Spring Security.
This is what I mean by "the new way" https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
Currently I'm extending WebSecurityConfigurerAdapter and have this configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(inMemoryAuthenticationProvider);
auth.authenticationProvider(ldapAuthenticationProvider);
auth.userDetailsService(userDetailsService);
}
How must the configuration look like without extending WebSecurityConfigurerAdapter?

I solved it by using filterChain as #Elyobek sugested but with this code:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authenticationProvider(inMemoryAuthenticationProvider);
http.authenticationProvider(ldapAuthenticationProvider);
http.userDetailsService(userDetailsService);
}
I didn't realize that I can set the AuthenticationProviders and UserDetailsService on HttpSecurity.

How about this:
#Configuration
public class SecurityConfig {
private AuthenticationManagerBuilder authBuilder;
public SecurityConfig(AuthenticationManagerBuilder authBuilder) {
this.authBuilder = authBuilder;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authBuilder.authenticationProvider(inMemoryAuthenticationProvider);
authBuilder.authenticationProvider(ldapAuthenticationProvider);
authBuilder.userDetailsService(userDetailsService);
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.authenticationManager(authBuilder.getOrBuild())
.httpBasic(withDefaults());
return http.build();
}
}

Related

Spring Security addFilterAfter method removes the registered filter

I have a spring project with only two dependencies web & security (for testing purpose)
I am just creating simple filter like that:
public class TestFilter implements Filter {
private AuthenticationManager authenticationManager;
public TestFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
}
And registering this filter in the configuration:
#Configuration
#EnableWebSecurity(debug = true)
public class TestConf extends WebSecurityConfigurerAdapter {
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new TestFilter(authenticationManagerBean()), BasicAuthenticationFilter.class);
}
}
Problem is that,
if I use addFilterAfter method, BasicAuthenticationFilter is removed by the Spring, (I can confirm that with console output, #EnableWebSecurity(debug = true) this annotation prints all security filters)
Here is the log:
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#158f4cfe, org.springframework.security.web.context.SecurityContextPersistenceFilter#1174a305, org.springframework.security.web.header.HeaderWriterFilter#2b037cfc, org.springframework.security.web.csrf.CsrfFilter#76db540e, org.springframework.security.web.authentication.logout.LogoutFilter#13741d5a, com.....security.filter.TestFilter#35f639fa, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#1866da85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#2ec3633f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#5aaaa446, org.springframework.security.web.session.SessionManagementFilter#19ee1ae6, org.springframework.security.web.access.ExceptionTranslationFilter#4eb1c69]
if i don't register my custom filter, then I can see BasicAuthenticationFilter: security.web.authentication.www.BasicAuthenticationFilter
Here is the configuration class:
#Configuration
#EnableWebSecurity(debug = true)
public class ProjectBeanConfiguration extends WebSecurityConfigurerAdapter {
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
Here is the log:
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#704641e3, org.springframework.security.web.context.SecurityContextPersistenceFilter#750ff7d3, org.springframework.security.web.header.HeaderWriterFilter#42b6d0cc, org.springframework.security.web.csrf.CsrfFilter#5c7668ba, org.springframework.security.web.authentication.logout.LogoutFilter#459b187a, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#741f8dbe, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter#7fd26ad8, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter#2f00f851, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#7a8406c2, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#2620e717, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#3d37203b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#4207609e, org.springframework.security.web.session.SessionManagementFilter#3f4f5330, org.springframework.security.web.access.ExceptionTranslationFilter#e042c99, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#17d32e9b]
What can be the reason?
It is because you define your own WebSecurityConfigurerAdapter which causes the default security configuration defined by spring-boot does not take effect. And in your configuration , you never enable BasicAuthenticationFilter. So try the following should fix the problem :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and().addFilterAfter(new TestFilter(authenticationManagerBean()), BasicAuthenticationFilter.class);
}
If you want to keep the default configuration provided by spring-boot and add a TestFilter on top of it , you have to configure it manually :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic()
.and().addFilterAfter(new TestFilter(authenticationManagerBean()), BasicAuthenticationFilter.class);
}
You can find the default configuration at here and its javadoc also already mentioned such behaviour :
The default configuration for web security. It relies on Spring
Security's content-negotiation strategy to determine what sort of
authentication to use. If the user specifies their own
WebSecurityConfigurerAdapter or SecurityFilterChain bean, this
will back-off completely and the users should specify all the bits
that they want to configure as part of the custom security
configuration.

url based custom mutiple auth providers calls always one provider only

I have configured multiple custom auth providers,using auth2 and spring boot, but it always executes the CustomInternalAuthenticationProvider only.can you please explain the how to apply ant matcher rules in order?i have used two WebSecurityConfigurerAdapter classes and one is orderded and one is default.guide me on how to handle the antmatcher rules properly?
#EnableResourceServer
#EnableWebSecurity
public class WebSecurityConfig{
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("#order");
http.antMatcher("/../main/**")
.requestMatchers()
.antMatchers("/","/login*", "/oauth/authorize**","/exit","**/logout")
.and().authenticationProvider(daoInternalAuthenticationProvider())
.formLogin().loginPage("/login")
;
}
#Bean
public AuthenticationProvider daoInternalAuthenticationProvider() throws Exception {
return new CustomInternalAuthenticationProvider();
}
}
#Configuration
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("default");
http.antMatcher("/../user/**")
.requestMatchers()
.antMatchers("/","/login*", "/oauth/authorize**","/exit","**/logout")
.and() .authenticationProvider(daoExternalAuthenticationProvider())
.formLogin().loginPage("/login")
;
}
#Bean
public AuthenticationProvider daoExternalAuthenticationProvider() throws Exception {
return new CustomExternalAuthonticationProvider();
}
}

Spring security oauth2 always returning 403

I have a Spring boot app serving Rest endpoints which I'm securing using Spring security and Oauth2.
I want to secure all my endpoints except the endpoints used to authenticate, to create an account or some info stuff.
The security configuration is like this :
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private MongoTokenStore tokenStore;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
//clients.withClientDetails(clientDetailsService);
clients.inMemory().withClient("app").secret("password")
.accessTokenValiditySeconds(30000).authorizedGrantTypes("password", "refresh_token")
.refreshTokenValiditySeconds(300000000)
.scopes("read");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.pathMapping("/oauth/confirm_access", "/access_confirmation");
}
#Bean
public TokenStore tokenStore() {
return this.tokenStore;
}
}
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Autowired
private SecurityContextService securityContextService;
#Autowired
private MongoTemplate mongoTemplate;
#Bean
public MongoUserDetailsManager mongoUserDetailsManager() throws Exception {
return new MongoUserDetailsManager(userRepository, securityContextService, authenticationManagerBean(), mongoTemplate);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(mongoUserDetailsManager());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/login", "/oauth/authorize", "/oauth/token", "/server/version", "/clients/register").permitAll()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable();
}
}
I can access to token endpoint to get my access_token, but I want to access to other secured endpoints using this access_token (by adding the Authorization:Bearer {access_toke} to the header), I always get HTTP 403.
Did I miss something? I'm not supposed as authorized if I add the Authorization header?
My Controllers are only annotated with these #RestController, #CrossOrigin
and #RequestMapping("/url")
There are 2 types of security configurations in case of OAuth security(as far as urls security is concerned) in Spring.
1. Basic Security Configuration
This class should implement WebSecurityConfigurerAdapter. It will handle all those requests coming without "Bearer" token type(urls that shouldn't be oauth protected).
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Autowired
private SecurityContextService securityContextService;
#Autowired
private MongoTemplate mongoTemplate;
#Bean
public MongoUserDetailsManager mongoUserDetailsManager() throws Exception {
return new MongoUserDetailsManager(userRepository, securityContextService, authenticationManagerBean(), mongoTemplate);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(mongoUserDetailsManager());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/login", "/oauth/authorize", "/oauth/token", "/server/version", "/clients/register").permitAll()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable();
}
}
2. Resource Server Configuration(OAuth Specific)
This class is responsible for handling all those requests coming with authorization header of type Bearer. It should be extended from ResourceServerConfigurerAdapter class. Here you should mention all those urls with security configurations that you like to be oauth protected.
#Configuration
#EnableResourceServer
public class OAuthResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/resources-to-be-protected/**").and().authorizeRequests()
.antMatchers("/resources-to-be-protected/**").access("#oauth2.isClient()");
}
}

Multiple user details services for different endpoints

I am building a REST API using Spring and am currently authenticating all my requests using a custom user details service and this configuration code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
I am also setting up a DaoAuthenticationProvider to use the my user details service and using that to configure global security.
Now, I want to provide an endpoint that (while still secured with HTTP basic authentication) uses a different user details service to check whether the user is allowed to access the given resource.
How do I use two different user details services for different endpoints?
One thing you can do is have two WebSecurityConfigurerAdapters:
#EnableWebSecurity
#Order(Ordered.HIGHEST_PRECEDENCE)
class FirstEndpointConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http
.requestMatchers()
.antMatchers("/specialendpoint")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(/* first of your userDetailsServices */);
}
}
#Configuration
class SecondEndpointConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http // all other requests handled here
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(/* second of your userDetailsServices */);
}
}
requestMatchers() exists for targeting springSecurityFilterChains to specific endpoints.
EDIT: Mahmoud Odeh makes a good point that if the user bases are the same, then you may not need multiple UserDetailsService instances. Instead, you can use one change that isolates your special endpoint by an authority on the user's account:
http
.authorizeRequests()
.antMatchers("/specialendpoint").hasAuthority("SPECIAL")
.anyRequest().authenticated()
.and()
.httpBasic();
Then, your single UserDetailsService would look up all users. It would include the SPECIAL GrantedAuthority in the UserDetails instance for users who have access to /specialendpoint.
I am trying to follow the solution given by M. Deinum but in my case it always goes to the same user service (v2userDetailsService) regardless of which URL is executed /v3/authorize/login or /v2/authorize/login. Here is my code:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration {
#Configuration
#Order(2)
public static class V2Configuration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("v2userDetailsService")
private UserDetailsService v2userDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
auth
.userDetailsService(v2userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
.frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/app").permitAll()
.antMatchers("/v2/authorize/login").permitAll()
.antMatchers("/v2/authorize/reLogin").permitAll()
.antMatchers("/v2/authorize/logout").permitAll();
}
}
#Configuration
#Order(1)
public static class V3Configuration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("v3UserDetailsService")
private UserDetailsService v3UserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
auth
.userDetailsService(v3UserDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
.frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/v3/authorize/login").permitAll()
.antMatchers("/v3/authorize/reLogin").permitAll()
.antMatchers("/v3/authorize/logout").permitAll();
}
}
}

Spring boot and spring security multiple login pages

#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/my/**", "/account/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
.and().formLogin().loginPage("/login");
}
}
#Configuration
#Order(2)
public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.and().formLogin().loginPage("/adminlogin");
}
}
}
This is supposed be two different login forms. My problem is that the one with the highest order /adminlogin is not displayed. I have idea why? Please help. The code is from Spring boot - how to configure multiple login pages?
Following Sofia's suggestion I tried this:
#Configuration
#Order(2)
public static class UserConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/my/**"))
.csrf().disable()
.authorizeRequests().antMatchers("/my/**").access("hasRole('ROLE_USER')")
.and().formLogin().loginPage("/login");
}
}
#Configuration
#Order(1)
public static class AdminConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/admin/**"))
.csrf().disable()
.authorizeRequests().antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.and().formLogin().loginPage("/adminlogin");
}
}
But in both cases /login is called
I reckon that the reason why your admin login is not activating is because: first, it is NOT higher in priority.
#Order defines the sort order for an annotated component.
The value is optional and represents an order value as defined in the Ordered interface. Lower values have higher priority. The default value is Ordered.LOWEST_PRECEDENCE, indicating lowest priority (losing to any other specified order value).
Second, according to HttpSecurity's Javadoc:
A HttpSecurity is similar to Spring Security's XML element in the namespace configuration. It allows configuring web based security for specific http requests. By default it will be applied to all requests, but can be restricted using requestMatcher(RequestMatcher) or other similar methods.
So try restricting the HttpSecurity object to activate for your admin pages by first configuring the requestMatcher such that:
http
.requestMatcher(new AntPathRequestMatcher("/admin/**"))
.csrf().disable()
.authorizeRequests().antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.and().formLogin().loginPage("/adminlogin");
I solved it using request matcher:
#Configuration
#EnableWebSecurity
public class AllConfig extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDeatailService myuserDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Bean
public static BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(myuserDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Bean
public static AuthenticationFailureHandler customAuthenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
#Configuration
#Order(1)
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcher("/admin/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.and().formLogin()
.loginPage("/admin/adminlogin").permitAll().usernameParameter("username")
.passwordParameter("password").defaultSuccessUrl("/admin/AdminDashBoard")
.failureHandler(customAuthenticationFailureHandler()).and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/home").and()
.exceptionHandling().accessDeniedPage("/403");
}
}
#Configuration
#Order(2)
public static class UserSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcher("/user/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").access("hasRole('ROLE_USER')").and().formLogin()
.loginPage("/user/userlogin").permitAll().usernameParameter("username")
.passwordParameter("password").defaultSuccessUrl("/user/UserDashBoard")
.failureHandler(customAuthenticationFailureHandler()).and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/").and()
.exceptionHandling().accessDeniedPage("/403");
}
}
}

Resources