【问题标题】:spring: customizing the authorizationEndpoint (OAuth2)spring:自定义授权端点(OAuth2)
【发布时间】:2020-02-19 13:48:31
【问题描述】:

我正在尝试自定义spring oauth授权服务器的代码。 现在我刚刚复制了框架授权端点代码并将其放在另一个类中。我只是将地址映射更改为/custom/oauth/authorize。我还在类声明之前添加了@Controller,否则根本不会使用此代码:

@Controller
//@Order(Ordered.HIGHEST_PRECEDENCE)
@SessionAttributes("authorizationRequest")

public class AuthorizationEndpointCustom extends AuthorizationEndpoint {


    @Autowired
    private AuthenticationManager authenticationManager;

    private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();

    private RedirectResolver redirectResolver = new DefaultRedirectResolver();

    private UserApprovalHandler userApprovalHandler = new DefaultUserApprovalHandler();

    private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

    private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();

    private String userApprovalPage = "forward:/oauth/confirm_access";

    private String errorPage = "forward:/oauth/error";

    private Object implicitLock = new Object();

    public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
        this.sessionAttributeStore = sessionAttributeStore;
    }

    public void setErrorPage(String errorPage) {
        this.errorPage = errorPage;
    }

    @RequestMapping(value = "/oauth/authorize")
    public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
            SessionStatus sessionStatus, Principal principal) {

        System.out.println("\n\ninside custom authorization endpoint");

        // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
        // query off of the authorization request instead of referring back to the parameters map. The contents of the
        // parameters map will be stored without change in the AuthorizationRequest object once it is created.
        AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

        Set<String> responseTypes = authorizationRequest.getResponseTypes();

        if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
            throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
        }

        if (authorizationRequest.getClientId() == null) {
            throw new InvalidClientException("A client id must be provided");
        }

        try {

            if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
                throw new InsufficientAuthenticationException(
                        "User must be authenticated with Spring Security before authorization can be completed.");
            }

            ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

            // The resolved redirect URI is either the redirect_uri from the parameters or the one from
            // clientDetails. Either way we need to store it on the AuthorizationRequest.
            String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
            String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
            if (!StringUtils.hasText(resolvedRedirect)) {
                throw new RedirectMismatchException(
                        "A redirectUri must be either supplied or preconfigured in the ClientDetails");
            }
            authorizationRequest.setRedirectUri(resolvedRedirect);

            // We intentionally only validate the parameters requested by the client (ignoring any data that may have
            // been added to the request by the manager).
            oauth2RequestValidator.validateScope(authorizationRequest, client);

            // Some systems may allow for approval decisions to be remembered or approved by default. Check for
            // such logic here, and set the approved flag on the authorization request accordingly.
            authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
                    (Authentication) principal);
            // TODO: is this call necessary?
            boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
            authorizationRequest.setApproved(approved);

            // Validation is all done, so we can check for auto approval...
            if (authorizationRequest.isApproved()) {
                if (responseTypes.contains("token")) {
                    return getImplicitGrantResponse(authorizationRequest);
                }
                if (responseTypes.contains("code")) {
                    return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
                            (Authentication) principal));
                }
            }

            // Place auth request into the model so that it is stored in the session
            // for approveOrDeny to use. That way we make sure that auth request comes from the session,
            // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
            model.put("authorizationRequest", authorizationRequest);

            return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

        }
        catch (RuntimeException e) {
            sessionStatus.setComplete();
            throw e;
        }

    }

    private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
            OAuth2Request storedOAuth2Request) {
        OAuth2AccessToken accessToken = null;
        // These 1 method calls have to be atomic, otherwise the ImplicitGrantService can have a race condition where
        // one thread removes the token request before another has a chance to redeem it.
        synchronized (this.implicitLock) {
            accessToken = getTokenGranter().grant("implicit",
                    new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
        }
        return accessToken;
    }


.
.
.

我还指示框架将映射从 /oauth/authorize 更改为 /custom/oauth/authorize:

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

    endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
            .accessTokenConverter(tokenEnhancer()).pathMapping("/oauth/authorize", "/custom/authorize/");
}

但是当我运行代码时遇到以下错误:

Description:

Field tokenGranter in com.example.demo.controller.AuthorizationEndpointCustom required a bean of type 'org.springframework.security.oauth2.provider.TokenGranter' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'org.springframework.security.oauth2.provider.TokenGranter' in your configuration.

AuthorizationEndpoint(AbstractEndpoint)的父类声明了tokenGranter,但没有实例化。这个类的这个属性和其他属性没有@autowired。谁生成这些变量并将其注入到这个类中? 如何获取 tokenGranter obj 并注入它?

