1. 程式人生 > >springCloud微服務系列——OAuth2+JWT模式下的【資源伺服器】獲得【自定義資訊】

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


    大體的流程再總結一下:資源認證伺服器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<? extends GrantedAuthority> defaultAuthorities;
	
	private UserDetailsService userDetailsService;
	
	private PrincipalConverter principalConverter;
	
	public AdditionalUserAuthenticationConverter(PrincipalConverter principalConverter) {
		this.principalConverter = principalConverter;
	}
	
	@Override
	public Authentication extractAuthentication(Map<String, ?> map) {
		if (map.containsKey(USERNAME)) {
			Object principal = principalConverter.converter(map);
			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;
	}
	
	private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> 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<String, ?> map) {
			return ProcessPrincipalHelper.converter(map);
		}
	};
}
public class ProcessPrincipalHelper {

	private static final String USER_NAME_KEY = "username";
	
	public static Object converter(Map<String, ?> map) {
		Map<String, Object> params = new HashMap<String, Object>();
		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<String, ?> 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()方法獲取我們自己實現的登入使用者資訊物件了