Spring Security java.lang.StackOverflowError exception after all providers - java

Environment:
Spring 4.1.6
Spring Security 4.0.1
I have 2 authentication providers - one that hits ActiveDirectory, and then one that hits a custom database provider I've created. Logging in as a user that is in either of those environments works perfectly. The user is authenticated and the app continues.
However, when an invalid user is entered and neither provider is able to authenticate, I get this exception on the page:
java.lang.StackOverflowError
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:393)
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:394)
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:394)
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:394)
Here is my WebSecurityConfigurerAdapter configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().loginPage("/login").failureUrl("/login?error").defaultSuccessUrl("/overview").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll()
.and()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/favicon.ico").permitAll()
.antMatchers("/**").hasRole("AUTH");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService());
authManagerBuilder
.authenticationProvider(databaseAuthenticationProvider())
.userDetailsService(userDetailsService());
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
UserDetailsContextMapper contextMapper = new MyUserDetailsContextMapper();
return contextMapper;
}
#Bean
public MyDatabaseAuthenticationProvider databaseAuthenticationProvider() {
return new MyDatabaseAuthenticationProvider();
}
There's really nothing special in the "MyDatabaseAuthenticationProvider" or "MyUserDetailsContextMapper" classes except for some custom logic for mapping and looking up users.
The app doesn't crash, but obviously not the page I want to show the user. :)
Any thoughts on how I can get rid of the StackOverflowError?

I had the same problem, this was the solution for me:
#Override
protected void configure(AuthenticationManagerBuilder
authManagerBuilder) throws Exception {
...
.userDetailsService(userDetailsService());
...
}
The problem where the brackets after userDetailsService - removed them and it works as expected.
From your code snippet I can't be sure where you get the userDetailsService from, for me I had it #Autowired.

I had the same problem and another solution worked in my case. The difference is, that I had simply username and password, database authentication.
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
#Bean
UserDetailsService userDetailsService() {
return new UserDetailsService();
}
The fix was to add the #Override annotation to the #Bean annotated method:
#Bean
#Override
UserDetailsService userDetailsService() {
return new UserDetailsService();
}

Related

Spring Boot secure actuator endpoint with basic auth while securing other endpoints with Oath [duplicate]

