Spring security - Simple Multi Security now working [duplicate] - spring

This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 1 year ago.
I am dealing with this, since 7 hours ago ,and I cant find an explanation, for simplicity, I just did the example a little smaller.
I need some URLs with security access (JWT), and other path (dashboard) with a form login.
This is my code:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JwtRequestFilter jwtRequestFilter;
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// Get Request and /Authenticate do not need authentication
.authorizeRequests()
.antMatchers("/authenticate", "/authenticate/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/**").permitAll()
// all others do need authentication
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/dashboard/index.html").authenticated()
.and()
.formLogin();
}
}
This example is working, the JWT mechanism works great.
The only thing it does not work, is the form login. When I hit the browser localhost:8080/dashboard/index.html, the file appears.
This is what I need:
/authorize --> Anyone can hit that URL to get the JWT token
/api --> Get methods do not need authorization
/api --> All others verbs, do need a token.
/dashboard/index.html --> A form login should appear.
I know that anyRequest().authenticated(), it is in the first configuration but if I even comment that line, the second Order is totally ignored.
What should I add or remove to accomplish my idea?

In your FormLoginWebSecurityConfigurerAdapter, the antMatchers() should be called before authorizeRequests() - this indicate that this filter chain only apply request to /dashboard/index.html.
http.antMatcher("/dashboard/index.html")
.authorizeRequests()
.anyRequest().authenticated() // since this filter chain only apply to /dashboard/index.html, don't need use antMatchers() to check again
.and()
.formLogin();
For more info: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#multiple-httpsecurity
The second issue is that the Order of yourFormLoginWebSecurityConfigurerAdapter must be before (less than) ApiWebSecurityConfigurationAdapter. WebSecurityConfigurerAdapter has a default #Order of 100, so you should annotate #Order(0) on your FormLoginWebSecurityConfigurerAdapter.

Related

Spring security - Specific session creation policy per matchers

