Understanding Spring Boot's Oauth2 starter - spring

I started off looking at the Oauth2 starter project and minimal configuration.
https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/java/demo/Application.java
All the examples either use in memory configuration or jdbc configuration for storing client roles (e.g ClientDetailsServiceConfigurer) . In my case the details should come in LDAP. So I have two questions.
How do override the default to go to ldap instead of memory or jdbc.
In general , where how do I unravel the Spring Boot thread and read the starter source code and how to change default config ? All I see is a high level annotation.
org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
This indirection in Spring Boot makes it extremely difficult to follow and scant documentation doesn't help. Or maybe I am missing something?
thanks !!! this has been bugging me for a while.

To implement Oauth2 with LDAP, you may follow this tutorial : https://raymondhlee.wordpress.com/2015/12/05/oauth2-authorization-server-with-spring-security.
You may also take a look a this other question: spring-security-oauth2 2.0.7 refresh token UserDetailsService Configuration - UserDetailsService is required
As for your other question "I want to follow the request and see what components get invoked and when": I suggest you add logging.
(1) Add logging in every method
(2) Set log level for security package in application.properties:
logging.level.org.springframework.security=DEBUG
(3) Add CommonsRequestLoggingFilter:
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
LOGGER.info("Creating CommonsRequestLoggingFilter");
CommonsRequestLoggingFilter crlf = new CommonsRequestLoggingFilter();
crlf.setIncludeClientInfo(true);
crlf.setIncludeQueryString(true);
crlf.setIncludePayload(true);
return crlf;
}
(4) Add log level for CommonsRequestLoggingFilter (in application.properties):
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
For the OAuth/LDAP tutorial, here's the notable parts (quoted from https://raymondhlee.wordpress.com/2015/12/05/oauth2-authorization-server-with-spring-security):
Authorization Server Configuration Below is my implementation of the
AuthorizationServerConfigurerAdapter. The database schema for JDBC
client details and token services can be found in here.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new JdbcTokenStore(dataSource)).authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
Login Security Configuration Below is the security configuration
handling user authorization.
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE) // note 1
public class LoginConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.domain}")
private String DOMAIN;
#Value("${ldap.url}")
private String URL;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel().anyRequest().requiresSecure();
// Only requests matching regex are handled by this security configurer
http.requestMatchers().regexMatchers("/login", "/login.+", "/oauth/.+", "/j_spring_security_check", "/logout"); //
AuthenticationEntryPoint entryPoint = entryPoint();
http.exceptionHandling().authenticationEntryPoint(entryPoint);
http.formLogin(); // note 3i
http.addFilter(usernamePasswordAuthenticationFilter());
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().antMatchers("/oauth/**").authenticated();
http.authorizeRequests().antMatchers("/j_spring_security_check").anonymous().and().csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { // note 4
authManagerBuilder.parentAuthenticationManager(authenticationManager());
}
protected AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
private AuthenticationEntryPoint entryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
private UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() {
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager();
AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?login_error=true");
filter.setAuthenticationFailureHandler(failureHandler);
return filter;
}
}

Related

Evaluate Web Services Interceptor Before Spring Security Filter Chain

