如何扩展 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,那么我不确定这是否会有所帮助。
我有一个非常相似的问题,我注入的主体只包含用户名。不像你的那样为空,但显然不是我想要的。我最终做的是扩展TokenEnhancer
和JwtAccessTokenConverter
。
我使用 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!");
【讨论】:
正如你所说,用例有点不同,因为我们没有使用 JWT 令牌,而且我们不是授权服务器而是客户端。但是,我会尝试在我们的案例中是否可以覆盖您建议的一些类。 哎呀我错过了那部分哈哈。祝你好运!以上是关于如何扩展 OAuth2 主体的主要内容,如果未能解决你的问题,请参考以下文章
资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...
资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...
资源服务器获取用户信息,java - Spring Security OAuth2资源服务器无法获取包含详细信息的主体 - 堆栈内存溢出...