代码之家  ›  专栏  ›  技术社区  ›  Tarnschaf

如何扩展OAuth2主体

  •  4
  • Tarnschaf  · 技术社区  · 6 年前

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

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

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

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

    tokenServices.setPrincipalExtractor(ourPrincipalExtractor);
    

    该实现基本上执行DB查找,并在映射函数中返回CustomUser对象。现在,尽管这似乎可行(调用了提取器),但它并没有正确地持久化到会话中。因此,在我们的许多REST资源中,我们正在注入当前用户:

    someRequestHandler(@AuthenticationPrincipal CustomUser activeUser) {
    

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

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

    2 回复  |  直到 6 年前
        1
  •  2
  •   Tarnschaf    6 年前

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

    1. PrincipalExtractor 似乎是定制主体的常规和标准方式
    2. 在我们的情况下,它不起作用,因为我们使用的是一个JHipster应用程序,它只会在登录之后立即用自己的应用程序覆盖主体 User . 所以所有映射 主牵引车 已重置。如果有人有同样的问题:调查 UserService .

    我想这就是使用生成的代码的缺点,您可能不知道详细信息。

        2
  •  1
  •   secondbreakfast    6 年前

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

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


    我使用 令牌增强器 嵌入类型为的扩展主体 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!");
        }
    
    }