I am using Spring Security OAuth2 2.0.7.RELEASE. As i am using ORM to connect to my database and default JdbcUserDetailsManager uses jdbc i wanted to implement my own UserDetailsService, which is
@Service
public class UserService
implements UserDetailsService {
@Override
public UserDetailsService loadUserByUsername(String username) throws UsernameNotFoundException {
// I tested this logic and works fine so i avoid this lines
return userDetailsService;
}
}
Besides, i've modified authorities schema as follows:
mysql> describe authorities;
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| authority_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| user_id | bigint(20) unsigned | NO | MUL | NULL | |
| authority | varchar(256) | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
Then i am injecting my custom userDetailsService like this:
@Configuration
@Import(OAuth2SupportConfig.class)
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
...
@Autowired
private UserDetailsService userDetailsService
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore).tokenServices(tokenService);
endpoints.userDetailsService(userDetailsService); // Inject custom
endpoints.authorizationCodeServices(authorizationCodeServices);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.jdbc(dataSource);
}
}
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationManagerConfiguration
extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userService;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(this.userService);// Inject custom
}
}
If i send /oauth/token request with grant_type=password then i get this error
POST /oauth/token HTTP/1.1
Host: localhost:8080
Authorization: Basic aW5kaXJhOnNlY3JldA==
Cache-Control: no-cache
Postman-Token: c89baf37-8ad2-4270-5251-9715bfab470a
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=user&password=pass
(where clientId and clientSecret is encoded)
{
"error": "unauthorized",
"error_description": "PreparedStatementCallback; bad SQL grammar [select username,authority from authorities where username = ?]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'username' in 'field list'"
}
Apparently is still using the default JdbcDaoImpl. In fact, when i started debugging, i found that is following these steps:
- Authenticates client (OK, since i haven't modified oauth_client_details table)
- Authenticates user with my custom userDetailsService(OK, users table modificated but my custom userDetailsService supports changes)
- Authenticates user with default userDetailsService(ERROR)
I don't know why this is happening. It sounds like a bug to me. Do you find anything wrong?
You are using auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(this.userService);// Inject custom
and I here it's creating two auth managers - one with default JdbcDaoImpl
and dataSource
pointing to this.dataSource
and another with your custom userService
. Try just putting auth.userDetailsService(this.userService)
(I hope that userService already have jdbc autowired inside).
The point here is that .and()
is used to add different authentication configurations to the authentication manager, not configuring the jdbcAuthentication()
one.
In 2.0.7
when you do a POST/GET
request on /oauth/token
with grant type as password
, it will actually except a ClientDetailsUserDetailsService
but not UserDetailsService
.
I had similar issue and this is how I solved it :
public class AppClientDetailsUserDetailsService extends ClientDetailsUserDetailsService {
public AppClientDetailsUserDetailsService(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
}
public class AppConsumerDetailsService implements ClientDetailsService {
public ClientDetails loadClientByClientId(String clientId)
throws OAuth2Exception {
//some logic
}
}
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="authenticationManager"
entry-point-ref="entryPoint" xmlns="http://www.springframework.org/schema/security"
>
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
<anonymous enabled="false" />
<http-basic entry-point-ref="entryPoint" />
<custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
</http>
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
authenticationManager
is the bean for AppClientDetailsUserDetailsService
whose constructor argument is AppConsumerDetailsService
.
The problem is that you are using default JdbcDaoImpl
. The query which is causing the problem is
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
This is used as a default query to load all authorities for the user by userName inside JdbcDaoImpl
. So if you still want to use default JdbcDaoImpl
- you can set the custom query, which will do the job with your tables, as the parameter of it.
I'm not sure what is the schema of you users table, but something like this should be similar(if you are configuring JdbcDaoImpl bean programmatically):
String query = "select username,authority " +
"from authorities join users on users.id = authorities.user_id " +
"where users.username = ?";
jdbcDaoImpl.setAuthoritiesByUsernameQuery(query);
Or if you create JdbcDaoImpl from XML:
<bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="authoritiesByUsernameQuery"
value="select username,authority from authorities
join users on users.id = authorities.user_id
where users.username = ?"/>
</bean>
There might be some other default queries that you would like to change to fit your schema, take a look at them inside JdbcDaoImpl
.
You might also consider a possibility of writing your own implementation of UserDetailsService
if it will start getting too far from default JdbcDaoImpl
one.
来源:https://stackoverflow.com/questions/31683166/problems-injecting-custom-userdetailsservice-in-spring-security-oauth2