如何在 spring-security-oauth2 中的资源服务器中获取自定义 UserDetailService 对象?

Posted

技术标签:

【中文标题】如何在 spring-security-oauth2 中的资源服务器中获取自定义 UserDetailService 对象?【英文标题】:How to get custom UserDetailService Object in Resource Server in spring-security-oauth2? 【发布时间】:2017-12-10 07:22:33 【问题描述】:

我有单独的授权服务器和资源服务器。 授权服务器指向一个单独的数据库。我使用CustomUserDetailService 获取用户相关信息。 我使用CustomTokenEnhancer 在响应中获取除令牌之外的其他信息。

@Configuration
public class OAuth2Configuration 


    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware 

        private static final String ENV_OAUTH = "authentication.oauth.";
        private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Autowired
        private DataSource dataSource;

        @Autowired
        private CustomUserDetailService userDetailsService;

        @Bean
        public TokenStore tokenStore() 
            return new CustomTokenStore(dataSource);
        

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception 
            endpoints
                    .tokenStore(tokenStore())
                    .userDetailsService(userDetailsService)

                    .tokenEnhancer(tokenEnhancer())
                    .accessTokenConverter(accessTokenConverter())

                    .authenticationManager(authenticationManager);
        

        @Bean
        public TokenEnhancer tokenEnhancer() 
           return new CustomTokenEnhancer();
        

        @Bean
        public DefaultAccessTokenConverter accessTokenConverter() 
            return new DefaultAccessTokenConverter();
        


        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer)
                throws Exception 
            oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
        
    


CustomUserDetailService 类:

@Service
public class CustomUserDetailService implements UserDetailsService 


    @Autowired
    private AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) 

        Account account = accountRepository.getByEmail(username);

        if(account == null) 
            throw new UsernameNotFoundException(username);
        

        return new MyUserPrincipal(account);

    

CustomTokenEnhancer 类:

public class CustomTokenEnhancer implements TokenEnhancer 

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) 
        MyUserPrincipal user = (MyUserPrincipal) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<>();

        additionalInfo.put("user_information", user.getAccount());

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    


请求/响应

http://localhost:9191/authserver/oauth/token



    "access_token": "fddb571e-224e-4cd7-974e-65104dd24b41",
    "token_type": "bearer",
    "refresh_token": "eb412b00-9e4e-4d6c-86d8-324d999b5f08",
    "expires_in": 100,
    "scope": "read write",
    "account_information": 
        "id": 14,
        "firstname": "name",
        "lastname": "lastname",

    

在资源服务器端,我使用RemoteTokenSerice 来验证用户提供的令牌是否有效。

@Configuration
@EnableResourceServer
public class OAuthResourceConfig extends ResourceServerConfigurerAdapter 

    private TokenExtractor tokenExtractor = new BearerTokenExtractor();

    @Override
    public void configure(HttpSecurity http) throws Exception 
        http.addFilterAfter(new OncePerRequestFilter() 
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                    HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException 
                if (tokenExtractor.extract(request) == null) 
                    SecurityContextHolder.clearContext();
                
                filterChain.doFilter(request, response);
            
        , AbstractPreAuthenticatedProcessingFilter.class);
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated();
    

    @Bean
    public AccessTokenConverter accessTokenConverter() 
        return new DefaultAccessTokenConverter();
    

    @Bean
    @Primary
    public RemoteTokenServices remoteTokenServices(final @Value("$auth.server.url") String checkTokenUrl,
            final @Value("$auth.server.clientId") String clientId,
            final @Value("$auth.server.clientsecret") String clientSecret) 


        final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
        remoteTokenServices.setClientId(clientId);
        remoteTokenServices.setClientSecret(clientSecret);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
        return remoteTokenServices;
    

    

所以它工作正常,当我使用令牌向资源服务器发出请求时,如果令牌有效,它会处理请求。我的问题是我想在资源服务器中获取 Account 对象。我尝试了以下方法:

 Account account = (Account)SecurityContextHolder.getContext().getAuthentication().getPrincipal()

但它给出的是字符串而不是完整的用户定义对象,因此会引发异常。如何在资源服务器的任何控制器中获取 Account 对象?


    "timestamp": 1499334657703,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "java.lang.ClassCastException",
    "message": "java.lang.String cannot be cast to Account",
    "path": "/secure"
    

我尝试使用link,但是否可以同时注入两个令牌服务 RemoteTokenService 和 CustomUserInfoTokenServices?

