需要说明的是,不管是使用对称加密或者非对称加密的JWT方式,添加额外信息的操作都是一样的。
可以自定义一个TokenEnhancer将额外的信息添加到token中:
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
// 获取登录信息
SecurityUserInfo user = (SecurityUserInfo) oAuth2Authentication.getUserAuthentication().getPrincipal();
Map<String, Object> customInfoMap = new HashMap<>();
customInfoMap.put("loginName", user.getUsername());//登录名
customInfoMap.put("name", user.getName());//用户姓名
customInfoMap.put("content", "这是一个测试的内容");
customInfoMap.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(customInfoMap);
return oAuth2AccessToken;
}
}
在这个代码中,我传递了一些信息,大多是是来自UserDetails类的(这个类中,一般就包含了用户的所有信息)。这里关键的一点是,我还床了一个content字段,这个是我自定义的,要是资源服务端能收到这个字段信息,则说明目的达到了。
修改configure(AuthorizationServerEndpointsConfigurer endpoints)方法的代码:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// @formatter:off
......略
if(JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {
// token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
}
// @formatter:on
}
这里,主要就是将我们自定义的CustomTokenEnhancer,加入到TokenEnhancerChain 中,其他的代码不用变化。
至此,授权服务端就算是改造完成了。
资源服务的改造,其实就是在公用包中,加一些代码,然后再提供一个工具类,来获取这个当前登录人的信息。
/**
* 自定义AccessTokenConverter转换
* 不定义这个类,那么自定义的字段的值不会取到(实际上是传过来了);
* 所以,这里主要的操作就是把传过来的自定义的值保存下来。
* 关于内部类CustomerUserAuthenticationConverter中的重写方法和私有方法getAuthorities,都是仿照父类DefaultUserAuthenticationConverter来写的,
* 在debug模式下,跟下代码就知道为什么这么设置值了
* @author FYK
2019年8月14日
* @version 1.0
* @since JDK:1.8
*/
public class CustomerAccessTokenConverter extends DefaultAccessTokenConverter {
public CustomerAccessTokenConverter() {
super.setUserTokenConverter(new CustomerUserAuthenticationConverter());
}
private class CustomerUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
return new UsernamePasswordAuthenticationToken(map, "N/A", this.getAuthorities(map));
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.arrayToCommaDelimitedString(new String[]{}));
}
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");
}
}
}
类注释已经说明清楚了,这里就不在赘言了。
修改accessTokenConverter()方法:也就是加入一行代码:converter.setAccessTokenConverter(new CustomerAccessTokenConverter());,将刚才新增的类添加进来;
@Bean
@Profile("jwt")
public JwtAccessTokenConverter accessTokenConverter() {
......略
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
return converter;
}
到这里,就算是已经完成了
现在要做的,就是提供一个公共方法,来获取当前用户的信息,其中包含我们的自定义信息。
public interface CurrentUserUtil {
/**
* 获取当前登录人的信息
* @author FYK
* @return
*/
public static LoginUserInfo getCurrentLoginUser() {
Log log = LogFactory.getLog(CurrentUserUtil.class);
LoginUserInfo loginUserInfo = new LoginUserInfo();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) authentication.getPrincipal();
try {
BeanUtils.populate(loginUserInfo, map);
} catch (IllegalAccessException | InvocationTargetException e) {
loginUserInfo = null;
log.error(e.getMessage());
}
return loginUserInfo;
}
}
这里的LoginUserInfo是一个简单的pojo对象,作用是将传递的信息,保存下来。只要这个对象的属性和传递的信息的key值相同,就可以填充好了。