spring security 以及 oauth2

spring security oauth2授权码接口源码流程

2021-09-29  本文已影响0人  virtual灬zzZ

使用spring security oauth2 的 授权码模式

获取授权码

路径为 : http://ip:port/oauth/authorize?client_id=CLIENT_ID&response_type=RESPONSE_TYPE&scope=SCOPE&state=STATE

查看源码:
org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint

@RequestMapping({"/oauth/authorize"})
    public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
        AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
        Set<String> responseTypes = authorizationRequest.getResponseTypes();
        if(!responseTypes.contains("token") && !responseTypes.contains("code")) {
            throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
        } else if(authorizationRequest.getClientId() == null) {
            throw new InvalidClientException("A client id must be provided");
        } else {
            try {
                if(principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
                    ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
                    String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
                    String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
                    if(!StringUtils.hasText(resolvedRedirect)) {
                        throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
                    } else {
                        authorizationRequest.setRedirectUri(resolvedRedirect);
                        this.oauth2RequestValidator.validateScope(authorizationRequest, client);
                        authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
                        boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
                        authorizationRequest.setApproved(approved);
                        if(authorizationRequest.isApproved()) {
                            if(responseTypes.contains("token")) {
                                return this.getImplicitGrantResponse(authorizationRequest);
                            }

                            if(responseTypes.contains("code")) {
                                return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
                            }
                        }

                        model.put("authorizationRequest", authorizationRequest);
                        model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", this.unmodifiableMap(authorizationRequest));
                        return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
                    }
                } else {
                    throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
                }
            } catch (RuntimeException var11) {
                sessionStatus.setComplete();
                throw var11;
            }
        }
    }

首先可见,response_type参数是必须为code或token(这是别的oauth2模式),client_id是必传的,之后会根据client_id去查询其是否存在,主要是由ClientDetail去操作 ,而clientDetail是我们配置的,在 继承 AuthorizationServerConfigurerAdapter类中重写**configure(AuthorizationServerEndpointsConfigurer endpoints) ** 方法中,可以是jdbc,如下图,也可以使内存,默认是内存,用hashmap装载。

 @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //MUST:密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息
        //末确认点.userDetailsService(userDetailsService)
        endpoints.authenticationManager(authenticationManager);
        endpoints.setClientDetailsService(jdbcClientDetailsService());
        endpoints.tokenStore(new JdbcTokenStore(dataSource));
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        endpoints.approvalStore(jdbcApprovalStore());
        //token增强器,多加点信息在里面
        endpoints.tokenEnhancer(tokenEnhancerChain());
        endpoints.authorizationCodeServices(jdbcAuthorizationCodeServices());
    }

@Bean
    public ClientDetailsService jdbcClientDetailsService() {
        //从数据库中读取client_id,client_secret
        return new JdbcClientDetailsService(dataSource);
    }

principal 是通过认证了的数据库用户,即spring security的userDetailService中的loadUserDetail方法。

clientDetail就是数据库中oauth_user_dtails表中配置的,里面拿到是access_token有效期,refresh_token有效期,scope、resourceIds等信息,redirectUri必须得配置有,不然会报错,源码有抛异常。

clientDetail类:

/**
 * Client details for OAuth 2
 * 
 * @author Ryan Heaton
 */
public interface ClientDetails extends Serializable {

    /**
     * The client id.
     * 
     * @return The client id.
     */
    String getClientId();

    /**
     * The resources that this client can access. Can be ignored by callers if empty.
     * 
     * @return The resources of this client.
     */
    Set<String> getResourceIds();

    /**
     * Whether a secret is required to authenticate this client.
     * 
     * @return Whether a secret is required to authenticate this client.
     */
    boolean isSecretRequired();

    /**
     * The client secret. Ignored if the {@link #isSecretRequired() secret isn't required}.
     * 
     * @return The client secret.
     */
    String getClientSecret();

    /**
     * Whether this client is limited to a specific scope. If false, the scope of the authentication request will be
     * ignored.
     * 
     * @return Whether this client is limited to a specific scope.
     */
    boolean isScoped();

    /**
     * The scope of this client. Empty if the client isn't scoped.
     * 
     * @return The scope of this client.
     */
    Set<String> getScope();

    /**
     * The grant types for which this client is authorized.
     * 
     * @return The grant types for which this client is authorized.
     */
    Set<String> getAuthorizedGrantTypes();

    /**
     * The pre-defined redirect URI for this client to use during the "authorization_code" access grant. See OAuth spec,
     * section 4.1.1.
     * 
     * @return The pre-defined redirect URI for this client.
     */
    Set<String> getRegisteredRedirectUri();

    /**
     * Returns the authorities that are granted to the OAuth client. Cannot return <code>null</code>.
     * Note that these are NOT the authorities that are granted to the user with an authorized access token.
     * Instead, these authorities are inherent to the client itself.
     * 
     * @return the authorities (never <code>null</code>)
     */
    Collection<GrantedAuthority> getAuthorities();

    /**
     * The access token validity period for this client. Null if not set explicitly (implementations might use that fact
     * to provide a default value for instance).
     * 
     * @return the access token validity period
     */
    Integer getAccessTokenValiditySeconds();

    /**
     * The refresh token validity period for this client. Null for default value set by token service, and 
     * zero or negative for non-expiring tokens.
     * 
     * @return the refresh token validity period
     */
    Integer getRefreshTokenValiditySeconds();
    
    /**
     * Test whether client needs user approval for a particular scope.
     * 
     * @param scope the scope to consider
     * @return true if this client does not need user approval
     */
    boolean isAutoApprove(String scope);

    /**
     * Additional information for this client, not needed by the vanilla OAuth protocol but might be useful, for example,
     * for storing descriptive information.
     * 
     * @return a map of additional information
     */
    Map<String, Object> getAdditionalInformation();

}
//取得数据sql
private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
            + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
            + "refresh_token_validity, additional_information, autoapprove";

    private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE;

之后就是验证redirectUri 是否为空,scope是否和传过来的对得上,否的话都会抛异常!


approve是否自动略过自带的批准页面,默认否,即认证成功后,会跳到一个页面,选择approve或者deny,approve后才进入redirectUri的地址。这里如果自动,这根据oauth2请求模式分别操作。

授权码的话就成功跳转到redirectUri,并附带上code,如果前面有传state,还带上state,返回的queryParam就这两个可见源码。

org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint 类中的

private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {

        if (authorizationCode == null) {
            throw new IllegalStateException("No authorization code found in the current request scope.");
        }

        Map<String, String> query = new LinkedHashMap<String, String>();
        query.put("code", authorizationCode);

        String state = authorizationRequest.getState();
        if (state != null) {
            query.put("state", state);
        }

        return append(authorizationRequest.getRedirectUri(), query, false);
    }
上一篇 下一篇

猜你喜欢

热点阅读