Springsceurity使用TokenEnhancer和JwtAccessConverter增强jwt令牌原理

什么是JWT

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。(更多信息建议去官网了解)

使用JWT替换传统Token有很多好处,比如:

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 不需要在服务端保存会话信息,特别适用于分布式微服务。

先了解一下使用到的两个组件的作用:
JwtAccessTokenConverter:TokenEnhancer的子类,帮助程序在JWT编码的令牌值和OAuth身份验证信息之间进行转换(在两个方向上),同时充当TokenEnhancer授予令牌的时间。
自定义的JwtAccessTokenConverter(把自己设置的jwt签名加入accessTokenConverter中)

@Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
            accessTokenConverter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
            return accessTokenConverter;
        }

TokenEnhancer:在AuthorizationServerTokenServices 实现存储访问令牌之前增强访问令牌的策略。
下面是自定义TokenEnhancer的代码(把附加信息加入oAuth2AccessToken中):

public class TuckerJwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String ,Object> info = new HashMap<>();
        info.put("admin","tucker");
        info.put("company","bobo");
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
        //System.out.println(oAuth2AccessToken.getAdditionalInformation());
        return oAuth2AccessToken;
    }
}

下面开始增强JWT令牌
Springsecurity默认生成Token的方法是DfaultTokenServices的createAccessToken方法:

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
        int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
        if (validitySeconds > 0) {
            token.setExpiration(new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L));
        }

        token.setRefreshToken(refreshToken);
        token.setScope(authentication.getOAuth2Request().getScope());
        return (OAuth2AccessToken)(this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
    }

该方法做了五件事:

  • 使用UUID生成Token
  • 判断Token是否过期,如果没过期,就把过期时间设为当前时间加1000s
  • 设置刷新令牌
  • 设置权限
  • 判断是否有增强器,如果有就调用它的enhance方法

对令牌的增强操作就在enhance方法中
下面在配置类中,将TokenEnhancer和JwtAccessConverter加到一个enhancerChain中

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);

        if (jwtAccessTokenConverter != null && jwtTokenEnhancer !=null){
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancers);//将自定义Enhancer加入EnhancerChain的delegates数组中

            endpoints.tokenEnhancer(enhancerChain)//为什么不直接把jwtTokenEnhancer加在这个位置呢?
            .accessTokenConverter(jwtAccessTokenConverter);
        }
    }

下面开始分析上面的问题,下面是TokenEnhancerChain 的源码:

public class TokenEnhancerChain implements TokenEnhancer {
    private List<TokenEnhancer> delegates = Collections.emptyList();

    public TokenEnhancerChain() {
    }

    public void setTokenEnhancers(List<TokenEnhancer> delegates) {
        this.delegates = delegates;
    }

    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        OAuth2AccessToken result = accessToken;

        TokenEnhancer enhancer;
        for(Iterator var4 = this.delegates.iterator(); var4.hasNext(); result = enhancer.enhance(result, authentication)) {
            enhancer = (TokenEnhancer)var4.next();
        }

        return result;
    }
}

它也是一个TokenEhancer的子类,Springsecurity把执行enhancer方法的任务委派给这个TokenEhancerChain,它的任务就是执行我们配置的enhancer。

下面是TokenEhancerChain的delegates数组中的元素,也就是我们自定义的TukcerTokenEnhancer和Springceurity中的JwtAccessTokenConverter,signingKey在我们自定JwtAccessTokenConverter这个Bena初始化时已经设置进去了
在这里插入图片描述
下面是JwtAccessTokenConverter中的enhancer方法中:

public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
		// info数组用来存储附加信息,也就是我们自定义的TokenEnhancer中的附附加信息,
        Map<String, Object> info = new LinkedHashMap(accessToken.getAdditionalInformation());
        String tokenId = result.getValue();
        if (!info.containsKey("jti")) {
            info.put("jti", tokenId);
        } else {
            tokenId = (String)info.get("jti");
        }

        result.setAdditionalInformation(info);
        result.setValue(this.encode(result, authentication));
        OAuth2RefreshToken refreshToken = result.getRefreshToken();
        if (refreshToken != null) {
            DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
            encodedRefreshToken.setValue(refreshToken.getValue());
            encodedRefreshToken.setExpiration((Date)null);

            try {
                Map<String, Object> claims = this.objectMapper.parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
                if (claims.containsKey("jti")) {
                    encodedRefreshToken.setValue(claims.get("jti").toString());
                }
            } catch (IllegalArgumentException var11) {
            }

            Map<String, Object> refreshTokenInfo = new LinkedHashMap(accessToken.getAdditionalInformation());
            refreshTokenInfo.put("jti", encodedRefreshToken.getValue());
            refreshTokenInfo.put("ati", tokenId);
            encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
            DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(this.encode(encodedRefreshToken, authentication));
            if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
                Date expiration = ((ExpiringOAuth2RefreshToken)refreshToken).getExpiration();
                encodedRefreshToken.setExpiration(expiration);
                token = new DefaultExpiringOAuth2RefreshToken(this.encode(encodedRefreshToken, authentication), expiration);
            }

            result.setRefreshToken((OAuth2RefreshToken)token);
        }

        return result;
    }

上面说这个Enhancer的作用是:帮助程序在JWT编码的令牌值和OAuth身份验证信息之间进行转换(在两个方向上),同时充当TokenEnhancer授予令牌的时间。通俗点讲它做了两件事:

  • 给JWT令牌中设置附加信息和jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
  • 判断请求中是否有refreshToken,如果有,就重新设置refreshToken并加入附加信息

我们自定义的TokenEnhancer接收调用DefaultOAuth2AccessToken的setAdditionalInformation(info)方法时,建立一个新的LinkedHashMap覆盖当前存有附加信息的Map

public void setAdditionalInformation(Map<String, Object> additionalInformation) {
        this.additionalInformation = new LinkedHashMap(additionalInformation);
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值