I have a SOAP-based web services application which is leveraging Spring Web Services (and Spring WS Security) as well as Spring Security. I am using a custom AbstractWsSecurityInterceptor to authenticate the incoming requests (using an injected AuthenticationManager) and to add the successful authentications to the SecurityContext. I then have a custom AcessDecisionManager which is using a custom WebSecurityExpressionHandler to validate a certain property from the principal added to the context by the interceptor.
Below is an idea of what my configuration files look like:
SecurityConfig.java:
#Getter
#Setter
#Configuration
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AccessDecisionManager customAccessDecisionManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
cors()
.and().csrf().disable()
.authorizeRequests()
.accessDecisionManager(customAccessDecisionManager)
.antMatchers(GET, "/actuator/**").permitAll()
.anyRequest().access("customAccessMethod()")
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
WebServiceConfig.java:
#EnableWs
#Configuration
#RequiredArgsConstructor
public class WebServiceConfig extends WsConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}
...
...
#Bean
AbstractWsSecurityInterceptor customAuthenticationInterceptor() {
return new CustomAuthenticationInterceptor(authenticationManager);
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(customAuthenticationInterceptor());
}
}
The issue with this setup is that the Spring Security filter chain is evaluated first and fails the authentication because the AccessDecisionManager is evaluated before the request has a chance to enter the custom AbstractWsSecurityInterceptor and place the authentication in the SecurityContext.
Is there any way to evaluate the interceptor and handling of the request on the Web Services and WS Security side of things before it then hits the Spring Security filter chain? Is this a possibility?
Thank you in advance for the help!

disable spring formlogin and basic auth

