I am trying to generate a token with grant_type password and when I get to userdetails service , my client id is getting passed as user name . I am using custom client details service and user details service , all in one class. I am pretty sure I am doing some wrong . My client details and user details are retrieved from mongo.
Auth server config
private @Autowired OAuthUserService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.withClientDetails(clientDetailsService);
}
ClientDetailsUserDetailService
@Component
public class OAuthUserService implements ClientDetailsService,
UserDetailsService {
private @Autowired OAuthClientDetailsRepository clientDetailsDao;
private @Autowired OAuthUserRepository userDetailsDao;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException
{
OAuthUser user=userDetailsDao.findByLogin(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
}
return new UserRepositoryUserDetails(user);
}
private final static class UserRepositoryUserDetails extends OAuthUser implements UserDetails
{
/**
*
*/
private static final long serialVersionUID = -4024636858150373536L;
private UserRepositoryUserDetails() {
}
private UserRepositoryUserDetails(OAuthUser user) {
super(user);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList(getRoles());
}
@Override
public String getUsername() {
return getLogin();
}
@Override
public boolean isAccountNonExpired() {
return isEnabled();
}
@Override
public boolean isAccountNonLocked() {
return isEnabled();
}
@Override
public boolean isCredentialsNonExpired() {
return isEnabled();
}
@Override
public boolean isEnabled() {
return true;
}
}
@Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException
{
OAuthClientDetails oauthClientDetails= clientDetailsDao.findByClientId(clientId);
if (oauthClientDetails == null) {
throw new UsernameNotFoundException(String.format("ClientDetails %s does not exist!", clientId));
}
BaseClientDetails clientDetails=new BaseClientDetails();
clientDetails.setClientId(oauthClientDetails.getClientId());
clientDetails.setScope(StringUtils.commaDelimitedListToSet(oauthClientDetails.getScope()));
clientDetails.setAuthorizedGrantTypes(StringUtils.commaDelimitedListToSet(oauthClientDetails.getAuthorizedGrantTypes()));
clientDetails.setAuthorities(AuthorityUtils.createAuthorityList(StringUtils.commaDelimitedListToStringArray(oauthClientDetails.getAuthorities())));
clientDetails.setAccessTokenValiditySeconds(oauthClientDetails.getAccessTokenValidity());
clientDetails.setClientSecret(oauthClientDetails.getClientSecret());
clientDetails.setAdditionalInformation(oauthClientDetails.getAdditionalInformation());
if(oauthClientDetails.isAutoApprove())
clientDetails.setAutoApproveScopes(StringUtils.commaDelimitedListToSet(oauthClientDetails.getAutoApproveScopes()));
else
clientDetails.setAutoApproveScopes(new HashSet<String>());
clientDetails.setAccessTokenValiditySeconds(oauthClientDetails.getAccessTokenValidity());
clientDetails.setRegisteredRedirectUri(StringUtils.commaDelimitedListToSet(oauthClientDetails.getWebserverRedirectURL()));
clientDetails.setResourceIds(StringUtils.commaDelimitedListToSet(oauthClientDetails.getResourceIds()));
clientDetails.setAdditionalInformation(oauthClientDetails.getAdditionalInformation());
return clientDetails;
}
implements ClientDetailsService, UserDetailsService
Why do you feel the need to do that?
The /token endpoint is protected by Basic auth (by default, and according to the recommendations in the spec) using client_id and client_secret as the credentials. So you should expect to see client_id as a "username" (an AuthentcationManager is provided by the framework so in most cases you only have to provide a ClientDetailsService). I don't see why you have to be able to load users as well in the same service.
if my grant_type =password , for example my curl looks some like this curl -X POST -vu passwordclient:secret http://localhost:9000/oauth/token -d "grant_type=password&username=user&password=test", if I need to authenticate my user and password from a database , wont I need userdeatails service as well . I am pretty sure I am missing some really stupid here .
The client is authenticated from the header (-u in your curl command) using the ClientDetailsService. The user is authenticated from the request body using whatever AuthenticationManager you passed into the AuthorizationServerConfigurer (I don't see that method in your code snippet above, so maybe that's what you are missing).
Thats what I thought and its puzzling me , here is Authserverconfig. I am using custom token store with mongoimpl , I dont think that matters for this., just an FYI.
@Configuration
@EnableAuthorizationServer
@EnableWebMvcSecurity
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter{
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
private @Autowired OAuthClientDetailsService clientDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// @formatter:off
endpoints
.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer())
.authenticationManager(this.authenticationManager).userApprovalHandler(approvalHandler());
// @formatter:on
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous()").checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.withClientDetails(clientDetailsService);
// @formatter:on
}
@Bean
@Qualifier("tokenStore")
public TokenStore tokenStore()
{
System.out.println("Created JwtTokenStore");
return new JWTMongoTokenStore(jwtTokenEnhancer());
}
@Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwtkeyaeb.jks"), "assurant".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwtkeyaeb"));
return converter;
}
@Bean
public UserApprovalHandler approvalHandler()
{
UserApprovalHandler approvalHandler=new DefaultUserApprovalHandler();
return approvalHandler;
}
My websecurity config
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private @Autowired OAuthUserService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
So what is it that doesn't work?
user not getting authenticated from request body , I am not seeing my username passed in rather I am seeing clientId's name as username during the invocation of loadUserByUsername call.
But that's expected since that is what is being authenticated. Show us the stack (e.g. from a debugger) when the loadUserByUsername is called.

