美文网首页spring security 以及 oauth2
spring security oauth2授权码接口源码流程

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

作者: virtual灬zzZ | 来源:发表于2021-09-29 19:19 被阅读0次

    使用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);
        }
    

    相关文章

      网友评论

        本文标题:spring security oauth2授权码接口源码流程

        本文链接:https://www.haomeiwen.com/subject/cqbjnltx.html