spring-securty-oauth2端点

在接入oauth2之后可以访问相关oauth2接口

[/oauth/authorize]
[/oauth/token]
[/oauth/check_token]
[/oauth/confirm_access]
[/oauth/token_key]
[/oauth/error]

会好奇是如何实现的。其实内部是基于spring mvc

我们搭建oauth2服务器会有以下配置

@Configuration
@EnableAuthorizationServer//<1>具体端点初始化在这里
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
 .......
}

<1>

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//<2>AuthorizationServerEndpointsConfiguration 为端点初始化自动化配置类
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}

<2>

@Configuration
@Import({TokenKeyEndpointRegistrar.class})
public class AuthorizationServerEndpointsConfiguration {
    private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

    public AuthorizationServerEndpointsConfiguration() {
    }

   
    //<3> /oauth/authorize spring mvchandler
    @Bean
    public AuthorizationEndpoint authorizationEndpoint() throws Exception {
        AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
        FrameworkEndpointHandlerMapping mapping = this.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
        authorizationEndpoint.setUserApprovalPage(this.extractPath(mapping, "/oauth/confirm_access"));
        authorizationEndpoint.setProviderExceptionHandler(this.exceptionTranslator());
        authorizationEndpoint.setErrorPage(this.extractPath(mapping, "/oauth/error"));
        authorizationEndpoint.setTokenGranter(this.tokenGranter());
        authorizationEndpoint.setClientDetailsService(this.clientDetailsService);
        authorizationEndpoint.setAuthorizationCodeServices(this.authorizationCodeServices());
        authorizationEndpoint.setOAuth2RequestFactory(this.oauth2RequestFactory());
        authorizationEndpoint.setOAuth2RequestValidator(this.oauth2RequestValidator());
        authorizationEndpoint.setUserApprovalHandler(this.userApprovalHandler());
        return authorizationEndpoint;
    }

     // /oauth/token spring mvchandler
    @Bean
    public TokenEndpoint tokenEndpoint() throws Exception {
        TokenEndpoint tokenEndpoint = new TokenEndpoint();
        tokenEndpoint.setClientDetailsService(this.clientDetailsService);
        tokenEndpoint.setProviderExceptionHandler(this.exceptionTranslator());
        tokenEndpoint.setTokenGranter(this.tokenGranter());
        tokenEndpoint.setOAuth2RequestFactory(this.oauth2RequestFactory());
        tokenEndpoint.setOAuth2RequestValidator(this.oauth2RequestValidator());
        tokenEndpoint.setAllowedRequestMethods(this.allowedTokenEndpointRequestMethods());
        return tokenEndpoint;
    }

     // /oauth/check_token spring mvchandler
    @Bean
    public CheckTokenEndpoint checkTokenEndpoint() {
        CheckTokenEndpoint endpoint = new CheckTokenEndpoint(this.getEndpointsConfigurer().getResourceServerTokenServices());
        endpoint.setAccessTokenConverter(this.getEndpointsConfigurer().getAccessTokenConverter());
        endpoint.setExceptionTranslator(this.exceptionTranslator());
        return endpoint;
    }

    // /oauth/confirm_access 端点
    @Bean
    public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() {
        return new WhitelabelApprovalEndpoint();
    }

    // /oauth/error 端点
    @Bean
    public WhitelabelErrorEndpoint whitelabelErrorEndpoint() {
        return new WhitelabelErrorEndpoint();
    }

    //<3>spring mvc RequestMappingHandlerMapping 扩展暴露端点
    @Bean
    public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exception {
        return this.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
    }

}

<4>

public class FrameworkEndpointHandlerMapping extends RequestMappingHandlerMapping {

    //<5>匹配端点
    protected boolean isHandler(Class<?> beanType) {
        return AnnotationUtils.findAnnotation(beanType, FrameworkEndpoint.class) != null;
    }

}

<5>

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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

import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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;

@FrameworkEndpoint //oauth2端点标记
public class TokenEndpoint extends AbstractEndpoint {
    private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
    private Set<HttpMethod> allowedRequestMethods;