I have the following spring boot 2.0 config but I am still getting the basic auth login screen. I DO NOT want to disable all spring security like almost every post on the internet suggests. I only want to stop the form login page and basic auth so I can use my own.
I have seen all the suggestions with permitAll and exclude = {SecurityAutoConfiguration.class} and a few others that I can't remember anymore. Those are not what I want. I want to use spring security but I wan my config not Spring Boots. Yes I know many people are going to say this is a duplicate but I disagree because all the other answers are to disable spring security completely and not just stop the stupid login page.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class CustomSecurity extends WebSecurityConfigurerAdapter {
private final RememberMeServices rememberMeService;
private final AuthenticationProvider customAuthProvider;
#Value("${server.session.cookie.secure:true}")
private boolean useSecureCookie;
#Inject
public CustomSecurity(RememberMeServices rememberMeService, AuthenticationProvider customAuthProvider) {
super(true);
this.rememberMeService = rememberMeService;
this.bouncerAuthProvider = bouncerAuthProvider;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/**").antMatchers("/webjars/**").antMatchers("/swagger-resources/**")
.antMatchers("/swagger-ui.html");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable().formLogin().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).headers().frameOptions().disable();
http.authenticationProvider(customAuthProvider).authorizeRequests().antMatchers("/health").permitAll()
.anyRequest().authenticated();
http.rememberMe().rememberMeServices(rememberMeService).useSecureCookie(useSecureCookie);
http.exceptionHandling().authenticationEntryPoint(new ForbiddenEntryPoint());
}
}
If you want to redirect to your own login page, i can show your sample code and configuration
remove the http.httpBasic().disable().formLogin().disable();, you should set your own login page to redirect instead of disable form login
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/my_login").permitAll().and().authorizeRequests().anyRequest().authenticated();
http.formLogin().loginPage("/my_login");
}
then create your own LoginController
#Controller
public class LoginController {
#RequestMapping("/my_login")
public ModelAndView myLogin() {
return new ModelAndView("login");
}
}
you can specified the login with thymeleaf view resolver

Authorization Server

I am using Spring Rest + OAUTH2 + React in my project. For creating authorization server I got some code from an example. But the problem is I am not able to understand the code. Can someone explain me this code:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "suleman123".toCharArray())
.getKeyPair("resourcekey");
converter.setKeyPair(keyPair);
return converter;
}
/**
* This method configure client details service by using inMemory implementation. JDBC Implementation can also used
*/
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme") // client id
.secret("acmesecret") // required for trusted clients
.authorizedGrantTypes("authorization_code", "refresh_token",
"password") // Grant types that are authorized for the client to use
.scopes("openid") // scope to which the client is limited
.autoApprove(true);
}
/**
* This method configure the grant types. By default all grant types are supported except password
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(
jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
}
Spring boot works with auto-configuration. What you see here is someone extending a spring auto-configuration class in order to customize it to his needs.
TL;DR:
They set a JWT based oauth2 authorization server.
Detailed Answer:
In this case, by combining #EnableAuthorizationServer and extending AuthorizationServerConfigurerAdapter, you can enable, manipulate and modify your authorization server.
In this example, instead of using normal string tokens, they want to use JWT. For that reason the first bean initialized is JwtAccessTokenConverter. More on JWT.
configure(ClientDetailsServiceConfigurer clients) - They configure one in-memory client to use in the application.
configure(AuthorizationServerEndpointsConfigurer endpoints) - They configure the default authenticationManager as the one initialized by spring and injected in the top of your configuration class and set the accessTokenConverter to use jwtAccessTokenConverter mentioned in #1. Doing that will allow them to generate JWT tokens when queering for a new token.
configure(AuthorizationServerSecurityConfigurer oauthServer) - They set all endpoints to allow access to everything when there is a token authenticated user (oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");).

Spring Security Remember Me Does not work With Spring boot 1.5.2, Spring 4.3, Spring Security 4.2 [duplicate]

Right after registration (sign up) I'm logging in my user programmatically via Spring Security:
public register(HttpServletRequest request, String user, String password) {
...
request.login(user, password);
}
This works fine, but it doesn't create the remember-me cookie (although with interactive login the cookie is created fine).
Now I've read in this and this answer, that you have to wire in the implementation of RememberMeServices (I use PersistentTokenBasedRememberMeServices) and then call onLoginSuccess. I haven't been successful to autowire PersistentTokenBasedRememberMeServices.
How to make this work? Is this the right way? Why Spring Security doesn't offer a more convenient way?
P.S.: This is an excerpt from my configuration:
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.tokenRepository(new MyPersistentTokenRepository())
.rememberMeCookieName("rememberme")
.tokenValiditySeconds(60 * 60 * 24)
.alwaysRemember(true)
.useSecureCookie(true)
.and()
....
...
}
}
You didn't mention the Spring version. Below configuration will work with Spring 4 but you can modify it for other version. In your WebSecConf class autowire PersistentTokenRepository and UserDetailsService interfaces. Add Bean to get PersistentTokenBasedRememberMeServices instance.
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
#Autowired
PersistentTokenRepository persistenceTokenRepository;
#Autowired
UserDetailsService userDetailsService;
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.tokenRepository(persistenceTokenRepository)
.rememberMeCookieName("rememberme")
.tokenValiditySeconds(60 * 60 * 24)
.alwaysRemember(true)
.useSecureCookie(true)
.and()
....
...
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices persistenceTokenBasedservice = new PersistentTokenBasedRememberMeServices("rememberme", userDetailsService, persistenceTokenRepository);
persistenceTokenBasedservice.setAlwaysRemember(true);
return persistenceTokenBasedservice;
}
}
Now in your Controller or class where you are doing programmatic login, autowire PersistentTokenBasedRememberMeServices and add below code inside the method to invoke loginSuccess method.
#Autowired
PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
persistentTokenBasedRememberMeServices.loginSuccess(request, response, auth);
}
I've stumbled on this issue and struggled a bit to get everything working correctly, for future reference this is how to set things up.
Define a RememberMeService bean configured to your needs.
Use TokenBasedRememberMeServices if you want a simple hash based token system or PersistentTokenBasedRememberMeServices if you'd rather persist the tokens to database. Both solutions are described in further details here : https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/remember-me.html
Please note that the constructor first argument is not the cookie name but the key used to validate remember-me tokens.
#Configuration
public class SecurityBeans {
#Autowire
PersistentTokenRepository persistenceTokenRepository;
#Autowired
UserDetailsService userDetailsService;
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices persistenceTokenBasedservice = new TokenBasedRememberMeServices("remember-me-key", userDetailsService, persistenceTokenRepository);
persistenceTokenBasedservice.setCookieName("rememberme");
persistenceTokenBasedservice.setTokenValiditySeconds(60 * 60 * 24);
persistenceTokenBasedservice.setAlwaysRemember(true);
persistenceTokenBasedservice.setUseSecureCookie(true);
return persistenceTokenBasedservice;
}
}
You should inject the RememberMeService directly when configuring HttpSecurity. You also have to configure the exact same key as defined in your RememberMeService because the configurer also sets up the RememberMeAuthenticationProvider which checks that the remember-me token key generated by RememberMeService is correct.
#Configuration
#EnableWebSecurity
public class WebSecConf extends WebSecurityConfigurerAdapter {
#Autowired
RememberMeServices rememberMeServices;
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.rememberMeServices(rememberMeServices)
.key("remember-me-key")
.and()
....
...
}
}
And finally you should invoke RememberMeService's loginSuccess in your method doing the programmatic login as described in abaghel's answer.

Add custom UserDetailsService to Spring Security OAuth2 app

How do I add the custom UserDetailsService below to this Spring OAuth2 sample?
The default user with default password is defined in the application.properties file of the authserver app.
However, I would like to add the following custom UserDetailsService to the demo package of the authserver app for testing purposes:
package demo;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Service;
#Service
class Users implements UserDetailsManager {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password;
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
if (username.equals("Samwise")) {
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
password = "TheShire";
}
else if (username.equals("Frodo")){
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
password = "MyRing";
}
else{throw new UsernameNotFoundException("Username was not found. ");}
return new org.springframework.security.core.userdetails.User(username, password, auth);
}
#Override
public void createUser(UserDetails user) {// TODO Auto-generated method stub
}
#Override
public void updateUser(UserDetails user) {// TODO Auto-generated method stub
}
#Override
public void deleteUser(String username) {// TODO Auto-generated method stub
}
#Override
public void changePassword(String oldPassword, String newPassword) {
// TODO Auto-generated method stub
}
#Override
public boolean userExists(String username) {
// TODO Auto-generated method stub
return false;
}
}
As you can see, this UserDetailsService is not autowired yet, and it purposely uses insecure passwords because it is only designed for testing purposes.
What specific changes need to be made to the GitHub sample app so that a user can login as username=Samwise with password=TheShire, or as username=Frodo with password=MyRing? Do changes need to be made to AuthserverApplication.java or elsewhere?
SUGGESTIONS:
The Spring OAuth2 Developer Guide says to use a GlobalAuthenticationManagerConfigurer to configure a UserDetailsService globally. However, googling those names produces less than helpful results.
Also, a different app that uses internal spring security INSTEAD OF OAuth uses the following syntax to hook up the UserDetailsService, but I am not sure how to adjust its syntax to the current OP:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private Users users;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(users);
}
}
I tried using #Autowire inside the OAuth2AuthorizationConfig to connect Users to the AuthorizationServerEndpointsConfigurer as follows:
#Autowired//THIS IS A TEST
private Users users;//THIS IS A TEST
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.userDetailsService(users)//DetailsService)//THIS LINE IS A TEST
;
}
But the Spring Boot logs indicate that the user Samwise was not found, which means that the UserDetailsService was not successfully hooked up. Here is the relevant excerpt from the Spring Boot logs:
2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9] o.s.s.a.dao.DaoAuthenticationProvider :
User 'Samwise' not found
2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9]
w.a.UsernamePasswordAuthenticationFilter :
Authentication request failed:
org.springframework.security.authentication.BadCredentialsException:
Bad credentials
What else can I try?
I ran into a similar issue when developing my oauth server with Spring Security. My situation was slightly different, as I wanted to add a UserDetailsService to authenticate refresh tokens, but I think my solution will help you as well.
Like you, I first tried specifying the UserDetailsService using the AuthorizationServerEndpointsConfigurer, but this does not work. I'm not sure if this is a bug or by design, but the UserDetailsService needs to be set in the AuthenticationManager in order for the various oauth2 classes to find it. This worked for me:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
Users userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// other stuff to configure your security
}
}
I think if you changed the following starting at line 73, it may work for you:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
You would also of course need to add #Autowired Users userDetailsService; somewhere in WebSecurityConfigurerAdapter
Other things I wanted to mention:
This may be version specific, I'm on spring-security-oauth2 2.0.12
I can't cite any sources for why this is the way it is, I'm not even sure if my solution is a real solution or a hack.
The GlobalAuthenticationManagerConfigurer referred to in the guide is almost certainly a typo, I can't find that string anywhere in the source code for anything in Spring.
My requirement was to get a database object off the back of the oauth2 email attribute. I found this question as I assumed that I need to create a custom user details service. Actually I need to implment the OidcUser interface and hook into that process.
Initially I thought it was the OAuth2UserService, but I've set up my AWS Cognito authentication provider so that it's open id connect..
//inside WebSecurityConfigurerAdapter
http
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(new CustomOidcUserServiceImpl());
...
public class CustomOidcUserServiceImpl implements OAuth2UserService<OidcUserRequest, OidcUser> {
private OidcUserService oidcUserService = new OidcUserService();
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser oidcUser = oidcUserService.loadUser(userRequest);
return new CustomUserPrincipal(oidcUser);
}
}
...
public class CustomUserPrincipal implements OidcUser {
private OidcUser oidcUser;
//forward all calls onto the included oidcUser
}
The custom service is where any bespoke logic can go.
I plan on implementing UserDetails interface on my CustomUserPrincipal so that I can have different authentication mechanisms for live and test to facilitate automated ui testing.
I ran into the same issue and originally had the same solution as Manan Mehta posted. Just recently, some version combination of spring security and spring oauth2 resulted in any attempt to refresh tokens resulting in an HTTP 500 error stating that UserDetailsService is required in my logs.
The relevant stack trace looks like:
java.lang.IllegalStateException: UserDetailsService is required.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:463)
at org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper.loadUserDetails(UserDetailsByNameServiceWrapper.java:68)
at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:103)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150)
You can see at the bottom that the DefaultTokenServices is attempting to refresh the token. It then calls into an AuthenticationManager to re-authenticate (in case the user revoked permission or the user was deleted, etc.) but this is where it all unravels. You see at the top of the stack trace that UserDetailsServiceDelegator is what gets the call to loadUserByUsername instead of my beautiful UserDetailsService. Even though inside my WebSecurityConfigurerAdapter I set the UserDetailsService, there are two other WebSecurityConfigurerAdapters. One for the ResourceServerConfiguration and one for the AuthorizationServerSecurityConfiguration and those configurations never get the UserDetailsService that I set.
In tracing all the way through Spring Security to piece together what is going on, I found that there is a "local" AuthenticationManagerBuilder and a "global" AuthenticationManagerBuilder and we need to set it on the global version in order to have this information passed to these other builder contexts.
So, the solution I came up with was to get the "global" version in the same way the other contexts were getting the global version. Inside my WebSecurityConfigurerAdapter I had the following:
#Autowired
public void setApplicationContext(ApplicationContext context) {
super.setApplicationContext(context);
AuthenticationManagerBuilder globalAuthBuilder = context
.getBean(AuthenticationManagerBuilder.class);
try {
globalAuthBuilder.userDetailsService(userDetailsService);
} catch (Exception e) {
e.printStackTrace();
}
}
And this worked. Other contexts now had my UserDetailsService. I leave this here for any brave soldiers who stumble upon this minefield in the future.
For anyone got UserDetailsService is required error when doing refresh token, and you confirm you already have UserDetailsService bean.
try add this:
#Configuration
public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter {
private UserDetailsService userDetailsService;
public GlobalSecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
This is my try and error, maybe not work for you.
By the way, if you give up "guess" which bean will pick by spring, you can extends AuthorizationServerConfigurerAdapter and WebSecurityConfigurerAdapter and config all stuff by yourself, but I think it's lose power of spring autoconfig.
Why I need config everything if I just need customize some config?

Resources