I'm trying to implement SessionCreationPolicy.ALWAYS for the /testMVCController/** endpoint and SessionCreationPolicy.STATELESS for rest of endpoints (/**).
Expected scenario:
When accessing to /testMVCController/displayUsers the user logs in once and the log I have implemented in UserDetailsService logs the authorities associated to that user.
After that, all the requests to /testMVCController/displayUsers or other URL under /testMVCController/** will not log the authorities again because the session creation policy is always and the user is already logged in.
This works when I don't specify the 2nd security configuration (X509ClientSessionCreationPolicyStateless) but when I add it, all the requests become session stateless.
It is not working with the current security configuration because after I log in with my client certificate, at any request executed under /testMVCController/** endpoint (e.g. /testMVCController/displayUsers), the authenticationUserDetailsService is consulted and the list of authorities is logged for each file request the browser makes (.js file, .css files, ...), even after the initial login.
So, if there are 3 requests (/testMVCController/displayUsers, displayUsers.js, displayUsers.css) the list of authorities log present in authenticationUserDetailsService is logged 3 times.
I configured SecurityConfiguration as shown below but it is not working:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration {
#Configuration
#Order(1)
public static class X509ClientSessionCreationPolicyAlways extends WebSecurityConfigurerAdapter {
#Autowired
private X509CUDService x509CUDService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/testMVCController/**")
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.x509()
.authenticationUserDetailsService(x509CUDService)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
#Configuration
#Order(2)
public static class X509ClientSessionCreationPolicyStateless extends WebSecurityConfigurerAdapter {
#Autowired
private X509CUDService X509CUDService ;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.x509()
.authenticationUserDetailsService(X509CUDService);
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
}
I've searched this issue and I found various links (e.g. Spring session creation policy per-request?, Spring Session: How to create separate session management policies for different URLs and Multiple HttpSecurity) but none of them worked.
Thanks in advance!
I was missing some details on my configuration. I was catching all the requests to /testMVCController/** and that was working, but in addition to catch the requests to any endpoint of the type /testMVCController/** (e.g.: /testMVCController/usersList), I also have to catch the requests that these pages make to get their scripts (.js files, .css files, .png files).
What was happening was: the request to /testMVCController/usersList), was configured with SessionCreationPolicy.ALWAYS, but the subsequent requests such as usersList.js, usersList.css, etc were configured with SessionCreationPolicy.STATELESS, and in these cases the X509CustomUserDetailsService was always consulted.
Example:
GET request to /testMVCController/usersList works, but there also requests in this usersList page to usersList.js, usersList.css, etc.
So, once I included these resource paths in the antMatchers all worked perfectly.

Configure public access, JWT authentication and HTTP basic authentication depending on paths in Spring Security

A Spring Boot application provides some REST endpoints with different authentication mechanisms. I'm trying to setup the security configuration according to the following requirements:
By default, all endpoints shall be "restricted", that is, if any endpoint is hit for which no specific rule exists, then it must be forbidden.
All endpoints starting with /services/** shall be secured with a JWT token.
All endpoints starting with /api/** shall be secured with HTTP basic authentication.
Any endpoint defined in a RESOURCE_WHITELIST shall be public, that is, they are accessible without any authentication. Even if rules #2 or #3 would apply.
This is what I came up with so far but it does not match the above requirements. Could you help me with this?
#Configuration
#RequiredArgsConstructor
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] RESOURCE_WHITELIST = {
"/services/login",
"/services/reset-password",
"/metrics",
"/api/notification"
};
private final JwtRequestFilter jwtRequestFilter;
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("some-username")
.password(passwordEncoder().encode("some-api-password"))
.roles("api-role");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf().disable()
.formLogin().disable()
// apply JWT authentication
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.mvcMatchers(RESOURCE_WHITELIST).permitAll()
.mvcMatchers("/services/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// apply HTTP Basic Authentication
.and()
.authorizeRequests()
.mvcMatchers("/api/**")
.hasRole(API_USER_ROLE)
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

Custom Authentication Entrypoint not being called on failed Authentication

I have setup an OAUTH Authorization server that's supposed to allow clients request for tokens. It's also supposed to allow admin users carry out other operations.
In my Web Security Configuration:
#Configuration
#EnableWebSecurity
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
private #Autowired CustomAuthenticationProvider authenticationProvider;
private #Autowired CustomAuthenticationEntryPoint entryPoint;
#Override
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().httpBasic().and().cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.defaultAuthenticationEntryPointFor(entryPoint, new AntPathRequestMatcher("/api/v1/**"));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
Ideally, when an admin user tries to call any endpoint under "/api/v1/**", they should be authenticated - and in fact, they are.
The issue now is, when authentication fails, the authentication entry endpoint is ignored. I don't understand why this is.
I even included the "default authentication entry point for" just to see if that would help, but it didn't.
Please, how do I resolve this?
After playing around with the http security configuration, I took inspiration from this article (https://www.baeldung.com/spring-security-basic-authentication) and changed it to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic().authenticationEntryPoint(entryPoint);
}
Honestly, I don't know why what I had before wasn't working. Plenty of people have posted that as the solution to problems about entry end points. But I guess maybe something has changed in Spring that I'm not aware of.

Spring Security: Multiple http elements with Multiple AuthenticationManagers

I am struggling with Java Config for Spring Security. I have multiple entry points but I cannot get the AuthenticationManagers provisioned correctly.
My first configuration file is like this:
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/service/**")
.addFilterAfter(requestHeaderAuthenticationFilter(), SecurityContextPersistenceFilter.class)
.authorizeRequests()
.antMatchers("/service/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(preAuthenticatedAuthenticationProvider(null));
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception
{
// Takes the value of the specified header as the user principal
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SECRET_HEADER");
filter.setAuthenticationManager(authenticationManager());
filter.setExceptionIfHeaderMissing(false);
return filter;
}
This all works correctly. When I set a breakpoint in the RequestHeaderAuthenticationFilter I see an AuthenticationManager with one AuthenticationProvider, and that is the preAuthenticatedAuthenticationProvider (not shown because is just a regular old bean).
I also have a special security chain for admin users and the like:
#Configuration
#Order(101)
public class AdminSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authenticationProvider(mainSiteLoginAuthenticationProvider())
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").access("SECRET ADMIN ACCESS EXPRESSION")
.antMatchers("/internal/**").access("SECRET INTERNAL ACCESS EXPRESSION")
.anyRequest().permitAll()
.and()
.formLogin()
.defaultSuccessUrl("/admin/thing")
.loginPage("/login")
.loginProcessingUrl("/do_login")
.defaultSuccessUrl("/admin/thing")
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.and()
.exceptionHandling()
//.authenticationEntryPoint(null) // entry-point-ref="loginEntryPoint"
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // create-session="ifRequired"
.and()
.csrf().disable();
}
This is now working (after a lot of struggle), but if I put a breakpoint in the UsernamePasswordAuthenticationFilter, I see that this filter has a different AuthenticationManager instance, which is provisioned with the mainSiteLoginAuthenticationProvider as expected. However, it has a parent AuthenticationManager which is provisioned with the default DaoAuthenticationProvider that generates a temporary password in the logs:
Using default security password: 47032daf-813e-4da1-a224-b6014a705805
So my questions are:
How can I get both security configs to use the same AuthenticationManager? I thought that the SecurityConfig, being order 100, would create one, and then AdminConfig, being 101, would just use it. But I have been unable to get them to use the same AuthenticationManager.
Failing that, how can I prevent the AuthenticationManger of AdminConfig from generating a parent that has the default DaoAuthenticationProvider?
I am using Spring Boot 1.5.9.RELEASE, which means Spring Security 4.2.3.RELEASE.

In Spring Security 3.2.5, what is causing an infinite loop inside the AuthenticationManager implementation?

I had an interesting situation not long ago which caused an infinite loop (and eventually a stack overflow) in Spring Security's AuthenticationManager. For months, everything worked as expected, but then I decided to transfer my XML configuration to code-only configuration. Here was my basic setup in Java configuration:
#Configuration
#EnableWebMvcSecurity
#ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Disable default configuration
public SecurityConfig() {
super(true);
}
#Autowired
AuthenticationProviderImpl authenticationProvider;
#Autowired
MyAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Ignore requests of resources in security
web.ignoring().antMatchers("/resources/**")
// Ignore requests to authentication
.and().ignoring().antMatchers("/auth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Define main authentication filter
http.addFilterBefore(authenticationTokenProcessingFilter,
UsernamePasswordAuthenticationFilter.class)
// Request path authorization
.authorizeRequests()
.antMatchers("/api/**")
.access("isAuthenticated()")
// Authentication provider
.and()
.authenticationProvider(authenticationProvider)
// Security failure exception handling
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
// Session Management
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// Default security HTTP headers
.and().headers().xssProtection().frameOptions()
.cacheControl().contentTypeOptions();
}
}
However, I soon found out that this configuration causes issues with my AuthenticationProviderImpl (which implements the Spring Security AuthenticationProvider interface). When the implementation's overridden authenticate method throws a BadCredentialsException, the exact same method in that class is called again perpetually until the stack overflows. The good news is that I fixed my configuration by simply overriding configure(AuthenticationManagerBuilder builder) in the SecurityConfig and declaring my implementation of the AuthenticationProvider there instead of in configure(HttpSecurity http). Here is the fixed version:
#Configuration
#EnableWebMvcSecurity
#ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Disable default configuration
public SecurityConfig() {
super(true);
}
#Autowired
AuthenticationProviderImpl authenticationProvider;
#Autowired
MyAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder builder) {
// Configure the authentication manager WITH the authentication
// provider. Not overriding this method causes very bad things to
// happen.
builder.authenticationProvider(authenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
// Ignore requests of resources in security
web.ignoring().antMatchers("/resources/**")
// Ignore requests to authentication
.and().ignoring().antMatchers("/auth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Define main authentication filter
http.addFilterBefore(authenticationTokenProcessingFilter,
UsernamePasswordAuthenticationFilter.class)
// Request path authorization
.authorizeRequests()
.antMatchers("/api/**")
.access("isAuthenticated()")
.and()
// Security failure exception handling
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
// Session Management
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// Default security HTTP headers
.and().headers().xssProtection().frameOptions()
.cacheControl().contentTypeOptions();
}
}
Though I believe my problem is solved with the fixed configuration, I still have no idea why the application was infinitely calling authenticate() when an exception was thrown by my implementation of AuthenticationProvider? I tried stepping through and examining the Spring Security classes, but I was not finding a logical answer. Thanks ahead for your expertise!
A few weeks ago I reproduced this behavior, too, see this thread on stackoverflow.
Dealing with the question I figured out that loops occur when the AuthenticationManager internally iterates through it's list of associated AuthenticationProviders, then finds a custom provider and tries to do the authentication using the provider that has been found. If the provider delegates the authentication back to the AuthenticationManager by calling authenticate(), you are in the loop. I guess your AuthenticationProviderImpl does something like that?
The order of your in the providers inside the java.util.List of the AuthenticationManager matters. The order is given by your configuration, e.g. by doing what you tried at first:
// Authentication provider
.and()
.authenticationProvider(authenticationProvider)
By changing your configuration, you influenced the internally managed list of providers attached to your manager, which in the end will solve your code.

Resources