    public TokenEndpoint() {
        this.allowedRequestMethods = new HashSet(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 (!this.allowedRequestMethods.contains(HttpMethod.GET)) {
            throw new HttpRequestMethodNotSupportedException("GET");
        } else {
            return this.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.");
        } else {
            String clientId = this.getClientId(principal);
            ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
            TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
            if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
                throw new InvalidClientException("Given client ID does not match authenticated client");
            } else {
                if (authenticatedClient != null) {
                    this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
                }

                if (!StringUtils.hasText(tokenRequest.getGrantType())) {
                    throw new InvalidRequestException("Missing grant type");
                } else if (tokenRequest.getGrantType().equals("implicit")) {
                    throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
                } else {
                    if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
                        this.logger.debug("Clearing scope of incoming token request");
                        tokenRequest.setScope(Collections.emptySet());
                    }

                    if (this.isRefreshTokenRequest(parameters)) {
                        tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
                    }

                    OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
                    if (token == null) {
                        throw new UnsupportedGrantTypeException("Unsupported grant type");
                    } else {
                        return this.getResponse(token);
                    }
                }
            }
        }
    }

    protected String getClientId(Principal principal) {
        Authentication client = (Authentication)principal;
        if (!client.isAuthenticated()) {
            throw new InsufficientAuthenticationException("The client is not authenticated.");
        } else {
            String clientId = client.getName();
            if (client instanceof OAuth2Authentication) {
                clientId = ((OAuth2Authentication)client).getOAuth2Request().getClientId();
            }

            return clientId;
        }
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public ResponseEntity<OAuth2Exception> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception {
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        }

        return this.getExceptionTranslator().translate(e);
    }

    @ExceptionHandler({Exception.class})
    public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
        if (this.logger.isErrorEnabled()) {
            this.logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e);
        }

        return this.getExceptionTranslator().translate(e);
    }

    @ExceptionHandler({ClientRegistrationException.class})
    public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception {
        if (this.logger.isWarnEnabled()) {
            this.logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        }

        return this.getExceptionTranslator().translate(new BadClientCredentialsException());
    }

    @ExceptionHandler({OAuth2Exception.class})
    public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception {
        if (this.logger.isWarnEnabled()) {
            this.logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        }

        return this.getExceptionTranslator().translate(e);
    }

    private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        return new ResponseEntity(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;
    }
}

 redis key相关

access:{token}
token与授权token映射 1天   登录用户数

根据token获取用户的 授权token信息  

"access_token": "bb6ecfd7-3895-4394-b0cd-2b1fbcd9c0d3",
    "token_type": "bearer",
    "refresh_token": "217ed8aa-02bd-4c20-9990-c2ce104089c7",
    "expires_in": 85510,
    "scope": "all",

2
auth_to_access:{userName+CLIENT_ID+SCOPE}
用户信息与授权token映射 1天   登录用户数 根据用户信息获取 授权token信息(自定义)

    "access_token": "bb6ecfd7-3895-4394-b0cd-2b1fbcd9c0d3",
    "token_type": "bearer",
    "refresh_token": "217ed8aa-02bd-4c20-9990-c2ce104089c7",
    "expires_in": 85510,
    "scope": "all",

3
auth:{token}
用户的信息与授权token映射 1天   登录用户数 根据token获取用户基础信息,登录信息,权限等信息
4
refresh_auth:{refreshToken}
refreshToken与用户信息映射       暂时未用
5
access_to_refresh:{refreshToken}
通过accetoken查找refreshtoken与token映射 1天     暂时未用
6
refresh:{refreshToken}
获取用户信息的refreshtoken 1天     暂时未用
7
refresh_to_access:{refreshToken}
refreshtoken与token映射 1天   登录用户数 用于刷新token
8
client_id_to_access:{clientId}
指定应用的关联的授权token集合 1天   登录用户数 清除应用下的所有token
9
uname_to_access:{username+clientId}
username+clientId用户的授权令牌集合 1天   登录用户数 用户可以申请指定应用的各种资源访问token,这里统一记录。
posted @ 2024-05-09 21:14  意犹未尽  阅读(21)  评论(0编辑  收藏  举报