另外我认为 spring 从资源服务器向授权服务器 (http://localhost:9191/authserver/oauth/check_token?token=d8dae984-7bd8-4aab-9990-a2c916dfe667) 进行内部调用以验证令牌。

有什么方法可以让我在控制器中获取这些信息而无需再次调用此端点。

回复:


    "exp": 1499333294,
    "account_information": 
        "accountid": 14,
        "firstname": "fname",
        "lastname": "lname",

    ,
    "user_name": "abc@abc.com",
    "client_id": "clientId",
    "scope": [
        "read",
        "write"
    ]

【问题讨论】:

【参考方案1】:

我已经重写了下面的方法并添加了一些逻辑。

public class CustomAccessTokenConverter extends DefaultAccessTokenConverter

    private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) 
        Map<String, String> parameters = new HashMap<String, String>();
        @SuppressWarnings("unchecked")
        Set<String> scope = new LinkedHashSet<String>(map.containsKey(SCOPE) ? (Collection<String>) map.get(SCOPE)
                : Collections.<String>emptySet());
        Authentication user = userTokenConverter.extractAuthentication(map);
        String clientId = (String) map.get(CLIENT_ID);
        parameters.put(CLIENT_ID, clientId);
        parameters.put("account_information", String.valueOf((((Map) map.get("account_information")).get("accountid"))));
        @SuppressWarnings("unchecked")
        Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD)
                : Collections.<String>emptySet());

        Map<String, Serializable> extensions = new HashMap<String, Serializable>();
        extensions.put("account_information", (HashMap) map.get("account_information"));

        OAuth2Request request = new OAuth2Request(parameters, clientId, null, true, scope, resourceIds, null, null,
                extensions);
        return new OAuth2Authentication(request, user);
    


资源服务器类

@Bean
    public AccessTokenConverter accessTokenConverter() 
        //return new DefaultAccessTokenConverter();
        return new CustomAccessTokenConverter();
    

    @Bean
    @Primary
    public RemoteTokenServices remoteTokenServices(final @Value("$auth.server.url") String checkTokenUrl,
            final @Value("$auth.server.clientId") String clientId,
            final @Value("$auth.server.clientsecret") String clientSecret) 


        final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
        remoteTokenServices.setClientId(clientId);
        remoteTokenServices.setClientSecret(clientSecret);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
        return remoteTokenServices;
    

现在我可以在控制器中获取更多信息。

OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();

        Map<String, Serializable> map = authentication.getOAuth2Request().getExtensions();

【讨论】:

【参考方案2】:

我是这样用的。 调用 /oauth/token 后,我可以进入下面,member_id 是我添加的附加字段。 "access_token": "this is access token", "token_type": "bearer", "refresh_token": this is refreshtoken", "expires_in": 3599, "scope": "web", "member_id": "d2lsbGlhbQ", "jti": "79b9b523-921d-45c1-ba97-d3565f1d68b7" 解码访问令牌后,我可以在其中看到这个自定义字段member_id

以下是我在资源服务器中所做的事情。

在配置类中清除 Bean DefaultTokenService

@Bean @Primary public DefaultTokenServices tokenServices() throws IOException DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); return defaultTokenServices;

这里我将资源注入到我的控制器中。

@Autowired private ResourceServerTokenServices resourceServerTokenServices;

@GetMapping("/addition") public Map<String, Object> addition() Map<String, Object> response = new HashMap<>(); response.put("member_id", resourceServerTokenServices.readAccessToken(((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getTokenValue()).getAdditionalInformation().get("member_id")); return response;

然后我称之为/addition,我可以看到响应。 "member_id": "d2lsbGlhbQ"

我是使用 JWT 的 oAuth2 的新手,所以我在互联网上做了一些研究,但找不到从资源服务器获取它的敏感方法。所以我尝试了一些方法来得到这个。希望它有效。

【讨论】:

接受的答案在资源服务器中对我有用:***.com/questions/54279755/…

以上是关于如何在 spring-security-oauth2 中的资源服务器中获取自定义 UserDetailService 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 redis 使用 spring-security-oauth2 持久化令牌

我们如何使用带有 Spring 5.0 的最新 spring-security-oauth2 jar 来实现授权服务器?

如何使用带有 WebClient 的 spring-security-oauth2 自定义 OAuth2 令牌请求的授权标头?

Spring 中的 spring-security-oauth2 与 spring-security-oauth2-core

使用带有 OpenID Connect 提供程序的 spring-security-oauth2 客户端时如何访问“id_token”和“refresh_token”?

spring-security-oauth2 替代品