hi dave,
i grabbed a recent M5+ version from github maybe a week ago, and tried out some of these ideas.
looks like the latest code base is being a good citizen w.r.t. not creating new tokens when there is an existing one, so nice work on that!
i have been working on attempting to hook as discussed above such that the client-user will only get prompted once for consent, and then subsequently will get whisked through without a consent prompt as long as there is an appropriate existing token.
i'm attempting to do this in a provider based on sparklr2 using the following altered version of the provided AccessConfirmationController:
Code:
package org.springframework.security.oauth.examples.sparklr.mvc;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
/**
* Controller for retrieving the model for and displaying the confirmation page for access to a protected resource.
*
* @author Ryan Heaton
*/
@Controller
@SessionAttributes(types = AuthorizationRequest.class)
public class AccessConfirmationController
{
private static final Logger log = LoggerFactory.getLogger(AccessConfirmationController.class);
private ClientDetailsService clientDetailsService;
private TokenStore tokenStore;
@RequestMapping("/oauth/confirm_access")
public String getAccessConfirmation(@ModelAttribute AuthorizationRequest authorizationRequest, Model model, Principal principal)
throws Exception
{
// if there is already a token granted for this client/user/resource/scope quadruple, then 'short-circuit' confirmation page.
//
// current strategy for determining this is to use implementation of TokenStore.getAccessToken(OAuth2Authentication) which
// uses DefaultAuthenticationKeyGenerator which in turn accounts for the client/user/resource/scope quadruple.
//
// current 'short-circuit' strategy is to forward to a page (based on the original access_confirmation.jsp)
// which does a javascript based 'auto-post' to simulate user accepting on consent page...
// at this point, authorizationRequest doesn't have resource-ids because they aren't passed on the request,
// but rather stashed with the client info, so we need to dig up ClientDetails, grab resource-ids and create
// a new AuthorizationRequest based on the original but augmented with resource-ids.
//
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
authorizationRequest = new AuthorizationRequest(clientDetails.getClientId(), authorizationRequest.getScope(),
clientDetails.getAuthorities(), clientDetails.getResourceIds());
UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(principal, null, null);
OAuth2Authentication oa2Authentication = new OAuth2Authentication(authorizationRequest, userAuthentication);
OAuth2AccessToken oa2AccessToken = tokenStore.getAccessToken(oa2Authentication);
String viewName = null;
if (oa2AccessToken != null)
{
// already issued a token for this client/user/resource/scope quadruple, so no need to prompt for access again,
// so initiate short-circuit strategy
//
viewName = "forward:/auto-confirm";
}
else
{
model.addAttribute("auth_request", authorizationRequest);
model.addAttribute("client", clientDetails);
viewName = "access_confirmation";
}
return viewName;
}
@Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService)
{
this.clientDetailsService = clientDetailsService;
}
public void setTokenStore(TokenStore tokenStore)
{
this.tokenStore = tokenStore;
}
}
i originally tried to avoid the 'auto-submit-form' page by passing control directly to AuthorizationEndpoint.approveOrDeny(), but ran into issues downstream in TokenEndpoint when AuthorizationCodeTokenGranter tossed a RedirectMismatchException because redirect-url wasn't included in the parameters for some reason with this strategy.
at that point, i was too beat down to investigate further, so i fell back to the clunky (but tried-and-true) 'auto-submit-form' method:
Code:
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).ready(function() {
$("#confirmationForm").submit();
});
</script>
</head>
<body>
<form id="confirmationForm" name="confirmationForm"
action="<%=request.getContextPath()%>/oauth/authorize" method="post">
<input name="user_oauth_approval" value="true" type="hidden" />
</form>
</body>
</html>
anyway, just wanted to kick my solution out for comments.
thanks,
tony.