如何扩展 OAuth2 主体

Posted

技术标签:

【中文标题】如何扩展 OAuth2 主体【英文标题】:How to extend OAuth2 principal 【发布时间】:2018-07-20 09:01:18 【问题描述】:

我们正在开发一个将 OAuth 2 用于两个用例的应用程序:

    访问后端微服务(使用client_credentials) 验证应用程序的用户(使用authorization_code,因此将用户重定向到Keycloak进行登录,大致配置如tutorial所示)。

在对我们的用户进行身份验证时,我们会从身份验证服务器接收部分信息(例如登录),而另一部分可以在本地用户表中找到。我们喜欢做的是创建一个 Principal 对象,其中还包含本地数据库中的数据。

PrincipalExtractor 似乎是the way to go。由于我们必须使用手动 OAuth 配置来不干扰 OAuth 用例 1,因此我们创建并设置它:

tokenServices.setPrincipalExtractor(ourPrincipalExtractor);

该实现基本上是进行数据库查找并在映射函数中返回一个 CustomUser 对象。现在,尽管这似乎有效(调用了提取器),但它并没有正确地保留在会话中。因此,在我们的许多 REST 资源中,我们正在注入当前用户:

someRequestHandler(@AuthenticationPrincipal CustomUser activeUser) 

并在那里接收 null。查看注入的Authentication 它表明它是一个带有默认Principal 对象的OAuth2Authentication 对象(我认为它是一个Spring User / UserDetails)。所以 null 因为它不是我们之前返回的CustomUser

我们是否误解了PrincipalExtractor 的工作方式?会不会是我们的过滤器链配置错误,因为我们在前面提到的同一个应用程序中有两种不同的 OAuth 机制? Spring 的 Principal 存储库中的断点向我们显示 CustomUser 保存在那里,然后使用原始类型进行保存,这似乎覆盖了它。

【问题讨论】:

【参考方案1】:

好的,回答我自己的问题:

    PrincipalExtractor 似乎是自定义主体的常用标准方式 在我们的例子中它不起作用,因为我们使用了一个 JHipster 应用程序,它只是在登录后用它自己的User 覆盖主体。所以PrincipalExtractor 中的所有映射都被重置。如果有人有同样的问题:请查看UserService

这就是使用生成的代码的缺点,我猜你不知道详细信息。

【讨论】:

【参考方案2】:

我可以告诉你我是如何使用 JWT 完成类似的事情的。如果您不使用 JWT,那么我不确定这是否会有所帮助。

我有一个非常相似的问题,我注入的主体只包含用户名。不像你的那样为空,但显然不是我想要的。我最终做的是扩展TokenEnhancerJwtAccessTokenConverter

我使用 TokenEnhancerCustomUserDetails 类型的扩展主体嵌入到 JWT 附加信息中。

public class CustomAccessTokenEnhancer implements TokenEnhancer 

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) 
        Authentication userAuthentication = authentication.getUserAuthentication();
        if (userAuthentication != null) 
            Object principal = authentication.getUserAuthentication().getPrincipal();
            if (principal instanceof CustomUserDetails) 
                Map<String, Object> additionalInfo = new HashMap<>();
                additionalInfo.put("userDetails", principal);
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            
        
        return accessToken;
    

然后在处理经过身份验证的请求时,在构建Authentication 对象时手动提取扩展主体。

public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter 

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) 
        OAuth2Authentication authentication = super.extractAuthentication(map);
        Authentication userAuthentication = authentication.getUserAuthentication();

        if (userAuthentication != null) 
            LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
            if (userDetails != null) 

                // build your extended principal here
                String localUserTableField = (String) userDetails.get("localUserTableField");
                CustomUserDetails extendedPrincipal = new CustomUserDetails(localUserTableField);

                Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();

                userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
                        userAuthentication.getCredentials(), authorities);
            
        
        return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
    

AuthorizationServer 配置将它们捆绑在一起。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter 

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() 
        CustomJwtAccessTokenConverter accessTokenConverter = new CustomJwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("a1b2c3d4e5f6g");
        return accessTokenConverter;
    

    @Bean
    public TokenStore tokenStore() 
        return new JwtTokenStore(accessTokenConverter());
    

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

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

    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
        clients.jdbc(dataSource).passwordEncoder(passwordEncoder());
    

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        endpoints
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception 
        security.passwordEncoder(passwordEncoder());
        security.checkTokenAccess("isAuthenticated()");
    

然后我可以像这样在我的资源控制器中访问我的扩展主体

@RestController
public class SomeResourceController 

    @RequestMapping("/some-resource")
    public ResponseEntity<?> someResource(Authentication authentication) 
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        return ResponseEntity.ok("woo hoo!");
    


【讨论】:

正如你所说,用例有点不同,因为我们没有使用 JWT 令牌,而且我们不是授权服务器而是客户端。但是,我会尝试在我们的案例中是否可以覆盖您建议的一些类。 哎呀我错过了那部分哈哈。祝你好运!

以上是关于如何扩展 OAuth2 主体的主要内容,如果未能解决你的问题,请参考以下文章

资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...

资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...

资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...

OAuth2 与多个网关实例共享主体对象

Spring security oauth2 - 从 OAuth2 主体获取自定义数据

Spring Oauth2 - 重新加载主体