Right so that's in the BasicAuthenticationFilter authenticating the client. Completely as it should be.
excuse my ignorance , is there a way to get around it ?
Get around what? It's doing what it it is supposed to.
Sorry I mis-understood , I was assuming that you were implying me to do something else.
This might be something you might have mentioned and it probably didnt click on , when I switched the clientdetailsservice configurer from clientdetails service to jdbc or inmemory , my client request is getting authenticated, when I use my custom client details its not.
Modified config
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception
{
// @formatter:off
clients
//.withClientDetails(clientDetailsService)
.inMemory()
.withClient("passwordclient")
.authorizedGrantTypes("password", "refresh_token")
.authorities("ROLE_USER")
.scopes("read", "write")
.resourceIds("hello:read,time:read,hello:write,time:write")
.accessTokenValiditySeconds(0)
.refreshTokenValiditySeconds(0)
.autoApprove(false)
.autoApprove("trust")
.additionalInformation(new LinkedHashMap<String, List<String>>())
.secret("secret");
// @formatter:on
}
I also encountered this issue, any development on the fix?
Did you follow the discussion? I'm pretty sure this is user error (misconfiguration).
The confusing part here is the OAuth2AuthenticationManager uses ClientDetailsService to authenticate the user. When grant_type is "password", it would be more intuitive if it used UserDetailsService.
Sorry for the confusion. The OAuth2AuthenticationManager never authenticates clients. Neither does it play any role in the password grant. It is used in a Resource Server. Is that clearer?
Yes and no. I made a bad assumption about the @Autowired AuthenticationManager in AuthorizationServerConfigurerAdapter. Stepping through it, i now see it's a ProviderManager.
My problem is that I want to use my own UserDetailsService to get the user (not the client) when authenticating a grant_type=password token. I expected the code below to work, but it doesn't.
public void configure(AuthorizationServerEndpointsConfigurer oauthServer) throws Exception {
oauthServer
.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.userDetailsService(myUserDetailsService) // This is not used to authenticate the user
.setClientDetailsService(clientDetailsService);
}
Any conclusion here? Also I want "ClientDetailsServiceConfigurer" with My client details is retrieved from MySQL database.
Only way I got it working was to doing this.
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception
{
// @formatter:off
endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer())
.userApprovalHandler(approvalHandler())
.authenticationManager(authenticationManager);
// @formatter:on
}
@nandhusriram Could you give me more code example of elaobration?
There's a JDBC sample in the integration tests. What's different about your app?
@dsyer Any example code of combined the OAuthUser with role of resourceId mapping in MySQL database?
What do you mean? Is there something you do that isn't covered in the integration test?
Yes, I am asking how to map the ResourceServer related resourceId to role assigned user or clientId?
I don't think that's anything to do with JDBC or about authentication. If you want to ask a general question on features or usage then Stackoverfow is probably the best place.
@derikl @dsyer
So please correct me if I'm wrong, but if the below example mentioned in the discusion is NOT setting the custom UserDetailsService used in grant_type=password, how could I achieve that then? Thank you in advance :)
public void configure(AuthorizationServerEndpointsConfigurer oauthServer) throws Exception {
oauthServer
.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.userDetailsService(myUserDetailsService) // This is not used to authenticate the user
.setClientDetailsService(clientDetailsService);
}
The authentication manager is used for authentication. You can create a custom one with your user details service using the normal Spring Security builder APIs (per the core docs and dozens of samples).
Yes, like @dsyer said, you need to create your own AuthenticationManager. I did this.
public void configure(AuthorizationServerEndpointsConfigurer oauthServer) throws Exception {
MyAuthenticationManager myAuthManager = new MyAuthenticationManager(userDetailsService);
oauthServer
.authenticationManager(myAuthManager)
.tokenStore(tokenStore)
.userDetailsService(userDetailsService) // This is not used to authenticate the user, unexpected...
.setClientDetailsService(clientDetailsService);
}
In your AuthenticationManager, override
Authentication authenticate(Authentication authentication) throws AuthenticationException;
If you want to get information about the scope or grant_type, etc., It's in authentication.getDetails().
Hi @nandhusriram
I have the similar problem.
In my implementation i need validade the username and password of my application in my database but i see that spring use client id and secret and obviously not not exist in my database.
If i send user name and password in header (and not client id and secret) the authentication is ok but a error "Handling error: NoSuchClientException, No client with requested id: pai' occur.
You solve your problem? Can help me with a tip?
Regards.
hey @mfalzetta .
Finally, i found a solution in StackOverFlow:
http://stackoverflow.com/questions/26208087/spring-boot-oauth-2-0-userdetails-user-not-found
I found that if I made the AuthenticationManager a bean and then passed it to the OAuth2, and I sent invalid client credentials, it will use my AuthenticationManager to try to validate my client credentials (and I only intended this AuthenticationManager to be used for user authentication (in a token grant). I fixed this by not defining the AuthenticationManager as a bean (meaning I've gotta use normal Java constructors not @Autowired or anything) and constructing the AuthenticationManager object in the configure override of my AuthorizationServerConfigurerAdapter as follows:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// NOTE: WESAuthenticationManager should not be a Bean so that the default Spring Security can apply for other end-points (and use Basic authentication with the client details)
endpoints.authenticationManager(new WESAuthenticationManager(authFactory, securityManager));
endpoints.setClientDetailsService(clientDetailsService);
endpoints.tokenStore(tokenStore());
endpoints.accessTokenConverter(accessTokenConverter());
endpoints.tokenEnhancer(accessTokenConverter());
// this is used by the token refresh to get user details to verify if the account is still active
endpoints.userDetailsService(userDetailsService());
}
In other words, like @derikl said...
Closing this as questions are better suited on Stack Overflow. We prefer to use GitHub issues for bugs and enhancements.
Most helpful comment
hey @mfalzetta .
Finally, i found a solution in StackOverFlow:
http://stackoverflow.com/questions/26208087/spring-boot-oauth-2-0-userdetails-user-not-found