【问题标题】:problems injecting custom userDetailsService in Spring Security OAuth2在 Spring Security OAuth2 中注入自定义 userDetailsS​​ervice 的问题
【发布时间】:2015-10-19 09:55:54
【问题描述】:

我正在使用 Spring Security OAuth2 2.0.7.RELEASE。由于我使用 ORM 连接到我的数据库并且默认 JdbcUserDetailsManager 使用 jdbc 我想实现我自己的 UserDetailsS​​ervice,这是

@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    |                |
+--------------+---------------------+------+-----+---------+----------------+

然后我像这样注入我的自定义 userDetailsS​​ervice:

@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=password 发送 /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。事实上,当我开始调试时,我发现正在执行以下步骤:

  1. 验证客户端(好的,因为我没有修改 oauth_client_details 表)
  2. 使用我的自定义 userDetailsS​​ervice 对用户进行身份验证(好的,用户表已修改,但我的自定义 userDetailsS​​ervice 支持更改)
  3. 使用默认 userDetailsS​​ervice(ERROR) 验证用户

我不知道为什么会这样。这对我来说听起来像是一个错误。 有没有发现什么问题?

【问题讨论】:

    标签: java spring spring-security spring-security-oauth2


    【解决方案1】:

    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>
    

    authenticationManagerAppClientDetailsUserDetailsService 的 bean,其构造函数参数为 AppConsumerDetailsService

    【讨论】:

    • 感谢您的回复。我认为当我使用 grant_type 密码进行 oauth/token 请求时,authenticationManager 身份验证管理器首先对客户端(使用 ClientDetailsS​​ervice)进行身份验证,然后对用户(使用 UserDetailsS​​ervice)进行身份验证。除此之外,我认为 clientDetailsS​​ervice 只使用 oauth_ 表(而不是用户、权限和组表),这就是为什么假设这是 UserDetailsS​​ervice 的问题。这一切都错了吗?
    • 第一部分是正确的,它验证客户端,然后验证用户。但我没有回答你问题的第二部分。顺便说一句,您对ClientDetailService 有任何实现吗?但您必须提及clientCredentialsTokenEndpointFilter 才能使其发挥作用。
    • 不,我不知道。我想我应该,你怎么了?但我想不出客户端登录的方法。我的意思是,我认为它只是去 oauth_client_details,我不明白为什么它应该去用户表..
    • @jscherman 您可以在问题中添加您向/oauth/token 提出的请求吗?我会尝试在此基础上进行解释。
    • 所以你在调用中有client idclient secret,应该有一个机制来验证这是否是一个有效的客户端,因为你需要一个ClientDetailsUserDetailsService
    【解决方案2】:

    问题是您使用的是默认JdbcDaoImpl。导致问题的查询是

    public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
            "select username,authority " +
            "from authorities " +
            "where username = ?";
    

    这被用作默认查询,通过JdbcDaoImpl 中的用户名加载用户的所有权限。因此,如果您仍想使用默认的JdbcDaoImpl - 您可以将自定义查询设置为它的参数,该查询将使用您的表完成这项工作。

    我不确定你的 users 表的架构是什么,但这样的东西应该是相似的(如果你正在以编程方式配置 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 相差太远的话。

    【讨论】:

    • 其实,这正是我所做的。我做了自己的实现并注入了它,问题是除了我的自定义 userDetailsS​​ervice 之外,它仍在使用默认值。正如你所说,我也尝试了覆盖查询,并且发生了同样的情况:它仍在使用原始查询,所以它失败了。还是谢谢
    • 没问题!无论哪种方式,我都感谢您的回应
    • 我还有一个猜测:您正在使用auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(this.userService);// Inject custom,我认为它在这里创建了两个身份验证管理器 - 一个默认为JdbcDaoImpl,dataSource 指向this.dataSource,另一个使用您的自定义userService .试试只放auth.userDetailsService(this.userService)(我希望userService里面已经自动连接了jdbc)。
    • 这是一个很好的猜测。我明天试试。但是,如果我取消它们,我应该也注入一个客户端服务以及 defaulttokenservices,对吧?
    • 成功了!你是个天才!留下你的答案,如果你愿意,我可以给你赏金
    【解决方案3】:

    您正在使用auth.jdbcAuthentication().dataSource(this.dataSource).and().userDetailsService(‌​this.userService);// Inject custom,而我在这里创建了两个身份验证管理器 - 一个使用默认的JdbcDaoImpldataSource 指向this.dataSource,另一个使用您的自定义userService。试试只输入auth.userDetailsService(this.userService)(我希望userService里面已经有jdbc autowired)。

    这里的重点是.and()是用来给认证管理器添加不同的认证配置,而不是配置jdbcAuthentication()一个。

    【讨论】:

      猜你喜欢
      • 2013-05-12
      • 2013-02-11
      • 2011-09-01
      • 2017-10-27
      • 2012-12-13
      • 2017-04-13
      • 2014-10-12
      • 2016-08-12
      • 2014-10-06
      相关资源
      最近更新 更多