Spring Security实践之extractKey引起的权限刷新问题

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值