springCloud微服务系列——OAuth2+JWT模式下的【资源服务器】获得【自定义信息】

       回过头来说一下资源服务器的问题点吧,这里OAuth2+JWT用的是spring security,具体怎么用spring security搭建资源服务器我就不说了。这里要讨论的问题是这样的,我们希望在spring mvc中,直接通过如下的形式获得登录用户信息

@GetMapping("/me")
public Authentication me(OAuth2Authentication user) {
	ProcessUser principal = (ProcessUser) user.getPrincipal(); // 获得自己实现的类
	return user;
}

        其中ProcessUser是我们自己实现的类,包含了通过tokenEnchance扩展的自定义用户信息。

        一、spring security资源服务器源码解析

        为了找到实现的思路,我们需要分析源码,找寻线索。和之前的文章一样,我把执行的关键部分总结成了一张图,有兴趣的可以自己打断点跟踪一遍。这里要吐槽一下,本来用写得画的图,画到一般出bug了,所以后面一部分用的excel

springCloud微服务系列——OAuth2+JWT模式下的【资源服务器】获得【自定义信息】_第1张图片

    大体的流程再总结一下:资源认证服务器filter->AuthenticationManager->DefaultTokenServices->JwtTokenStore->JwtAccessTokenConverter->DefaultAccessTokenConverter

  大体思路也总结一下:从http头中取出token值->封装成accessToken对象->解密为map对象->通过DefaultAcccessTokenConverter进行解析map,转换成OAuth2Authentication对象

    二、解决方案

    通过源码分析,我们已经知道最后转换成OAuth2Authentication对象是通过DefaultAcccessTokenConverter的userTokenConverter,而DefaultAcccessTokenConverter则是JwtAccessTokenConverter默认的调用对象,我们可以直接用DefaultAccessToken,只是把它的userTokenConverter换成我们的即可。

    因此我们可以继承JwtAccessTokenConverter,覆写extractAuthentication方法,在其中设置accessTokenConverter的userTokenConverter为我们自定义的Converter。最后再把我们实现的JwtAccessTokenConverter注入到spring中。

    1、实现我们的userTokenConverter

public class AdditionalUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

	private Collection defaultAuthorities;
	
	private UserDetailsService userDetailsService;
	
	private PrincipalConverter principalConverter;
	
	public AdditionalUserAuthenticationConverter(PrincipalConverter principalConverter) {
		this.principalConverter = principalConverter;
	}
	
	@Override
	public Authentication extractAuthentication(Map map) {
		if (map.containsKey(USERNAME)) {
			Object principal = principalConverter.converter(map);
			Collection 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;
	}
	
	private Collection getAuthorities(Map map) {
		if (!map.containsKey(AUTHORITIES)) {
			return defaultAuthorities;
		}
		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");
	}
	
}

      通过这句话Object principal = principalConverter.converter(map);实现转换成我们自己的对象。principalConverter是为了保证通用性而制定的一个接口,具体项目,实现该接口,注册到spring中就可以了。

@Bean
public PrincipalConverter principalConverter() {
	return new PrincipalConverter() {
		@Override
		public Object converter(Map map) {
			return ProcessPrincipalHelper.converter(map);
		}
	};
}
public class ProcessPrincipalHelper {

	private static final String USER_NAME_KEY = "username";
	
	public static Object converter(Map map) {
		Map params = new HashMap();
		for(String key : map.keySet()) {
			params.put(key, map.get(key));
		}
		
		ProcessUser processUser = new ProcessUser((String) map.get(USER_NAME_KEY));
		return BeanUtils.mapToBean(params, processUser);
	}
	
}

   2、继承JwtAccessTokenConverter

    主要是覆写extractAuthentication方法,还是用DefaultAccessTokenConverter处理逻辑,只是把它的userTokenConverter换成我们的实现即可

public class AdditionalJwtAccessTokenConverter extends JwtAccessTokenConverter {

	private DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
	
	private PrincipalConverter principalConverter;
	
	public AdditionalJwtAccessTokenConverter(PrincipalConverter principalConverter) {
		this.principalConverter = principalConverter;
	}
	
	@Override
	public OAuth2Authentication extractAuthentication(Map map) {
		UserAuthenticationConverter userAuthenticationConverter = 
				new AdditionalUserAuthenticationConverter(principalConverter);
		accessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
		return accessTokenConverter.extractAuthentication(map);
	}
	
}

3、注入我们实现的JwtAccessTokenConverter

@Bean
@ConditionalOnMissingBean(name = "jwtAccessTokenConverter")
public JwtAccessTokenConverter jwtAccessTokenConverter() {
	AdditionalJwtAccessTokenConverter jwtAccessTokenConverter = new AdditionalJwtAccessTokenConverter(principalConverter());
	jwtAccessTokenConverter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
	return jwtAccessTokenConverter;
}
至此,我们就可以通过OAuth2Authentication对象的getPrincipal()方法获取我们自己实现的登录用户信息对象了


你可能感兴趣的:(java,web)