I wanted to code a JSF application where the login page is a JSF page rather than a CAS page. I see this as a use case that cannot be overlooked in present day application development.
I understand that it is discouraged to present credentials to an application but an applications security model shouldn't have to be custom for every security solution plugged in. In my case I'm using spring security as the mechanism to glue the security stuff together. If I switch from plain security to CAS security it seems that all documentation points to me doing custom things for the login page such as iframe etc.. So here's what I've come up with. I'd appreciate feedback on this solution: In login-webflow.xml I made it so if ticketGrantingTicketId is passed as a parameter on the /cas/login URL then Service cookie can be issued directly using the ticketGrantingTicketId. In my case I get the ticketGrantingTicketId using the CAS restful api. <action-state id="initialFlowSetup"> <action bean="initialFlowSetupAction" /> <!-- garpinc replace --> <!-- <transition on="success" to="ticketGrantingTicketExistsCheck" /> --> <!-- garpinc with --> <transition on="success" to="ticketGrantingTicketIdExistsCheck" /> <!-- garpinc end replace --> </action-state> <!-- added by garpinc --> <decision-state id="ticketGrantingTicketIdExistsCheck"> <if test="${requestParameters.ticketGrantingTicketId == null}" then="ticketGrantingTicketExistsCheck" else="populateFromRequestParams" /> </decision-state> <action-state id="populateFromRequestParams"> <set attribute="ticketGrantingTicketId" value="requestParameters.ticketGrantingTicketId" scope="flow"/> <transition on="success" to="sendTicketGrantingTicket" /> </action-state> <!-- end of garpinc add --> Then I define a JSF controller. The pseudo steps are as follows 1) username/password provided to jsf page 2) adaptAuthenticationRequest is called to change this into an Authentication CasAuthenticationProvider can process 3) user is authenticated using provider 4) to participate in SSO getFinalOutcome is called which redirects to /cas/login with ticketGrantingTicketId and service which is page you want to go to on successful login. public class CasAuthenticationController extends AbstractAuthenticationController { private static final String CAS_TGR = "CAS_TGR"; private ServiceProperties serviceProperties; public void setServiceProperties(ServiceProperties serviceProperties) { this.serviceProperties = serviceProperties; } private String secureCasURL; public void setSecureCasURL(String secureCasURL) { this.secureCasURL = secureCasURL; } private String ticketURL; public void setTicketURL(String ticketURL) { this.ticketURL = ticketURL; } private boolean useCasSSO = true; public void setUseCasSSO(boolean useCasSSO) { this.useCasSSO = useCasSSO; } protected void updateFacesContext(Authentication aut) { } final protected String getFinalOutcome(String outcome) { if (outcome.equals("success") && useCasSSO) { FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext extCtx = ctx.getExternalContext(); HttpServletRequest request = (HttpServletRequest) extCtx.getRequest(); String ticket = (String) request.getAttribute(CAS_TGR); try { extCtx.redirect( secureCasURL + "/login?service=" + request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/view/secure/facelet/content/home.iface&ticketGrantingTicketId=" + ticket); // this is ignored because extCtx.redirect return "redirect"; } catch (IOException e) { throw new RuntimeException(e); } } else { return outcome; } } protected Authentication adaptAuthenticationRequest( UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws Exception { HttpClient client = new HttpClient(); PostMethod post = new PostMethod(ticketURL); NameValuePair[] usernamePasswordArray = { new NameValuePair("username",usernamePasswordAuthenticationToken.getPrincipal(). toString()), new NameValuePair("password",usernamePasswordAuthenticationToken.getCredentials( ).toString()) }; post.setRequestBody(usernamePasswordArray); int responseCode = client.executeMethod(post); if (responseCode != 201) { String body = post.getResponseBodyAsString(); Pattern pattern = Pattern.compile(".*<h3>(.*)</h3>.*", Pattern.DOTALL); Matcher matcher = pattern.matcher(body); if (matcher.matches()) { String casError = matcher.group(1); throw new AuthenticationServiceException(casError); } else { throw new RuntimeException("body does not contain an error:" + body); } } String ticketGrantingResource = post.getResponseHeader("Location").getValue(); Pattern pattern = Pattern.compile(".*\\/(.*)"); Matcher matcher = pattern.matcher(ticketGrantingResource); if (matcher.matches()) { String ticketGrantingResourceToStore = matcher.group(1); // Update cookie in browser for SSO HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance() .getExternalContext().getRequest(); request.setAttribute(CAS_TGR, ticketGrantingResourceToStore); } else { throw new RuntimeException("ticketGrantingResource not in right format:" + ticketGrantingResource); } post = new PostMethod(ticketGrantingResource); NameValuePair[] serviceArray = { new NameValuePair("service",serviceProperties.getService()), }; post.setRequestBody(serviceArray); responseCode = client.executeMethod(post); if (responseCode != 200) { throw new RuntimeException("response code was: " + responseCode); } String ticket = post.getResponseBodyAsString(); return new UsernamePasswordAuthenticationToken( CasProcessingFilter.CAS_STATEFUL_IDENTIFIER, ticket); } }
<<attachment: winmail.dat>>
_______________________________________________ Yale CAS mailing list cas@tp.its.yale.edu http://tp.its.yale.edu/mailman/listinfo/cas