Closed santam85 closed 1 year ago
Sorry that you are having trouble, @santam85.
With the information shared so far, I'm unable to reproduce the issue. Would you please put together a minimal sample that reproduces the issue?
Yes, will create a repo ASAP...
Here's a minimal reproduction sample. Perhaps some of the security configuration can be further simplified.
https://github.com/santam85/spring-boot-8369
To reproduce the issue, I send a request to any of the authenticated urls with an Authentication: Bearer 123
token.
Thanks, @santam85, that was helpful, I will play around with this more.
What do you need to expose the AuthenticationManager
bean for?
Thanks, @santam85, that was helpful, I will play around with this more.
What do you need to expose the
AuthenticationManager
bean for?
I tried removing it and I couldn't reproduce the problem anymore, so it's definitely related.
I use it in my app to create custom AuthorizationPolicy
(Apache Camel concept).
I followed the instructions described here: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html#authenticationManagerBean--
Thanks, @santam85. I think there is a configuration cycle that may take some time to unravel correctly.
There are a couple of workarounds that you can use in the meantime, which I'll list below.
protected configure(AuthenticationManagerBuilder builder) {
// empty
}
Actually, any configuration here will suit, but since oauth2ResourceServer
and oauth2Login
configure their own authentication, you've got nothing to add here at this point.
UserDetailsService
@Bean
@Override
public UserDetailsService userDetailsService() {
return username -> {
throw new UnsupportedOperationException("unsupported");
}
}
Publishing a UserDetailsService
is shorthand for:
protected configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(...);
}
So, really it's just a corollary to the first workaround.
The reason this came up with the OAuth 2.0 support is that other authentication mechanisms like httpBasic()
typically need a UserDetailsService
, masking the issue.
More specific to your use case, you could consider if there is a smaller Spring Security component that your AuthorizationPolicy
can use. For example, if it is only verifying JWTs, then you could possibly wire the SpringSecurityAuthorizationPolicy
(not sure if that's what you're using) with only the AuthenticationProvider
that it needs, e.g.:
@Bean
AuthorizationPolicy policy(JwtDecoder jwtDecoder) {
SpringSecurityAuthorizationPolicy policy = ...
JwtAuthenticationProvider provider = new JwtAuthenticationProvider(jwtDecoder);
policy.setAuthenticationManager(provider::authenticate);
// ...
return policy;
}
Would you try these and let me know if it resolves the StackOverflowError?
I tried workaround 1 successfully, thanks for your suggestion! Since the api-docs page I am serving from this app has a swagger API testing tool, unfortunately I need to verify both JWTs and session IDs, making solution 3 not viable I guess. There are also 3 different JWT decoder beans in my context, since I am using the discovery document and have all validation methods enabled, so not sure how to cover all 3 cases there.
I'll be watching the ticket for updates, thanks for the quick answer!
Looks like the error has been there for a while: http://blog.iampfac.com/blog/2015/02/12/stackoverflow-error-with-spring-security-authentication/
It was fixed also using with the workaround 1)
@jzheaux any updates on this?
I'm afraid not, @santam85, though thanks for the bump. We'll be taking a look at deprecating WebSecurityConfigurerAdapter
in the near future, and it seems like something like this could be addressed at that point.
unfortunately I need to verify both JWTs and session IDs ... 3 different JWT decoder beans
Can you clarify these points further? AuthenticationManager
is separated from the HTTP layer, so I'm not clear on how your manager is verifying session IDs. Also, the related provider that verifies JWTs must be wired with one JwtDecoder
, so it seems that somehow your application is picking one of the three; Spring Security doesn't resolve this for you.
The endpoints protected by
.oauth2Login()
trigger an OAuth flow ultimately providing the client a Session ID cookie, but since the paths I use for the resource server overlap, I can use the same to authenticate to the APIs. I guess the order of the providers set by SpringBoot coincidentally matches my use case?
Most often, each provider works with a unique Authentication
instance. For example, JwtAuthenticationProvider
in Resource Server uses BearerTokenAuthenticationToken
, but OAuth2LoginAuthenticationProvider
in Client uses OAuth2LoginAuthenticationToken
. Order often doesn't cause a problem since typically only one provider would match for a given authentication mechanism.
When your custom AuthorizationPolicy
is calling the AuthenticationManager#authenticate
, what type of Authentication
is it sending? Have you already tried to construct your own AuthenticationManager
that contains only the AuthenticationProvider
that matches that Authentication
type?
This is the class performing the call to AuthenticationManager#authenticate
:
https://github.com/apache/camel/blob/master/components/camel-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityAuthorizationPolicy.java
I use instances of this as beans and at creation I autowire whatever AuthenticationManager SpringBoot creates. I haven't tried creating my own, because I need to validate both the oauth2Login
and the resourceServer
providers.
@Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } Use this
// empty }
Hola buenas tengo un problema nose si me pueda ayudar tengo un error de referencia circular que sucede en mi "securityConfig" con mi controller logincontroller.package idat.pe.Examen.Security;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import idat.pe.Examen.JWT.JwtRequestFilter; import idat.pe.Examen.JWT.JwtTokenUtil; import idat.pe.Examen.Model.JWT.JwtAuthenticationEntryPoint; import idat.pe.Examen.Service.UserServiceImpl; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userserviceimpl;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return new JwtAuthenticationEntryPoint();
}
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//en database
auth.userDetailsService(userserviceimpl).passwordEncoder(passwordEncoder());;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/Logueo/login").permitAll()
.antMatchers("/Logueo/crear").permitAll()
.antMatchers("/Logueo/tokenInfo").permitAll()
.antMatchers("/Rol/registrar").permitAll()
.antMatchers("/Citas/listar").hasAuthority("PACIENTE")
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint())
.and()
.headers()
.frameOptions()
.sameOrigin();
}
} y este es mi logueocontroller .: package idat.pe.Examen.Controller;
import java.util.Date; import java.util.List; import java.util.Map;
import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import idat.pe.Examen.DTO.PacienteDto; import idat.pe.Examen.JWT.JwtTokenUtil; import idat.pe.Examen.Model.JWT.JwtResponse; import idat.pe.Examen.Model.JWT.LoginRequest; import idat.pe.Examen.Service.PacienteService; import idat.pe.Examen.Service.UserService;
@RestController @RequestMapping("/Logueo") public class LogueoController { @Autowired private PacienteService pacienteService; @Autowired private UserService userservice; @Autowired @Lazy private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService;
@PostMapping("/crear")
public ResponseEntity<Void> crearUsuarioYPaciente(@RequestBody PacienteDto pacienteDto) {
userservice.crearUsuarioYPaciente(pacienteDto);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws AuthenticationException {
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getEmail());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
@PostMapping("/tokenInfo")
public String getTokenInfo(@RequestBody Map<String, String> tokenMap) {
String authToken = tokenMap.get("token");
String email = jwtTokenUtil.getEmailFromToken(authToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
String emailuser = jwtTokenUtil.getEmailFromToken(authToken);
Date expirationDate = jwtTokenUtil.getExpirationDateFromToken(authToken);
List<String> roles = jwtTokenUtil.getClaimFromToken(authToken, claims -> claims.get("roles", List.class));
JSONObject tokenInfo = new JSONObject();
tokenInfo.put("email", emailuser);
tokenInfo.put("expirationDate", expirationDate);
tokenInfo.put("roles", roles);
return tokenInfo.toString();
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build().toString();
}
}
} yo pienso que mi error es en @Autowired@Lazy private AuthenticationManager authenticationManager; le puse @Lazy para probar si es que se puede resolver el error por el tiempo de carga pero no funciona si me podrian ayudar se los agradeceria mucho
Closing this in favor of https://github.com/spring-projects/spring-security/issues/12343 since that issue has the explanation for the most common case of StackOverflowError
and how to fix it.
Summary
When using oauth2ResourceServer, I am getting a stackoverflow error if the application receives an invalid token
Actual Behavior
I see this error in the console and i get a 500 statusCode to my request
Expected Behavior
I get no stackTrace and a 401/403
Configuration
I could reproduce by using this configuration:
and sending an Authorization header of "Bearer 1234"
Version
5.2.3.RELEASE