spring:自定义授权端点(OAuth2)

spring:自定义授权端点(OAuth2)

问题描述:

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

I am trying to customize the code of the spring oauth authorization server. for now I have just copied the framework authorizationEndpoint code and placed it in another class. I just changed the address mapping to /custom/oauth/authorize. I have also added @Controller before the class declaration otherwise this code will not be used at all:

@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:

I have also instructed the framework to change the mappring from /oauth/authorize to /custom/oauth/authorize:

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

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

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

but when I run the code I encounter the following error:

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 并注入它?

the parent class of AuthorizationEndpoint (AbstractEndpoint) declares tokenGranter but it is not instantiated. there is no @autowired for this and other attributes of this class. who does genereate and inject these variable into this class? how can I get hold of tokenGranter obj and inject it?

/*
 * 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;
    }

}

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

I am answering my own question. I took a good look at the framework code and I found out that AuthorizationServerEndpointsConfiguration class creates an object of type AuthorizationEndpoint and populates it's attributes and then return this object as a bean. I managed to solve above mentioned problem with TokenGranter by creating a bean of my new AuthorizationEndpointCustom the same way AuthorizationServerEndpointsConfiguration does. this is the code to do so:

@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 的情况导致了另一个问题:如何替换框架bean

but this did not result in what I hoped to. the new bean does not replace the bean from framework code. this situation with overriding beans led to another question: how replace framework beans

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

buttom line, this is not the way to override the framework endpoints. you can simply create a controller with mappings for these endpoints (e.g /oauth/authorize or /oauth/token). automatically these mappings will get precedence over framework endpoints. for more info refer to spring doc