用戶攜帶token 請求資源服務器spring
資源服務器攔截器 攜帶token 去認證服務器 調用tokenstore 對token 合法性校驗安全
資源服務器拿到token,默認只會含有用戶名信息服務器
經過用戶名調用userdetailsservice.loadbyusername 查詢用戶所有信息
網絡
如上步驟在實際使用,會形成認證中心的負載壓力過大,成爲形成整個系統瓶頸的關鍵點。session
更爲詳細的源碼講解能夠參考我上篇文章《Spring Cloud OAuth2 資源服務器CheckToken 源碼解析》app
check-token 涉及到的核心類ide
爲何使用jwt 替代默認的UUID token ?
經過jwt 訪問資源服務器後,再也不使用check-token 過程,經過對jwt 的解析便可實現身份驗證,登陸信息的傳遞。減小網絡開銷,提升總體微服務集羣的性能微服務
spring security oauth 默認的jwttoken 只含有username,經過擴展TokenEnhancer,實現關鍵字段的注入到 JWT 中,方便資源服務器使用性能
@Bean
spa
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
if (SecurityConstants.CLIENT_CREDENTIALS
.equals(authentication.getOAuth2Request().getGrantType())) {
return accessToken;
}
final Map<String, Object> additionalInfo = new HashMap<>(8);
PigxUser pigxUser = (PigxUser) authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("user_id", pigxUser.getId());
additionalInfo.put("username", pigxUser.getUsername());
additionalInfo.put("dept_id", pigxUser.getDeptId());
additionalInfo.put("tenant_id", pigxUser.getTenantId());
additionalInfo.put("license", SecurityConstants.PIGX_LICENSE);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}
生成的token 以下,含有關鍵的字段
再也不使用RemoteTokenServices 去掉用認證中心 CheckToken,自定義客戶端TokenService
@Slf4j
public class PigxCustomTokenServices implements ResourceServerTokenServices {
@Setter
private TokenStore tokenStore;
@Setter
private DefaultAccessTokenConverter defaultAccessTokenConverter;
@Setter
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter();
defaultAccessTokenConverter.setUserTokenConverter(userTokenConverter);
Map<String, ?> map = jwtAccessTokenConverter.convertAccessToken(readAccessToken(accessToken), oAuth2Authentication);
return defaultAccessTokenConverter.extractAuthentication(map);
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
return tokenStore.readAccessToken(accessToken);
}
}
解析jwt 組裝成Authentication
/**
* @author lengleng
* @date 2019-03-17
* <p>
* jwt 轉化用戶信息
*/
public class PigxUserAuthenticationConverter implements UserAuthenticationConverter {
private static final String USER_ID = "user_id";
private static final String DEPT_ID = "dept_id";
private static final String TENANT_ID = "tenant_id";
private static final String N_A = "N/A";
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
String username = (String) map.get(USERNAME);
Integer id = (Integer) map.get(USER_ID);
Integer deptId = (Integer) map.get(DEPT_ID);
Integer tenantId = (Integer) map.get(TENANT_ID);
PigxUser user = new PigxUser(id, deptId, tenantId, username, N_A, true
, true, true, true, authorities);
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
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");
}
}
資源服務器配置中注入以上配置便可
@Slf4j
public class PigxResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter();
accessTokenConverter.setUserTokenConverter(userTokenConverter);
PigxCustomTokenServices tokenServices = new PigxCustomTokenServices();
// 這裏的簽名key 保持和認證中心一致
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
converter.setVerifier(new MacSigner("123"));
JwtTokenStore jwtTokenStore = new JwtTokenStore(converter);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setJwtAccessTokenConverter(converter);
tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
resources
.authenticationEntryPoint(resourceAuthExceptionEntryPoint)
.tokenServices(tokenServices);
}
}
JWT 的最大缺點是,因爲服務器不保存 session 狀態,所以沒法在使用過程當中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期以前就會始終有效,除非服務器部署額外的邏輯。
去認證服務器校驗的過程就是 經過tokenstore 來控制jwt 安全性的一個方法,去掉Check-token 意味着 jwt token 安全性不可保證
JWT 自己包含了認證信息,一旦泄露,任何人均可以得到該令牌的全部權限。爲了減小盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
爲了減小盜用,JWT 不該該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。