扩展jwt解决oauth2 性能瓶颈

作者: 冷冷zz | 来源:发表于2019-03-18 09:08 被阅读8次

    oauth2 性能瓶颈

    资源服务器的请求都会被拦截 到认证服务器校验合法性 (如下图)

    • 用户携带token 请求资源服务器
    • 资源服务器拦截器 携带token 去认证服务器 调用tokenstore 对token 合法性校验
    • 资源服务器拿到token,默认只会含有用户名信息
    • 通过用户名调用userdetailsservice.loadbyusername 查询用户全部信息

    如上步骤在实际使用,会造成认证中心的负载压力过大,成为造成整个系统瓶颈的关键点。

    image

    check-token 过程中涉及的源码

    扩展jwt 生成携带用户详细信息

    • 为什么使用jwt 替代默认的UUID token ?
      通过jwt 访问资源服务器后,不再使用check-token 过程,通过对jwt 的解析即可实现身份验证,登录信息的传递。减少网络开销,提高整体微服务集群的性能
    • spring security oauth 默认的jwttoken 只含有username,通过扩展TokenEnhancer,实现关键字段的注入到 JWT 中,方便资源服务器使用
        @Bean
        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 如下,含有关键的字段


      image

    重写默认的资源服务器处理行为

    • 不再使用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 扩展后带来的问题

    • JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

    • 去认证服务器校验的过程就是 通过tokenstore 来控制jwt 安全性的一个方法,去掉Check-token 意味着 jwt token 安全性不可保证

    • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

    • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

    关注我

    相关文章

      网友评论

        本文标题:扩展jwt解决oauth2 性能瓶颈

        本文链接:https://www.haomeiwen.com/subject/hrbmmqtx.html