/*
 * Copyright 2002-2011 the original author or authors.
 *
 * 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.util.Assert;

/**
 * @author Dave Syer
 * 
 */
public class AbstractEndpoint implements InitializingBean {

    protected final Log logger = LogFactory.getLog(getClass());

    private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator();

    private TokenGranter tokenGranter;

    private ClientDetailsService clientDetailsService;

    private OAuth2RequestFactory oAuth2RequestFactory;

    private OAuth2RequestFactory defaultOAuth2RequestFactory;

    public void afterPropertiesSet() throws Exception {
        Assert.state(tokenGranter != null, "TokenGranter must be provided");
        Assert.state(clientDetailsService != null, "ClientDetailsService must be provided");
        defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(getClientDetailsService());
        if (oAuth2RequestFactory == null) {
            oAuth2RequestFactory = defaultOAuth2RequestFactory;
        }
    }

    public void setProviderExceptionHandler(WebResponseExceptionTranslator providerExceptionHandler) {
        this.providerExceptionHandler = providerExceptionHandler;
    }

    public void setTokenGranter(TokenGranter tokenGranter) {
        this.tokenGranter = tokenGranter;
    }

    protected TokenGranter getTokenGranter() {
        return tokenGranter;
    }

    protected WebResponseExceptionTranslator getExceptionTranslator() {
        return providerExceptionHandler;
    }

    protected OAuth2RequestFactory getOAuth2RequestFactory() {
        return oAuth2RequestFactory;
    }

    protected OAuth2RequestFactory getDefaultOAuth2RequestFactory() {
        return defaultOAuth2RequestFactory;
    }

    public void setOAuth2RequestFactory(OAuth2RequestFactory oAuth2RequestFactory) {
        this.oAuth2RequestFactory = oAuth2RequestFactory;
    }

    protected ClientDetailsService getClientDetailsService() {
        return clientDetailsService;
    }

    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

}

【问题讨论】:

    标签: spring oauth-2.0


    【解决方案1】:

    我正在回答我自己的问题。 我仔细查看了框架代码,发现 AuthorizationServerEndpointsConfiguration 类创建了一个 AuthorizationEndpoint 类型的对象并填充它的属性,然后将该对象作为 bean 返回。 我设法通过与 AuthorizationServerEndpointsConfiguration 相同的方式创建我的新 AuthorizationEndpointCustom 的 bean,从而解决了 TokenGranter 的上述问题。这是这样做的代码:

    @Autowired
    private ClientDetailsService clientDetailsService;
    
    @Autowired
    AuthorizationServerEndpointsConfiguration asec;
    
    
    @Bean
    @Order(value = Ordered.HIGHEST_PRECEDENCE)
    @Primary
    public AuthorizationEndpoint authorizationEndpoint () throws Exception{
    
        AuthorizationEndpointCustom authorizationEndpoint = new AuthorizationEndpointCustom();
        FrameworkEndpointHandlerMapping mapping = asec.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
        authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
        authorizationEndpoint.setProviderExceptionHandler(asec.getEndpointsConfigurer().getExceptionTranslator());
        authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
        authorizationEndpoint.setTokenGranter(asec.getEndpointsConfigurer().getTokenGranter());
        authorizationEndpoint.setClientDetailsService(clientDetailsService);
        authorizationEndpoint.setAuthorizationCodeServices(asec.getEndpointsConfigurer().getAuthorizationCodeServices());
        authorizationEndpoint.setOAuth2RequestFactory(asec.getEndpointsConfigurer().getOAuth2RequestFactory());
        authorizationEndpoint.setOAuth2RequestValidator(asec.getEndpointsConfigurer().getOAuth2RequestValidator());
        authorizationEndpoint.setUserApprovalHandler(asec.getEndpointsConfigurer().getUserApprovalHandler());
    
        return authorizationEndpoint;
    }
    
    private String extractPath(FrameworkEndpointHandlerMapping mapping, String page) {
        String path = mapping.getPath(page);
        if (path.contains(":")) {
            return path;
        }
        return "forward:" + path;
    }
    

    但这并没有达到我的预期。新 bean 不会替换框架代码中的 bean。这种使用覆盖 bean 的情况导致了另一个问题: how replace framework beans

    最重要的是,这不是覆盖框架端点的方法。您可以简单地为这些端点创建一个带有映射的控制器(例如 /oauth/authorize 或 /oauth/token)。这些映射将自动优先于框架端点。更多信息请参考spring doc

    【讨论】:

      猜你喜欢
      • 2018-11-13
      • 2022-11-14
      • 2018-09-26
      • 2020-07-13
      • 2013-02-13
      • 2015-08-26
      • 1970-01-01
      • 2015-10-15
      • 2022-11-04
      相关资源
      最近更新 更多