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