Springsecurity-oauth2之RemoteTokenServices

原文鏈接:https://cloud.tencent.com/developer/article/1413439

 Spring-security-oauth2的版本是2.2.3。

    RemoteTokenServices是用於向遠程認證服務器驗證token,同時獲取token對應的用戶的信息。

                                                                 圖1

    RemoteTokenServices會通過RestTemplate調用遠程服務,我們在使用這個類時,要設置checkTokenEndpointUrl、clientId、clientSecret等。

    RemoteTokenServices的源碼如下,

 List-1

public class RemoteTokenServices implements ResourceServerTokenServices {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private RestOperations restTemplate = new RestTemplate();
    private String checkTokenEndpointUrl;
    private String clientId;
    private String clientSecret;
    private String tokenName = "token";
    private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();

    public RemoteTokenServices() {
        ((RestTemplate)this.restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400) {
                    super.handleError(response);
                }

            }
        });
    }

    public void setRestTemplate(RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
        this.checkTokenEndpointUrl = checkTokenEndpointUrl;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
        this.tokenConverter = accessTokenConverter;
    }

    public void setTokenName(String tokenName) {
        this.tokenName = tokenName;
    }

    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        formData.add(this.tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
        Map<String, Object> map = this.postForMap(this.checkTokenEndpointUrl, formData, headers);
        if (map.containsKey("error")) {
            this.logger.debug("check_token returned error: " + map.get("error"));
            throw new InvalidTokenException(accessToken);
        } else if (!Boolean.TRUE.equals(map.get("active"))) {
            this.logger.debug("check_token returned active attribute: " + map.get("active"));
            throw new InvalidTokenException(accessToken);
        } else {
            return this.tokenConverter.extractAuthentication(map);
        }
    }

    public OAuth2AccessToken readAccessToken(String accessToken) {
        throw new UnsupportedOperationException("Not supported: read access token");
    }

    private Stri』g getAuthorizationHeader(String clientId, String clientSecret) {
        if (clientId == null || clientSecret == null) {
            this.logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
        }

        String creds = String.format("%s:%s", clientId, clientSecret);

        try {
            return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException var5) {
            throw new IllegalStateException("Could not convert String");
        }
    }

    private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        if (headers.getContentType() == null) {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        }

        Map map = (Map)this.restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
        return map;
    }
}

圖1的步驟4中,請求/oauth/check_token,如下List-2,查詢token的合法性,之後返回其信息。

    List-2

package org.springframework.security.oauth2.provider.endpoint;

@FrameworkEndpoint
public class CheckTokenEndpoint {
    private ResourceServerTokenServices resourceServerTokenServices;
    private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
    protected final Log logger = LogFactory.getLog(this.getClass());
    private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

    public CheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) {
        this.resourceServerTokenServices = resourceServerTokenServices;
    }

    public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
        this.exceptionTranslator = exceptionTranslator;
    }

    public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
        this.accessTokenConverter = accessTokenConverter;
    }

    @RequestMapping({"/oauth/check_token"})
    @ResponseBody
    public Map<String, ?> checkToken(@RequestParam("token") String value) {
        OAuth2AccessToken token = this.resourceServerTokenServices.readAccessToken(value);
        if (token == null) {
            throw new InvalidTokenException("Token was not recognised");
        } else if (token.isExpired()) {
            throw new InvalidTokenException("Token has expired");
        } else {
            OAuth2Authentication authentication = this.resourceServerTokenServices.loadAuthentication(token.getValue());
            Map<String, ?> response = this.accessTokenConverter.convertAccessToken(token, authentication);
            return response;
        }
    }
......

圖1中的步驟8中,如果UserDetailsService不爲空,那麼會調用它的loadUserByUsername方法來獲取用戶信息,

    List-3

public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
    ......

    public Authentication extractAuthentication(Map<String, ?> map) {
        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;
        }
    }
......

 建議讀者閱讀源碼。

    在哪裏使用到RemoteTokenServices呢,看如下圖2,OAuth2AuthenticationProcessingFilter會攔截請求,將access_token轉換爲用戶信息。

                                                                  圖2

Reference:

  1. http://springcloud.cn/view/431
  2. Spring-security-oauth2的源碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章