I am trying to set up multiple WebsecurityConfigurerAdapter for my project where the spring boot actuator APIs are secured using basic auth and all other endpoints are authenticated using JWtAuthentication. I am just not able to make it work together, only the config with the lower order works. I am using Spring Boot 2.1.5.RELEASE
Security Config One with JWT Authenticator
#Order(1)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] AUTH_WHITELIST = {
"/docs/**",
"/csrf/**",
"/webjars/**",
"/**swagger**/**",
"/swagger-resources",
"/swagger-resources/**",
"/v2/api-docs"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/abc/**", "/abc/pdf/**").hasAuthority("ABC")
.antMatchers("/ddd/**").hasAuthority("DDD")
.and()
.csrf().disable()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(new GrantedAuthoritiesExtractor());
}
}
The basic Auth config with username/password
#Order(2)
#Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
/* #Bean
public UserDetailsService userDetailsService(final PasswordEncoder encoder) {
final InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User
.withUsername("user1")
.password(encoder.encode("password"))
.roles("ADMIN")
.build()
);
return manager;
}
#Bean PasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/**").hasRole("ADMIN")
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("password").authorities("ADMIN");
}
}
I have been trying to make it work for many days but cannot make both of them work together. If i swap the order, only basic auth works and not the JWT Auth Manager.
I have gone through a lot of SOF Questions, like
[https://stackoverflow.com/questions/40743780/spring-boot-security-multiple-websecurityconfigureradapter][1]
[https://stackoverflow.com/questions/52606720/issue-with-having-multiple-websecurityconfigureradapter-in-spring-boot][1]
[https://github.com/spring-projects/spring-security/issues/5593][1]
[https://www.baeldung.com/spring-security-multiple-entry-points][1]
Nothing seems to be working, is this a known issue in Spring?
To use multiple WebsecurityConfigurerAdapter, you need restrict them to specific URL patterns using RequestMatcher.
In your case you can set a higher priority for ActuatorSecurityConfig and limit it only to actuator endpoints:
#Order(-1)
#Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/actuator/**")
.and()
.authorizeRequests().anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}

Why doesn't RestController pick up request after AuthenticationFilter has finished

I have a working Spring Boot with Spring Security. Everything mostly works. What I'm not understanding is why the RestController never fires following the filter authorizing the request.
In other words, I have a rest controller set up to accept POST requests from /foo/bar and I have an AuthenticationFilter set up to first verify the users credentials before performing what the user requested.
Given that my RestController never fires, I've had to implement my code in the Success Handler, but my code belongs in the Controller instead.
I've attempted to debug this by stepping through Spring Security code, but nothing appears to suggest it would skip my RestController.
#RestController
#RequestMapping("foo")
public class FooController {
#PostMapping("bar") // this never executes
public ResponseEntity<FooResponse> foobar(#RequestBody Credentials creds) {
return new ResponseEntity<>(new FooResponse("baz"), HttpStatus.OK);
}
}
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationFilter authenticationTokenFilter() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandlerImpl());
return filter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(accountService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/foo/bar").permitAll()
.and()
.addFilter(new AuthorizationFilter(properties, authenticationManager(), tokenService)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public AuthenticationFilter() {
super("/foo/bar");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
Credentials creds = new ObjectMapper().readValue(request.getInputStream(), Credentials.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
}
}
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// do nothing and prevent redirect to /login, /logout, etc
}
}
You are using http.cors(). Have you configured it elsewhere? If not, it probably considers you are not authenticated. Check this link out: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/cors.html
I would suggest you to remove it if you are not going to use it
Also you have here an example of an easy security configuration: https://www.baeldung.com/spring-security-login
From my point of view, removing cors() should fix your problem (or configuring it the right way :)

Toggle Spring Security for the requests with particular Request Header

I am trying to toggle/bypass/disable Spring Security (Authentication and Authorization) for all the requests having particular Request Header.
For example, if a request url is hit with that Request Header, Spring Security should be bypassed, if not it should not be bypassed.
For this, I am using following requestMatchers Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
My remaining Security Config is :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
#ConditionalOnProperty (name = "security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProps securityProps;
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
#Autowired
private MyCORSFilter myCORSFilter;
public SecurityConfig() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(myCORSFilter, SessionManagementFilter.class)
.addFilterBefore(requestHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
.antMatchers(HttpMethod.OPTIONS, securityProps.getNoAuthOptionsPattern()).permitAll()
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint);
}
#Autowired
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preauthAuthProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
public RequestHeaderAuthenticationFilter requestHeaderFilter() throws Exception {
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
requestHeaderAuthenticationFilter.setPrincipalRequestHeader(MySecurityConstants.LOGIN_HEADER);
requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
requestHeaderAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof MySecurityException) {
myAuthenticationEntryPoint.commenceMySecurityException(request, response, (MySecurityException) exception);
} else if (exception instanceof UsernameNotFoundException) {
myAuthenticationEntryPoint.commenceUsernameNotFoundException(request, response,
(UsernameNotFoundException) exception);
} else if (exception instanceof PreAuthenticatedCredentialsNotFoundException) {
myAuthenticationEntryPoint.commence(request, response, exception);
}
}
});
return requestHeaderAuthenticationFilter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return authProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper()
throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<>();
wrapper.setUserDetailsService(ivyUserDetailsService);
return wrapper;
}
}
With the above settings, I am unable to disable/bypass Spring Security and I am getting the AuthenticationCredentialsNotFoundException exception:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Can anyone help me by identifying what am I doing wrong? Is my approach correct or I need to do something else to achieve this?
EDIT :
I am getting this exception in org.springframework.security.access.intercept.AbstractSecurityInterceptor class in beforeInvocation() method where it tries to get the authentication object from SecurityContextHolder. AbstractSecurityInterceptor is invoked by its subclass MethodSecurityInterceptor which is invoked from my Spring Controller which is annotated with #PreAuthorize.
I think your bypass is working fine. Its skipping the check.
The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter.
So when you skip security filter SecurityContext is not set yet thus the error
You can do something like this to set it manually for your Custom Header Case
try {
SecurityContext ctx = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(ctx);
ctx.setAuthentication(event.getAuthentication());
} finally {
SecurityContextHolder.clearContext();
}
Edit 1:
Answering all the queries.
But if thats the case, then I guess all GET call should also have
failed, but my GET calls are working fine.
Since you have added this line All your GET calls are skipped from security check.
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
where can I add the code you have mentioned? Any particular filter or
somewhere else ?
I have done something like this in a Filter.
Refer Here
Look at TokenAuthenticationFilter Class in Answer. Where am manually setting.
Note: Its JWT implementation but good to refer
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
What is event in your answer?
I just got that case from Some Answer, cant find its link now. But you can setAuthentication like this or like above
Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
authentication.setAuthenticated(true);
context.setAuthentication(authentication);

Server connection lost after successful login with Spring Security

After successful login using Spring security I will loose connection and my client is trying to reconnect. This is my config file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select nickname,password, true from client where nickname=?")
.authoritiesByUsernameQuery(
"select username, role from user_roles where username=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login")
.permitAll()
.antMatchers("/*")
.access("hasRole('ROLE_USER')");
http.formLogin()
.defaultSuccessUrl("/", true);
}
#Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
I am using vaadin flow. After successful login my base page will show but right after that it will loose connection and it will start reconnecting in an endless loop.
This is the header of my root page.
#Route(value = "")
public class BasePageView extends VerticalLayout
Thank you for any help.
Add this http.csrf().disable() to your configure method in SecurityConfig.
I do not know exactly why it is not working without that. If it is Vaadin Flow specific or not. Maybe someone else will help us to understand.
I think you should override super class method
protected void configure(AuthenticationManagerBuilder auth) throws Exception
instead of public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception
For instance this sample works for me :
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("test").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login")
.permitAll()
.antMatchers("/*")
.access("hasRole('ROLE_USER')");
http.formLogin()
.defaultSuccessUrl("/", true);
}
}
alsoe double check that your role name is correct in your database, and I think you should set antMatchers to /** instead of /* to handle multi-level url

Redirect using spring ldap login page causes extra url path which is incorrect

If I am using spring security to connect to ldap when I connect lets say to the url: www.serverAdress/myapp/
I should be redirected to
www.serverAdress/myapp/login
but instead I am redirected to
www.serverAddress/myappmyapp/login
I am not sure how the extra myapp is added it is also obtained if a redirect is used within the return of the views.
Edit added the configuration
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider("domain", "ldap://url", "rootDN custom");
authenticationProvider.setSearchFilter("customfilter");
authenticationProvider.setConvertSubErrorCodesToExceptions(true);
authenticationProvider.setUseAuthenticationRequestCredentials(true);
return authenticationProvider;
}
}

Categories