|
@ -0,0 +1,699 @@
|
|
|
|
package com.yihu.jw.security.oauth2.provider.endpoint;
|
|
|
|
|
|
|
|
import com.yihu.jw.security.core.userdetails.jdbc.WlyyUserDetailsService;
|
|
|
|
import com.yihu.jw.security.oauth2.provider.client.WlyyJdbcClientRedirectUriService;
|
|
|
|
import com.yihu.utils.network.IPInfoUtils;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
|
import org.springframework.security.access.AccessDeniedException;
|
|
|
|
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
|
|
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
|
import org.springframework.security.core.AuthenticationException;
|
|
|
|
import org.springframework.security.core.context.SecurityContext;
|
|
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
|
|
|
import org.springframework.security.core.userdetails.UserDetails;
|
|
|
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
|
|
|
import org.springframework.security.oauth2.common.exceptions.*;
|
|
|
|
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
|
|
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
|
|
|
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
|
|
|
import org.springframework.security.oauth2.provider.*;
|
|
|
|
import org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler;
|
|
|
|
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
|
|
|
|
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
|
|
|
|
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
|
|
|
|
import org.springframework.security.oauth2.provider.endpoint.*;
|
|
|
|
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
|
|
|
|
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
|
|
|
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
|
|
|
import org.springframework.stereotype.Controller;
|
|
|
|
import org.springframework.util.StringUtils;
|
|
|
|
import org.springframework.web.HttpSessionRequiredException;
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
|
|
|
|
import org.springframework.web.bind.support.SessionAttributeStore;
|
|
|
|
import org.springframework.web.bind.support.SessionStatus;
|
|
|
|
import org.springframework.web.context.request.RequestContextHolder;
|
|
|
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
|
|
import org.springframework.web.context.request.ServletWebRequest;
|
|
|
|
import org.springframework.web.servlet.ModelAndView;
|
|
|
|
import org.springframework.web.servlet.View;
|
|
|
|
import org.springframework.web.servlet.view.RedirectView;
|
|
|
|
import org.springframework.web.util.UriComponents;
|
|
|
|
import org.springframework.web.util.UriComponentsBuilder;
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.security.Principal;
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* authorization_code & implicit & sso Endpoint
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* Implementation of the Authorization Endpoint from the OAuth2 specification. Accepts authorization requests, and
|
|
|
|
* handles user approval if the grant type is authorization code. The tokens themselves are obtained from the
|
|
|
|
* {@link WlyyTokenEndpoint Token Endpoint}, except in the implicit grant type (where they come from the Authorization
|
|
|
|
* Endpoint via <code>response_type=token</code>.
|
|
|
|
* {@link WlyyLoginEndpoint Login Endpoint}, in order to verify the token to obtain user information.
|
|
|
|
*
|
|
|
|
* </p>
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* This endpoint should be secured so that it is only accessible to fully authenticated users (as a minimum requirement)
|
|
|
|
* since it represents a request from a valid user to act on his or her behalf.
|
|
|
|
* </p>
|
|
|
|
*
|
|
|
|
* @author Progr1mmer
|
|
|
|
* @created on 2018/8/29.
|
|
|
|
*/
|
|
|
|
@Controller
|
|
|
|
@SessionAttributes("authorizationRequest")
|
|
|
|
public class WlyyAuthorizationEndpoint extends AbstractEndpoint {
|
|
|
|
|
|
|
|
private final Logger LOG = LoggerFactory.getLogger(getClass());
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
private TokenStore tokenStore;
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
private WlyyUserDetailsService userDetailsService;
|
|
|
|
@Autowired
|
|
|
|
private AuthorizationServerEndpointsConfiguration authorizationServerEndpointsConfiguration;
|
|
|
|
@Autowired
|
|
|
|
private WlyyJdbcClientRedirectUriService wlyyJdbcClientRedirectUriService;
|
|
|
|
|
|
|
|
@PostConstruct
|
|
|
|
private void init() {
|
|
|
|
AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer = authorizationServerEndpointsConfiguration.getEndpointsConfigurer();
|
|
|
|
super.setTokenGranter(authorizationServerEndpointsConfigurer.getTokenGranter());
|
|
|
|
super.setClientDetailsService(authorizationServerEndpointsConfigurer.getClientDetailsService());
|
|
|
|
this.tokenStore = authorizationServerEndpointsConfigurer.getTokenStore();
|
|
|
|
this.authorizationCodeServices = authorizationServerEndpointsConfigurer.getAuthorizationCodeServices();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
|
|
|
|
this.sessionAttributeStore = sessionAttributeStore;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setErrorPage(String errorPage) {
|
|
|
|
this.errorPage = errorPage;
|
|
|
|
}
|
|
|
|
|
|
|
|
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.GET)
|
|
|
|
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
|
|
|
|
SessionStatus sessionStatus, Principal principal) {
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
/* default approve */
|
|
|
|
authorizationRequest.setApproved(true);
|
|
|
|
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()) {
|
|
|
|
if (parameters.containsKey("ak")) {
|
|
|
|
String ak = parameters.get("ak");
|
|
|
|
UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken("admin", "cdb7df66a1955b0ed7402e665ed0586a", new ArrayList<>());
|
|
|
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
|
|
|
securityContext.setAuthentication(userAuth);
|
|
|
|
SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
|
|
|
|
securityContextHolderStrategy.setContext(securityContext);
|
|
|
|
principal = userAuth;
|
|
|
|
} else {
|
|
|
|
throw new InsufficientAuthenticationException("ak must be provided.");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*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 validUrl = wlyyJdbcClientRedirectUriService.loadValidUri(authorizationRequest.getClientId(), redirectUriParameter);
|
|
|
|
String resolvedRedirect = redirectResolver.resolveRedirect(validUrl, client);
|
|
|
|
if (!StringUtils.hasText(resolvedRedirect)) {
|
|
|
|
throw new RedirectMismatchException(
|
|
|
|
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
|
|
|
|
}
|
|
|
|
|
|
|
|
// (customize) If redirectUriParameter is empty
|
|
|
|
// load internal and external network ip information to redirect
|
|
|
|
String accessUri = wlyyJdbcClientRedirectUriService.loadAccessUri(authorizationRequest.getClientId(), redirectUriParameter);
|
|
|
|
authorizationRequest.setRedirectUri(accessUri);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
|
|
|
|
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
|
|
|
|
SessionStatus sessionStatus, Principal principal) {
|
|
|
|
|
|
|
|
if (!(principal instanceof Authentication)) {
|
|
|
|
sessionStatus.setComplete();
|
|
|
|
throw new InsufficientAuthenticationException(
|
|
|
|
"User must be authenticated with Spring Security before authorizing an access token.");
|
|
|
|
}
|
|
|
|
|
|
|
|
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
|
|
|
|
|
|
|
|
if (authorizationRequest == null) {
|
|
|
|
sessionStatus.setComplete();
|
|
|
|
throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
Set<String> responseTypes = authorizationRequest.getResponseTypes();
|
|
|
|
|
|
|
|
authorizationRequest.setApprovalParameters(approvalParameters);
|
|
|
|
authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
|
|
|
|
(Authentication) principal);
|
|
|
|
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
|
|
|
|
authorizationRequest.setApproved(approved);
|
|
|
|
|
|
|
|
if (authorizationRequest.getRedirectUri() == null) {
|
|
|
|
sessionStatus.setComplete();
|
|
|
|
throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!authorizationRequest.isApproved()) {
|
|
|
|
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
|
|
|
|
new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
|
|
|
|
false, true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (responseTypes.contains("token")) {
|
|
|
|
return getImplicitGrantResponse(authorizationRequest).getView();
|
|
|
|
}
|
|
|
|
|
|
|
|
return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
sessionStatus.setComplete();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@RequestMapping(value = "/oauth/sso", method = RequestMethod.GET)
|
|
|
|
public ModelAndView sso(Map<String, Object> model, @RequestParam Map<String, String> parameters,
|
|
|
|
SessionStatus sessionStatus, Principal principal) {
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
/* default approve */
|
|
|
|
authorizationRequest.setApproved(true);
|
|
|
|
Set<String> responseTypes = authorizationRequest.getResponseTypes();
|
|
|
|
|
|
|
|
/* Does not accept code type verification */
|
|
|
|
if (!responseTypes.contains("token")) {
|
|
|
|
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
|
|
|
|
}
|
|
|
|
/*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 {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* load verification information by token
|
|
|
|
*/
|
|
|
|
if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
|
|
|
|
if (parameters.containsKey("access_token")) {
|
|
|
|
String token = parameters.get("access_token");
|
|
|
|
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
|
|
|
|
if (null == oAuth2AccessToken) {
|
|
|
|
throw new InvalidTokenException("Invalid access_token");
|
|
|
|
}
|
|
|
|
if (oAuth2AccessToken.isExpired()) {
|
|
|
|
throw new InvalidTokenException("Expired accessToken");
|
|
|
|
}
|
|
|
|
OAuth2Authentication authentication = tokenStore.readAuthentication(token);
|
|
|
|
if (authentication != null) {
|
|
|
|
String userName = authentication.getName();
|
|
|
|
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
|
|
|
|
UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
|
|
|
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
|
|
|
securityContext.setAuthentication(userAuth);
|
|
|
|
SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
|
|
|
|
securityContextHolderStrategy.setContext(securityContext);
|
|
|
|
principal = userAuth;
|
|
|
|
} else {
|
|
|
|
throw new InvalidTokenException("Cant not load authentication");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new InvalidRequestException("access_token must be provided.");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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 validUrl = wlyyJdbcClientRedirectUriService.loadValidUri(authorizationRequest.getClientId(), redirectUriParameter);
|
|
|
|
String resolvedRedirect = redirectResolver.resolveRedirect(validUrl, client);
|
|
|
|
if (!StringUtils.hasText(resolvedRedirect)) {
|
|
|
|
throw new RedirectMismatchException(
|
|
|
|
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
|
|
|
|
}
|
|
|
|
|
|
|
|
// (customize) If redirectUriParameter is empty
|
|
|
|
// load internal and external network ip information to redirect
|
|
|
|
String accessUri = wlyyJdbcClientRedirectUriService.loadAccessUri(authorizationRequest.getClientId(), redirectUriParameter);
|
|
|
|
authorizationRequest.setRedirectUri(accessUri);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need explicit approval from the user.
|
|
|
|
private ModelAndView getUserApprovalPageResponse(Map<String, Object> model,
|
|
|
|
AuthorizationRequest authorizationRequest, Authentication principal) {
|
|
|
|
LOG.debug("Loading user approval page: " + userApprovalPage);
|
|
|
|
model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
|
|
|
|
return new ModelAndView(userApprovalPage, model);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can grant a token and return it with implicit approval.
|
|
|
|
private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
|
|
|
|
try {
|
|
|
|
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
|
|
|
|
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
|
|
|
|
OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
|
|
|
|
if (accessToken == null) {
|
|
|
|
throw new UnsupportedResponseTypeException("Unsupported response type: token");
|
|
|
|
}
|
|
|
|
return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
|
|
|
|
false));
|
|
|
|
}
|
|
|
|
catch (OAuth2Exception e) {
|
|
|
|
return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
|
|
|
|
true, false));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {
|
|
|
|
try {
|
|
|
|
return new RedirectView(getSuccessfulRedirect(authorizationRequest,
|
|
|
|
generateCode(authorizationRequest, authUser)), false, true, false);
|
|
|
|
}
|
|
|
|
catch (OAuth2Exception e) {
|
|
|
|
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {
|
|
|
|
|
|
|
|
Map<String, Object> vars = new LinkedHashMap<String, Object>();
|
|
|
|
Map<String, String> keys = new HashMap<String, String>();
|
|
|
|
|
|
|
|
if (accessToken == null) {
|
|
|
|
throw new InvalidRequestException("An implicit grant could not be made");
|
|
|
|
}
|
|
|
|
|
|
|
|
vars.put("accessToken", accessToken.getValue());
|
|
|
|
vars.put("tokenType", accessToken.getTokenType());
|
|
|
|
String state = authorizationRequest.getState();
|
|
|
|
|
|
|
|
if (state != null) {
|
|
|
|
vars.put("state", state);
|
|
|
|
}
|
|
|
|
Date expiration = accessToken.getExpiration();
|
|
|
|
if (expiration != null) {
|
|
|
|
long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
|
|
|
|
vars.put("expiresIn", expires_in);
|
|
|
|
}
|
|
|
|
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
|
|
|
|
if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
|
|
|
|
vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
|
|
|
|
}
|
|
|
|
Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
|
|
|
|
for (String key : additionalInformation.keySet()) {
|
|
|
|
Object value = additionalInformation.get(key);
|
|
|
|
if (value != null) {
|
|
|
|
keys.put("extra_" + key, key);
|
|
|
|
vars.put("extra_" + key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Do not include the refresh token (even if there is one)
|
|
|
|
return append(authorizationRequest.getRedirectUri(), vars, keys, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
|
|
|
|
throws AuthenticationException {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
|
|
|
|
|
|
|
|
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
|
|
|
|
String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
|
|
|
|
|
|
|
|
return code;
|
|
|
|
|
|
|
|
}
|
|
|
|
catch (OAuth2Exception e) {
|
|
|
|
|
|
|
|
if (authorizationRequest.getState() != null) {
|
|
|
|
e.addAdditionalInformation("state", authorizationRequest.getState());
|
|
|
|
}
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure,
|
|
|
|
boolean fragment) {
|
|
|
|
|
|
|
|
if (authorizationRequest == null || authorizationRequest.getRedirectUri() == null) {
|
|
|
|
// we have no redirect for the user. very sad.
|
|
|
|
throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, String> query = new LinkedHashMap<String, String>();
|
|
|
|
|
|
|
|
query.put("error", failure.getOAuth2ErrorCode());
|
|
|
|
query.put("error_description", failure.getMessage());
|
|
|
|
|
|
|
|
if (authorizationRequest.getState() != null) {
|
|
|
|
query.put("state", authorizationRequest.getState());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (failure.getAdditionalInformation() != null) {
|
|
|
|
for (Map.Entry<String, String> additionalInfo : failure.getAdditionalInformation().entrySet()) {
|
|
|
|
query.put(additionalInfo.getKey(), additionalInfo.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(authorizationRequest.getRedirectUri(), query, fragment);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private String append(String base, Map<String, ?> query, boolean fragment) {
|
|
|
|
return append(base, query, null, fragment);
|
|
|
|
}
|
|
|
|
|
|
|
|
private String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
|
|
|
|
|
|
|
|
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
|
|
|
|
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
|
|
|
|
URI redirectUri;
|
|
|
|
try {
|
|
|
|
// assume it's encoded to start with (if it came in over the wire)
|
|
|
|
redirectUri = builder.build(true).toUri();
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
// ... but allow client registrations to contain hard-coded non-encoded values
|
|
|
|
redirectUri = builder.build().toUri();
|
|
|
|
builder = UriComponentsBuilder.fromUri(redirectUri);
|
|
|
|
}
|
|
|
|
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
|
|
|
|
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
|
|
|
|
|
|
|
|
if (fragment) {
|
|
|
|
StringBuilder values = new StringBuilder();
|
|
|
|
if (redirectUri.getFragment() != null) {
|
|
|
|
String append = redirectUri.getFragment();
|
|
|
|
values.append(append);
|
|
|
|
}
|
|
|
|
for (String key : query.keySet()) {
|
|
|
|
if (values.length() > 0) {
|
|
|
|
values.append("&");
|
|
|
|
}
|
|
|
|
String name = key;
|
|
|
|
if (keys != null && keys.containsKey(key)) {
|
|
|
|
name = keys.get(key);
|
|
|
|
}
|
|
|
|
values.append(name + "={" + key + "}");
|
|
|
|
}
|
|
|
|
if (values.length() > 0) {
|
|
|
|
template.fragment(values.toString());
|
|
|
|
}
|
|
|
|
UriComponents encoded = template.build().expand(query).encode();
|
|
|
|
builder.fragment(encoded.getFragment());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (String key : query.keySet()) {
|
|
|
|
String name = key;
|
|
|
|
if (keys != null && keys.containsKey(key)) {
|
|
|
|
name = keys.get(key);
|
|
|
|
}
|
|
|
|
template.queryParam(name, "{" + key + "}");
|
|
|
|
}
|
|
|
|
template.fragment(redirectUri.getFragment());
|
|
|
|
UriComponents encoded = template.build().expand(query).encode();
|
|
|
|
builder.query(encoded.getQuery());
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.build().toUriString();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setUserApprovalPage(String userApprovalPage) {
|
|
|
|
this.userApprovalPage = userApprovalPage;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setAuthorizationCodeServices(AuthorizationCodeServices authorizationCodeServices) {
|
|
|
|
this.authorizationCodeServices = authorizationCodeServices;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRedirectResolver(RedirectResolver redirectResolver) {
|
|
|
|
this.redirectResolver = redirectResolver;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setUserApprovalHandler(UserApprovalHandler userApprovalHandler) {
|
|
|
|
this.userApprovalHandler = userApprovalHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setOAuth2RequestValidator(OAuth2RequestValidator oauth2RequestValidator) {
|
|
|
|
this.oauth2RequestValidator = oauth2RequestValidator;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation")
|
|
|
|
public void setImplicitGrantService(
|
|
|
|
org.springframework.security.oauth2.provider.implicit.ImplicitGrantService implicitGrantService) {
|
|
|
|
}
|
|
|
|
|
|
|
|
@ExceptionHandler(Exception.class)
|
|
|
|
public ModelAndView handleGlobalException(Exception e) throws Exception {
|
|
|
|
LOG.info(e.getMessage(), e);
|
|
|
|
return new ModelAndView(errorPage, Collections.singletonMap("error", new OAuth2Exception(e.getMessage(), e)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*@ExceptionHandler(ClientRegistrationException.class)
|
|
|
|
public ModelAndView handleClientRegistrationException(Exception e, ServletWebRequest webRequest) throws Exception {
|
|
|
|
LOG.info("Handling ClientRegistrationException error: " + e.getMessage());
|
|
|
|
return handleException(new BadClientCredentialsException(), webRequest);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ExceptionHandler(OAuth2Exception.class)
|
|
|
|
public ModelAndView handleOAuth2Exception(OAuth2Exception e, ServletWebRequest webRequest) throws Exception {
|
|
|
|
LOG.info("Handling OAuth2 error: " + e.getSummary());
|
|
|
|
return handleException(e, webRequest);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ExceptionHandler(HttpSessionRequiredException.class)
|
|
|
|
public ModelAndView handleHttpSessionRequiredException(HttpSessionRequiredException e, ServletWebRequest webRequest)
|
|
|
|
throws Exception {
|
|
|
|
LOG.info("Handling Session required error: " + e.getMessage());
|
|
|
|
return handleException(new AccessDeniedException("Could not obtain authorization request from session", e),
|
|
|
|
webRequest);
|
|
|
|
}*/
|
|
|
|
|
|
|
|
private ModelAndView handleException(Exception e, ServletWebRequest webRequest) throws Exception {
|
|
|
|
|
|
|
|
ResponseEntity<OAuth2Exception> translate = getExceptionTranslator().translate(e);
|
|
|
|
webRequest.getResponse().setStatus(translate.getStatusCode().value());
|
|
|
|
|
|
|
|
if (e instanceof ClientAuthenticationException || e instanceof RedirectMismatchException) {
|
|
|
|
return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody()));
|
|
|
|
}
|
|
|
|
|
|
|
|
AuthorizationRequest authorizationRequest = null;
|
|
|
|
try {
|
|
|
|
authorizationRequest = getAuthorizationRequestForError(webRequest);
|
|
|
|
String requestedRedirectParam = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
|
|
|
|
String requestedRedirect = redirectResolver.resolveRedirect(requestedRedirectParam,
|
|
|
|
getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()));
|
|
|
|
authorizationRequest.setRedirectUri(requestedRedirect);
|
|
|
|
String redirect = getUnsuccessfulRedirect(authorizationRequest, translate.getBody(), authorizationRequest
|
|
|
|
.getResponseTypes().contains("token"));
|
|
|
|
return new ModelAndView(new RedirectView(redirect, false, true, false));
|
|
|
|
}
|
|
|
|
catch (OAuth2Exception ex) {
|
|
|
|
// If an AuthorizationRequest cannot be created from the incoming parameters it must be
|
|
|
|
// an error. OAuth2Exception can be handled this way. Other exceptions will generate a standard 500
|
|
|
|
// response.
|
|
|
|
return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody()));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private AuthorizationRequest getAuthorizationRequestForError(ServletWebRequest webRequest) {
|
|
|
|
|
|
|
|
// If it's already there then we are in the approveOrDeny phase and we can use the saved request
|
|
|
|
AuthorizationRequest authorizationRequest = (AuthorizationRequest) sessionAttributeStore.retrieveAttribute(
|
|
|
|
webRequest, "authorizationRequest");
|
|
|
|
if (authorizationRequest != null) {
|
|
|
|
return authorizationRequest;
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, String> parameters = new HashMap<String, String>();
|
|
|
|
Map<String, String[]> map = webRequest.getParameterMap();
|
|
|
|
for (String key : map.keySet()) {
|
|
|
|
String[] values = map.get(key);
|
|
|
|
if (values != null && values.length > 0) {
|
|
|
|
parameters.put(key, values[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return getOAuth2RequestFactory().createAuthorizationRequest(parameters);
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
return getDefaultOAuth2RequestFactory().createAuthorizationRequest(parameters);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|