最近用spring security + oauth2 做认证和授权,碰到个非常懊恼的问题,从安全上下文获取用户信息,永远只给你返回个用户名。因为在很多地方,要用到用户id, 不可能又用用户名去数据库查id吧,这就非常蛋腾了。
百度了人民群众的意见,折腾了老半天,终于把它搞掂了。
终极大法就是重写源码里面默认的实现方法。
源码就不贴了, 这贴修改的,供各位参考参考:
一 、 重写DefaultUserAuthenticationConverter里面的convertUserAuthentication方法:
@Component
@Slf4j
public class SetAdditionalInfoToTokenConverter extends DefaultUserAuthenticationConverter {
/**
* 这个方法把额外的信息添加进jwt token (默认只添加用户名,权限)
* 这里额外添加了用户id
* 方法将会在授权服务器起作用
* @param authentication
* @return
*/
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
// response.put(USERNAME, authentication.getName()); 源码里面的,不知道USERNAME是什么,注释掉吧
Object principal = authentication.getPrincipal();
//不要强制转换principal, 用json转化,不然各种莫名其妙的坑
String jsonStr=JSON.toJSONString(principal);
JSONObject jsonObject=JSON.parseObject(jsonStr);
//加上自定义的用户名和id
long id=jsonObject.getLong("id");
response.put("id", id);
String username=jsonObject.getString("username");
response.put("username",username);
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
//懒得写, 从源码复制过来的
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return List.of();
}
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
二,把这个重写的类 SetAdditionalInfoToTokenConverter 设置到授权服务器配置类里面 :
SetAdditionalInfoToTokenConverter —-> defaultAccessTokenConverter ->jwtAccessTokenConverter—>endpints.tokenEnhancer()
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Qualifier("customUserDetailService")
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
SetAdditionalInfoToTokenConverter setAdditionalInfoToTokenConverter; //定制保存在jwt里面的信息
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenStore(jwtTokenStore())
.tokenEnhancer(jwtAccessTokenConverter());
super.configure(endpoints);
}
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setAccessTokenConverter(accessTokenConverter());
return jwtAccessTokenConverter;
}
@Bean
public DefaultAccessTokenConverter accessTokenConverter(){
DefaultAccessTokenConverter defaultAccessTokenConverter = new
DefaultAccessTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(setAdditionalInfoToTokenConverter);
return defaultAccessTokenConverter;
}
}
三, 上面两步只是把额外信息(用户id,用户名)设置进jwtoken, 还不能用get principal获取,我们再继续重写 DefaultUserAuthenticationConverte 里面的 extractAuthentication方法,这个方法实现怎样提取信息到anthentication。因为后面要把重写的类设置进资源服务器配置类里面,所以重新创建一个component: ExtractAuthenticationConverter
@Component
@Slf4j
public class ExtractAuthenticationConverter extends DefaultUserAuthenticationConverter {
/**
* 这个方法重新定义了安全上下文 securityContextHolder.getContext.getPrincipal 返回的信息;
* 默认只返回用户名
* 这里添加了返回用户id
* 方法在资源服务器起作用
* @param map
* @return
*/
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
String username="username";
String id="id";
Map<String,Object> principal=new HashMap<>();
if(map.containsKey(username)){
principal.put(username, map.get(username));
}
if(map.containsKey(id)){
principal.put(id, map.get(id));
}
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
//从源码复制过来的
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return List.of();
}
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
四、 把重写了 extractAuthentication的类 extractAuthenticationConverter 设置进资源服务器配置类:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
ExtractAuthenticationConverter extractAuthenticationConverter;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(jwtTokenStore());
}
private TokenStore jwtTokenStore() {
JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
return jwtTokenStore;
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
//如果用了公私钥,在这里解析公钥。。。 省略
// 新建一个defaultAccessTokenConverter中介把 自定义的extractAuthenticationConverter设置进去
DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(extractAuthenticationConverter);
tokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
return tokenConverter;
}
}
至此,全部设置完,建个接口测试一下:
@GetMapping("/principle")
public Object getUserInfo(){
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
返回 :
{
“id”: 12312154321,
“username”: “admin”
}
ok,成功。