在使用JWT存储token的时候,由于授权服务把token给了资源服务之后,资源服务就不会再访问授权服务索求token相关的信息,以及当前登录人的信息。因为这些token的校验,以及授权服务想告诉资源服务的信息,都可以存放在这个token里,资源服务只需要去解析就可以了。
在默认的情况下,存放的信息是UserDetails(也就是security中,我们的用户表继承实现的那个类,在我这里的是SecurityUserInfo)的信息。但是,如果我想传一些其他信息呢?

添加额外的信息

需要说明的是,不管是使用对称加密或者非对称加密的JWT方式,添加额外信息的操作都是一样的。

授权服务改造

添加CustomTokenEnhancer类:

可以自定义一个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字段,这个是我自定义的,要是资源服务端能收到这个字段信息,则说明目的达到了。

授权服务配置类:OAuth2AuthorizationConfig改造

修改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 中,其他的代码不用变化。
至此,授权服务端就算是改造完成了。

资源服务改造

资源服务的改造,其实就是在公用包中,加一些代码,然后再提供一个工具类,来获取这个当前登录人的信息。

新增CustomerAccessTokenConverter类:

/**
 * 自定义AccessTokenConverter转换<br/>
 * 不定义这个类,那么自定义的字段的值不会取到(实际上是传过来了);<br/>
 * 所以,这里主要的操作就是把传过来的自定义的值保存下来。<br/>
 * 关于内部类CustomerUserAuthenticationConverter中的重写方法和私有方法getAuthorities,都是仿照父类DefaultUserAuthenticationConverter来写的,
 * 在debug模式下,跟下代码就知道为什么这么设置值了
 * @author FYK<br/> 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");
		}
	}
}

类注释已经说明清楚了,这里就不在赘言了。

资源服务配置类改造:OAuth2ResourceConfig

修改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值相同,就可以填充好了。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