擴展資源服務器解決oauth2 性能瓶頸

  • 用戶攜帶token 請求資源服務器
  • 資源服務器攔截器 攜帶token 去認證服務器 調用tokenstore 對token 合法性校驗
  • 資源服務器拿到token,默認只會含有用戶名信息
  • 通過用戶名調用userdetailsservice.loadbyusername 查詢用戶全部信息

詳細性能瓶頸分析,請參考上篇文章《擴展jwt解決oauth2 性能瓶頸》
本文是針對傳統使用UUID token 的情況進行擴展,提高系統的吞吐率,解決性能瓶頸的問題

默認check-token 解析邏輯

  • RemoteTokenServices 入口
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

    MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    formData.add(tokenName, accessToken);
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
    // 調用認證服務器的check-token 接口檢查token
    Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);

    return tokenConverter.extractAuthentication(map);
}
  • 解析認證服務器返回的信息

DefaultAccessTokenConverter

public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        Map<String, String> parameters = new HashMap<String, String>();
        Set<String> scope = extractScope(map);
        // 主要是 用戶的信息的抽取
        Authentication user = userTokenConverter.extractAuthentication(map);
        // 一些oauth2 信息的填充
        OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
                null);
        return new OAuth2Authentication(request, user);
    }
  • 組裝當前用戶信息

DefaultUserAuthenticationConverter

public Authentication extractAuthentication(Map<String, ?> map) {
    if (map.containsKey(USERNAME)) {
        Object principal = map.get(USERNAME);
        Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
        if (userDetailsService != null) {
            UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
            authorities = user.getAuthorities();
            principal = user;
        }
        return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
    }
    return null;
}

問題分析

  • 認證服務器check-token 返回的全部信息
  • 資源服務器在根據返回信息組裝用戶信息的時候,只是用了username
  • 如果設置了 userDetailsService 的實現則去調用 loadUserByUsername 再去查詢一次用戶信息

造成問題現象

  1. 如果設置了userDetailsService 即可在spring security 上下文獲取用戶的全部信息,不設置則只能得到用戶名。
  2. 增加了一次查詢邏輯,對性能產生不必要的影響

解決問題

  • 擴展UserAuthenticationConverter 的解析過程,把認證服務器返回的信息全部組裝到spring security的上下文對象中
/**
 * @author lengleng
 * @date 2019-03-07
 * <p>
 * 根據checktoken 的結果轉化用戶信息
 */
public class PigxUserAuthenticationConverter implements UserAuthenticationConverter {
    private static final String N_A = "N/A";
    // map 是check-token 返回的全部信息
    @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(SecurityConstants.DETAILS_USER_ID);
            Integer deptId = (Integer) map.get(SecurityConstants.DETAILS_DEPT_ID);
            Integer tenantId = (Integer) map.get(SecurityConstants.DETAILS_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;
    }
}
  • 給remoteTokenServices 注入這個實現
public class PigxResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter();
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        remoteTokenServices.setRestTemplate(lbRestTemplate);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
        resources.
                .tokenServices(remoteTokenServices);
    }
}
  • 完成擴展,再來看文章開頭的流程圖就變成了如下

關注我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章