我们正在开发一个将OAuth 2用于两个用例的应用程序:
client_credentials
)authorization_code
,因此将用户重定向到Keycloak进行登录,大致配置如tutorial所示)。在验证我们的用户时,我们从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
被保存在那里,然后是原始类型的保存,似乎覆盖了它。
答案 0 :(得分:1)
我可以告诉你我是如何使用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!");
}
}
答案 1 :(得分:1)
好的,回答我自己的问题:
PrincipalExtractor
似乎是定制主体User
覆盖主体。因此,PrincipalExtractor
中的所有映射都将重置。如果有人有同样的问题:请查看UserService
。这是使用您不熟悉的生成代码的缺点。我想。