你有两个选择:
1-
将 UserDetailsService 实例设置为 DefaultUserAuthenticationConverter
并将转换器设置为 JwtAccessTokenConverter 所以当 spring 从 DefaultUserAuthenticationConverter 调用 extractAuthentication 方法时,它发现 (userDetailsService != null) 所以它得到了整个 UserDetails 对象,在调用此行时调用 loadUserByUsername 的实现:
userDetailsService.loadUserByUsername((String) map.get(USERNAME))
在 spring 类 org 中的 next 方法中实现。springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter.java 但只是添加它以阐明 spring 如何从映射中获取主体对象(首先通过用户名,如果 userDetailsService 不为空,则获取整个对象):
//Note: This method implemented by spring but just putting it to show where spring exctract principal object and how extracting it
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
所以你需要在你的微服务中实现的是:
@Bean//this method just used with token store bean example: new JwtTokenStore(tokenEnhancer());
public JwtAccessTokenConverter tokenEnhancer() {
/**
* CustomTokenConverter is a class extends JwtAccessTokenConverter
* which override "enhance" to add extra information to OAuth2AccessToken after
* authenticate the user and get it by loadUserByUsername implementation
* like profileId in your case
**/
JwtAccessTokenConverter converter = new CustomTokenConverter();
DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
datc.setUserTokenConverter(userAuthenticationConverter());
converter.setAccessTokenConverter(datc);
//Other method code implementation....
}
@Autowired
private UserDetailsService userDetailsService;
@Bean
public UserAuthenticationConverter userAuthenticationConverter() {
DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
duac.setUserDetailsService(userDetailsService);
return duac;
}
注意:第一种方式将在每个请求中访问数据库,因此它通过用户名加载用户并获取 UserDetails 对象,以便将其分配给身份验证内的主体对象。
2-
如果出于任何原因,您认为最好不要在每个请求中访问数据库,并且执行所需的数据(如从请求中传递的令牌中的 profileId)没有问题。
假设您知道在生成 oauth2 令牌时分配给用户的旧权限将始终在令牌中,直到它变为无效,即使您在数据库中为在请求中传递令牌的用户更改它,以便用户可以调用不允许他使用的方法/her 提取令牌后不再存在,并且在提取令牌之前允许。
所以这意味着如果用户权限在生成令牌后发生更改,@PreAuthorize 不会检查新权限,因为它没有被删除或添加到令牌中,您必须等到旧令牌无效或过期,以便用户强制执行再次服务以获取新的 oauth 令牌。
无论如何,在第二个选项中,您只需要覆盖 CustomTokenConverter 类中的 extractAuthentication 方法 extends JwtAccessTokenConverter 并忘记设置访问令牌转换器 converter.setAccessTokenConverter 来自第一个选项中的 tokenEnhancer() 方法,这里是整个 CustomTokenConverter,您可以使用它从令牌中读取数据并返回主体对象,而不仅仅是字符串用户名:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
public class CustomTokenConverter extends JwtAccessTokenConverter {
// This is the method you need to override to read data direct from token passed in request
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Object userIdObj = map.get(AuthenticationUtils.USER_ID);
UUID userId = userIdObj != null ? UUID.fromString(userIdObj.toString()) : null;
Object profileIdObj = map.get(AuthenticationUtils.PROFILE_ID);
UUID profileId = profileIdObj != null ? UUID.fromString(profileIdObj.toString()) : null;
Object firstNameObj = map.get(AuthenticationUtils.FIRST_NAME);
String firstName = firstNameObj != null ? String.valueOf(firstNameObj) : null;
Object lastNameObj = map.get(AuthenticationUtils.LAST_NAME);
String lastName = lastNameObj != null ? String.valueOf(lastNameObj) : null;
JwtUser principal = new JwtUser(userId, profileId, authentication.getUserAuthentication().getName(), "N/A", authentication.getUserAuthentication().getAuthorities(), firstName, lastName);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(),
new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getUserAuthentication().getAuthorities()));
return authentication;
}
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
JwtUser user = (JwtUser) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<>(accessToken.getAdditionalInformation());
if (user.getId() != null)
info.put(AuthenticationUtils.USER_ID, user.getId());
if (user.getProfileId() != null)
info.put(AuthenticationUtils.PROFILE_ID, user.getProfileId());
if (isNotNullNotEmpty(user.getFirstName()))
info.put(AuthenticationUtils.FIRST_NAME, user.getFirstName());
if (isNotNullNotEmpty(user.getLastName()))
info.put(AuthenticationUtils.LAST_NAME, user.getLastName());
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
return super.enhance(customAccessToken, authentication);
}
private boolean isNotNullNotEmpty(String str) {
return Optional.ofNullable(str).map(String::trim).map(string -> !str.isEmpty()).orElse(false);
}
}
最后:猜猜我怎么知道你在问与 OAuth2 一起使用的 JWT?
因为我是贵公司的一员 :P 并且你知道 :P