1.问题
通过给UserDetail设置不同的Authority来实现一个用户的权限改变,但是最近在对发现当给UserDetail设置不同Authority的时候,重新登录之后加载出来的权限没有变化。
2.思考
首先基于 Spring Security从单体应用到分布式(四)-基于Zuul的Oauth2应用鉴权逻辑如下图,当经过OAuth2ClientAuthenticationProcessingFilter的时候,如果当前session能获取acesstoken的时候,我们就能直接访问目标API。如果当前session中无法获取acesstoken将会通过流成图获取acesstoken,但是我通过浏览器清理session之后发现,发现重新登录获取的acesstoken没有改变。
3.分析
在Spring Security框架获取创建token的路径为/oauth/token,而暴露/oauth/token对应的类为TokenEndpoint,试着从源码中寻找答案,其中找到了
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
再往下进入到DefaultTokenServices的createAccessToken方法的时候,通过分析代码可知,如果通过tokenStore.getAccessToken()获取的existingAccessToken不为空, 此时则进入if循环不生成新的accesstoken,而当existingAccessToken为空的时候则会往下执行创建新的accesstoken。
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// Re-store the access token in case the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
进入tokenStore.getAccessToken()方法可知,通过authenticationKeyGenerator.extractKey()方法得到的key值从而获取一个accesstoken。
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
String key = authenticationKeyGenerator.extractKey(authentication);
OAuth2AccessToken accessToken = authenticationToAccessTokenStore.get(key);
if (accessToken != null
&& !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) {
// Keep the stores consistent (maybe the same user is represented by this authentication but the details
// have changed)
storeAccessToken(accessToken, authentication);
}
return accessToken;
}
再进入authenticationKeyGenerator.extractKey(),在框架默认的判断逻辑当中,仅当使用用户名,域,客户端id作为key的生成标准,即无论我们无论如何修改当前用户的权限都不会引起accesstoken的改变。
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<String, String>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
}
return generateKey(values);
}
4.修改
通过values.put("test", UUID.randomUUID().toString());模拟改变过后的权限
public class CustomKeyGenerator implements AuthenticationKeyGenerator {
private static final String CLIENT_ID = "client_id";
private static final String SCOPE = "scope";
private static final String USERNAME = "username";
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<String, String>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
}
values.put("test", UUID.randomUUID().toString());
return generateKey(values);
}
protected String generateKey(Map<String, String> values) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));
return String.format("%032x", new BigInteger(1, bytes));
} catch (NoSuchAlgorithmException nsae) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).", nsae);
} catch (UnsupportedEncodingException uee) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).", uee);
}
}
}
5.结果
在个人信息接口中添加accesstoken的输出
@RequestMapping(value ="/userinfo", method = RequestMethod.GET)
public Principal user(Principal principal) {
OAuth2Authentication oAuth2Authentication= (OAuth2Authentication) principal;
OAuth2AuthenticationDetails auth2AccessToken= (OAuth2AuthenticationDetails) oAuth2Authentication.getDetails();
System.out.println("currentAccessToken:"+auth2AccessToken.getTokenValue());
return principal;
}
使用原生的KeyGenerator的时候,当刷新session后生成的accesstoken因为只使用用户名,域,客户端id作为生成key的参数,因此accesstoken不变,
使用自定义的KeyGenerator的时候,当刷新session后生成的accesstoken,通过随机因子生成使accesstoken每次得以生成新的值。
github:https://github.com/tale2009/spring-security-learning/tree/master/cloudsecurity