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

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

  1. 访问后端微服务(使用client_credentials
  2. 验证应用程序的用户(使用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 保存在那里,然后使用原始类型进行保存,这似乎覆盖了它。

【问题讨论】:

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


    【解决方案1】:

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

    1. PrincipalExtractor 似乎是自定义主体的常用标准方式
    2. 在我们的例子中它不起作用,因为我们使用了一个 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 令牌,而且我们不是授权服务器而是客户端。但是,我会尝试在我们的案例中是否可以覆盖您建议的一些类。
      • 哎呀我错过了那部分哈哈。祝你好运!
      猜你喜欢
      • 1970-01-01
      • 2016-01-09
      • 2018-01-20
      • 2011-03-31
      • 2017-01-12
      • 1970-01-01
      • 2020-02-01
      • 1970-01-01
      • 2018-04-10
      相关资源
      最近更新 更多