OAuth2資源服務器驗證源碼分析

目錄

資源服務器配置類

校驗服務器字段配置

jwt校驗路徑追蹤

OAuth2AuthenticationProcessingFilter

OAuth2AuthenticationManager

DefaultTokenServices

第一次JwtTokenStore

第一次JwtAccessTokenConverter

傳回DefaultTokenServices

第二次JwtTokenStore

第二次JwtAccessTokenConverter

DefaultAccessTokenConverter

DefaultUserAuthenticationConverter

回到DefaultAccessTokenConverter

再次傳回DefaultTokenServices

傳回OAuth2AuthenticationManager

傳回OAuth2AuthenticationProcessingFilter

jwt校驗總結

@PreAuthorize中hasAuthority怎麼獲取權限的?

SecurityExpressionRoot怎麼生成的?


資源服務器配置類

這裏因爲配置了TokenStore 和JwtAccessTokenConverter ,所以下面解析jwt使用的配置是這個

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的 PreAuthorize註解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    //公鑰
    private static final String PUBLIC_KEY = "publickey.txt";

    //定義JwtTokenStore,使用jwt令牌
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    //定義JJwtAccessTokenConverter,使用jwt令牌
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }
    /**
     * 獲取非對稱加密公鑰 Key
     * @return 公鑰 Key
     * */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }
    //Http安全配置,對每個到達系統的http請求鏈接進行校驗
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //所有請求必須認證通過
        http.authorizeRequests()
                .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources","/swagger-resources/configuration/security",
                        "/swagger-ui.html","/webjars/**","/course/courseview/**").permitAll()//針對swagger-ui進行放行
                .anyRequest().authenticated();
    }
}

校驗服務器字段配置

/**
 * 自定義token顯示內容
 */
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
    @Autowired
    UserDetailsService userDetailsService;

    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        LinkedHashMap response = new LinkedHashMap();
        String name = authentication.getName();
        response.put("user_name", name);

        Object principal = authentication.getPrincipal();
        UserJwt userJwt = null;
        if(principal instanceof  UserJwt){
            userJwt = (UserJwt) principal;
        }else{
            //refresh_token默認不去調用userdetailService獲取用戶信息,這裏我們手動去調用,得到 UserJwt
            UserDetails userDetails = userDetailsService.loadUserByUsername(name);
            userJwt = (UserJwt) userDetails;
        }
        response.put("name", userJwt.getName());
        response.put("id", userJwt.getId());
        response.put("utype",userJwt.getUtype());
        response.put("userpic",userJwt.getUserpic());
        response.put("companyId",userJwt.getCompanyId());
        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }

        return response;
    }


}

jwt校驗路徑追蹤

當已經登錄成功,得到token進行請求資源服務器

OAuth2AuthenticationProcessingFilter

首先會被

過濾器進行攔截,通過請求header得到JWT token,將token封裝到Authorication中

然後調用

的authenticate方法

進行驗證解析jwt Token

OAuth2AuthenticationManager

authenticate會通過Authorization得到jwtToken,並將jwtToken作爲參數調用

的loadAuthentication方法

DefaultTokenServices

loadAuthentication會調用

的readAccessToken方法,讀取解析token中的內容

第一次JwtTokenStore

readAccessToken會調用convertAccessToken,方法體內會調用

經過配置文件中配置的公鑰進行解析jwt Token中的內容得到 jwt中 claim的map集合(包含所有jwt的可可視信息,DefaultUserAuthenticationConverter中配置的屬性項)

並且將 map集合和jwt Token 作爲參數 繼續調用JwtAccessTokenConverter的extractAccessToken

第一次JwtAccessTokenConverter

extractAccessToken會往下傳遞調用

的extractAccessToken方法。

進行map集合的數據格式整理,最後封裝到

然後傳回

傳回DefaultTokenServices

接着會將上面傳回的OAuth2AccessToken作爲參數調用

的readAuthentication方法 進行權限讀取。

第二次JwtTokenStore

readAuthentication會將OAuth2AccessToken中的jwt Token取出

進行解析信息後將map傳入

extractAuthentication方法

第二次JwtAccessTokenConverter

緊接着會像上面過程一樣。會將參數傳遞調用

的extractAuthentication方法

 

DefaultAccessTokenConverter

extractAuthentication方法中會將map作爲參數傳入

的extractAuthentication方法 進行權限封裝

DefaultUserAuthenticationConverter

2個方法主要作用是判斷map中是否有用戶名(user_name)和權限(authorities)2個屬性。如果沒有user_name直接返回爲null,反之返回由這2個屬性封裝好的UsernamePasswordAuthenticationToken。

(可以在配置類配置userDetailsService,配置後可以進行通過loadUserByUserName校驗然後更改原有的權限和用戶。)

這裏需要注意 user_name和authorities是固定好的名字,所以在DefaultUserAuthenticationConverter中需要注意傳入jwt claim的參數名。

public Authentication extractAuthentication(Map<String, ?> map) {
    //檢查map中包不包含user_name
    if (map.containsKey("user_name")) {
        Object principal = map.get("user_name");
        Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
        if (this.userDetailsService != null) {
            UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get("user_name"));
            authorities = user.getAuthorities();
            principal = user;
        }

        return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
    } else {
        return null;
    }
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
    if (!map.containsKey("authorities")) {
        return this.defaultAuthorities;
    } else {
        Object authorities = map.get("authorities");
        if (authorities instanceof String) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
        } else if (authorities instanceof Collection) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
        } else {
            throw new IllegalArgumentException("Authorities must be either a String or a Collection");
        }
    }
}

回到DefaultAccessTokenConverter

注意綠字屬性名,這裏代表有些屬性名是固定的。

注意紅框部分。這裏代表了jwt Token 沒有帶user_name 也可以,只要jwt Token中包含了authorities屬性,然後將Authentication和OAuth2Request封裝爲OAuth2Authentication依次傳回到DefaultTokenServices

再次傳回DefaultTokenServices

這裏也需要注意。這個類的ClientDetailsService也是可以配置的,配置後可以進行檢驗ClientId是否合法

繼續傳回

傳回OAuth2AuthenticationManager

傳回OAuth2AuthenticationProcessingFilter

最後將用戶認證信息設置到SecurityContext中

jwt校驗總結

通過設置請求頭方式請求資源服務器,會被OAuth2的OAuth2AuthenticationProcessingFilter過濾器所攔截,通過資源服務配置文件中指定的JwtTokenStore和JwtAccessTokenConverter,使用配置好的公鑰進行解析jwt Token得到一系列信息。這裏需要注意,如user_name,authorities等屬性名已經固定。所以需要在token生成的時候就注意名字規範。最後將封裝好的Authorication設置到SecurityContext中。

@PreAuthorize中hasAuthority怎麼獲取權限的?

hasAuthority爲SecurityExpressionRoot的方法,此方法會從Authorication中獲取authorities集合,並檢查hasAuthority中要求的權限是否在集合中。滿足要求則執行,否則拋出異常。

 

SecurityExpressionRoot怎麼生成的?

經過路徑查看可以看出通過Aop的方式在

的beforeInvocation方法傳入Authorication一路傳遞參數,最後初始化SecurityExpressionRoot類

 

 

 

 

 

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