在 Spring 安全 OAuth2 中注入自定义 userDetailsService 时出现问题
problems injecting custom userDetailsService in Spring Security OAuth2
我正在使用 Spring 安全 OAuth2 2.0.7.RELEASE。因为我使用 ORM 连接到我的数据库并且默认 JdbcUserDetailsManager 使用 jdbc 我想实现我自己的 UserDetailsService,即
@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;
}
}
此外,我已经修改权限架构如下:
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 | |
+--------------+---------------------+------+-----+---------+----------------+
然后我像这样注入我的自定义 userDetailsService:
@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
}
}
如果我用 grant_type=密码发送 /oauth/token 请求然后我得到这个错误
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
(其中对clientId和clientSecret进行了编码)
{
"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'"
}
显然仍在使用默认的 JdbcDaoImpl。事实上,当我开始调试时,我发现是按照以下步骤进行的:
- 验证客户端(好的,因为我没有修改 oauth_client_details table)
- 使用我的自定义 userDetailsService 验证用户(好的,用户 table 已修改,但我的自定义 userDetailsService 支持更改)
- 使用默认 userDetailsService 验证用户(错误)
我不知道为什么会这样。对我来说这听起来像是一个错误。
有没有发现什么问题?
在 2.0.7
中,当您在 /oauth/token
上执行 POST/GET
请求且授权类型为 password
时,它实际上会排除 ClientDetailsUserDetailsService
但不会 UserDetailsService
.
我遇到了类似的问题,我是这样解决的:
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
是 AppClientDetailsUserDetailsService
的 bean,其构造函数参数是 AppConsumerDetailsService
.
问题是您使用的是默认 JdbcDaoImpl
。导致问题的查询是
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
这被用作默认查询,通过 JdbcDaoImpl
中的用户名加载用户的所有权限。因此,如果您仍然想使用默认 JdbcDaoImpl
- 您可以设置自定义查询,它将使用您的 table 作为它的参数来完成工作。
我不确定你们用户的模式是什么table,但类似的东西应该是类似的(如果你以编程方式配置 JdbcDaoImpl bean):
String query = "select username,authority " +
"from authorities join users on users.id = authorities.user_id " +
"where users.username = ?";
jdbcDaoImpl.setAuthoritiesByUsernameQuery(query);
或者如果您从 XML 创建 JdbcDaoImpl:
<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>
您可能希望更改其他一些默认查询以适合您的架构,请在 JdbcDaoImpl
中查看它们。
如果 UserDetailsService
与默认的 JdbcDaoImpl
相去甚远,您也可以考虑编写自己的实现。
您正在使用 auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(this.userService);// Inject custom
,我在这里创建了两个身份验证管理器 - 一个默认 JdbcDaoImpl
和 dataSource
指向 this.dataSource
,另一个使用您的自定义 userService
。尝试只放置 auth.userDetailsService(this.userService)
(我希望 userService 已经在内部自动装配了 jdbc)。
这里的重点是.and()
用来给认证管理器添加不同的认证配置,而不是配置jdbcAuthentication()
那个
我正在使用 Spring 安全 OAuth2 2.0.7.RELEASE。因为我使用 ORM 连接到我的数据库并且默认 JdbcUserDetailsManager 使用 jdbc 我想实现我自己的 UserDetailsService,即
@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;
}
}
此外,我已经修改权限架构如下:
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 | |
+--------------+---------------------+------+-----+---------+----------------+
然后我像这样注入我的自定义 userDetailsService:
@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
}
}
如果我用 grant_type=密码发送 /oauth/token 请求然后我得到这个错误
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
(其中对clientId和clientSecret进行了编码)
{
"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'"
}
显然仍在使用默认的 JdbcDaoImpl。事实上,当我开始调试时,我发现是按照以下步骤进行的:
- 验证客户端(好的,因为我没有修改 oauth_client_details table)
- 使用我的自定义 userDetailsService 验证用户(好的,用户 table 已修改,但我的自定义 userDetailsService 支持更改)
- 使用默认 userDetailsService 验证用户(错误)
我不知道为什么会这样。对我来说这听起来像是一个错误。 有没有发现什么问题?
在 2.0.7
中,当您在 /oauth/token
上执行 POST/GET
请求且授权类型为 password
时,它实际上会排除 ClientDetailsUserDetailsService
但不会 UserDetailsService
.
我遇到了类似的问题,我是这样解决的:
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
是 AppClientDetailsUserDetailsService
的 bean,其构造函数参数是 AppConsumerDetailsService
.
问题是您使用的是默认 JdbcDaoImpl
。导致问题的查询是
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
这被用作默认查询,通过 JdbcDaoImpl
中的用户名加载用户的所有权限。因此,如果您仍然想使用默认 JdbcDaoImpl
- 您可以设置自定义查询,它将使用您的 table 作为它的参数来完成工作。
我不确定你们用户的模式是什么table,但类似的东西应该是类似的(如果你以编程方式配置 JdbcDaoImpl bean):
String query = "select username,authority " +
"from authorities join users on users.id = authorities.user_id " +
"where users.username = ?";
jdbcDaoImpl.setAuthoritiesByUsernameQuery(query);
或者如果您从 XML 创建 JdbcDaoImpl:
<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>
您可能希望更改其他一些默认查询以适合您的架构,请在 JdbcDaoImpl
中查看它们。
如果 UserDetailsService
与默认的 JdbcDaoImpl
相去甚远,您也可以考虑编写自己的实现。
您正在使用 auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(this.userService);// Inject custom
,我在这里创建了两个身份验证管理器 - 一个默认 JdbcDaoImpl
和 dataSource
指向 this.dataSource
,另一个使用您的自定义 userService
。尝试只放置 auth.userDetailsService(this.userService)
(我希望 userService 已经在内部自动装配了 jdbc)。
这里的重点是.and()
用来给认证管理器添加不同的认证配置,而不是配置jdbcAuthentication()
那个