如何扩展OAuth2主体

时间:2018-02-09 14:50:22

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

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

  1. 访问后端微生物(使用client_credentials
  2. 验证应用程序的用户(使用authorization_code,因此将用户重定向到Keycloak进行登录,大致配置如tutorial所示)。
  3. 在验证我们的用户时,我们从auth服务器接收部分信息(例如登录),而另一部分可以在本地用户表中找到。我们要做的是创建一个Principal对象,该对象还包含来自本地数据库的数据。

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

    tokenServices.setPrincipalExtractor(ourPrincipalExtractor);
    

    该实现基本上执行数据库查找并在映射函数中返回CustomUser对象。现在虽然这似乎有效(调用提取器),但它并没有在会话中持久存在。因此,在我们的许多REST资源中,我们正在注入当前用户:

    someRequestHandler(@AuthenticationPrincipal CustomUser activeUser) {
    

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

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

2 个答案:

答案 0 :(得分:1)

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

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


我使用TokenEnhancer将我的扩展主体CustomUserDetails嵌入到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!");
    }

}

答案 1 :(得分:1)

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

  1. PrincipalExtractor似乎是定制主体
  2. 的通常和标准方式
  3. 它在我们的情况下不起作用,因为我们正在使用一个JHipster应用程序,只需在登录后用它自己的User覆盖主体。因此,PrincipalExtractor中的所有映射都将重置。如果有人有同样的问题:请查看UserService
  4. 这是使用您不熟悉的生成代码的缺点。我想。