Spring Security 之 OAuth2 TokenEndpoint

Spring Security 之 OAuth2 TokenEndpoint

Spring Security OAuth2 认证通过返回TOKEN时,是通过TokenEndpoint来实现的

TokenEndpoint

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. package org.springframework.security.oauth2.provider.endpoint; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2RequestValidator; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; * Endpoint for token requests as described in the OAuth2 spec. Clients post requests with a <code>grant_type</code> * parameter (e.g. "authorization_code") and other parameters as determined by the grant type. Supported grant types are * handled by the provided {@link #setTokenGranter(org.springframework.security.oauth2.provider.TokenGranter) token * granter}. * Clients must be authenticated using a Spring Security {@link Authentication} to access this endpoint, and the client * id is extracted from the authentication token. The best way to arrange this (as per the OAuth2 spec) is to use HTTP * basic authentication for this endpoint with standard Spring Security support. * @author Dave Syer @FrameworkEndpoint public class TokenEndpoint extends AbstractEndpoint { private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator(); private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST)); @RequestMapping(value = "/oauth/token", method=RequestMethod.GET) public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { if (!allowedRequestMethods.contains(HttpMethod.GET)) { throw new HttpRequestMethodNotSupportedException("GET"); return postAccessToken(principal, parameters); @RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); String clientId = getClientId(principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); if (clientId != null && !clientId.equals("")) { // Only validate the client details if a client authenticated during this // request. if (!clientId.equals(tokenRequest.getClientId())) { // double check to make sure that the client ID in the token request is the same as that in the // authenticated client throw new InvalidClientException("Given client ID does not match authenticated client"); if (authenticatedClient != null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); if (tokenRequest.getGrantType().equals("implicit")) { throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); if (isAuthCodeRequest(parameters)) { // The scope was requested or determined during the authorization step if (!tokenRequest.getScope().isEmpty()) { logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections.<String> emptySet()); if (isRefreshTokenRequest(parameters)) { // A refresh token has its own default scopes, so we should ignore any added by the factory here. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); return getResponse(token); * @param principal the currently authentication principal * @return a client id if there is one in the principal protected String getClientId(Principal principal) { Authentication client = (Authentication) principal; if (!client.isAuthenticated()) { throw new InsufficientAuthenticationException("The client is not authenticated."); String clientId = client.getName(); if (client instanceof OAuth2Authentication) { // Might be a client and user combined authentication clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId(); return clientId; @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity<OAuth2Exception> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception { if (logger.isInfoEnabled()) { logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); return getExceptionTranslator().translate(e); @ExceptionHandler(Exception.class) public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception { if (logger.isWarnEnabled()) { logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); return getExceptionTranslator().translate(e); @ExceptionHandler(ClientRegistrationException.class) public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception { if (logger.isWarnEnabled()) { logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); return getExceptionTranslator().translate(new BadClientCredentialsException()); @ExceptionHandler(OAuth2Exception.class) public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception { if (logger.isWarnEnabled()) { logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); return getExceptionTranslator().translate(e); private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) { HttpHeaders headers = new HttpHeaders(); headers.set("Cache-Control", "no-store"); headers.set("Pragma", "no-cache"); headers.set("Content-Type", "application/json;charset=UTF-8"); return new ResponseEntity<OAuth2AccessToken>(accessToken, headers, HttpStatus.OK); private boolean isRefreshTokenRequest(Map<String, String> parameters) { return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null; private boolean isAuthCodeRequest(Map<String, String> parameters) { return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null; public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) { this.oAuth2RequestValidator = oAuth2RequestValidator; public void setAllowedRequestMethods(Set<HttpMethod> allowedRequestMethods) { this.allowedRequestMethods = allowedRequestMethods;

自定义返回格式

@RestController
@RequestMapping("/v2/oauth")
public class OauthV2Controller {
    @Autowired
    private TokenEndpoint tokenEndpoint;
    @GetMapping("/token")
    public R<?> getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        return custom(tokenEndpoint.getAccessToken(principal, parameters).getBody());
    @PostMapping("/token")
    public R<?> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        return custom(tokenEndpoint.postAccessToken(principal, parameters).getBody());
    // 自定义返回格式
    private R<?> custom(OAuth2AccessToken oAuth2AccessToken) {
        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
        Map<String, Object> maper = new LinkedHashMap<String, Object>(token.getAdditionalInformation());
        maper.put("access_token", token.getValue());
        maper.put("token_type", token.getTokenType());
        maper.put("expires_in", token.getExpiresIn());
        maper.put("scope", token.getScope());
        if (token.getRefreshToken() != null) {
            maper.put("refresh_token", token.getRefreshToken().getValue());