Spring Security CAS: show client error on login.jsp - spring

I'm using Spring Security with CAS and have the following issue. When the authentication error is thrown from CAS Server (e.g. invalid username/password) it is shown well in form and is displayed correctly using tag:
<form:errors path="*" id="msg" cssClass="alert alert-danger" element="div"/>
But in cases when CAS Server returns success and the AuthenticationException is thrown on CAS Client none of the errors are displayed as basically CAS Client redirects back to http://localhost:8080/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fj_spring_cas_security_check
So I can't really display what went wrong on the client side. Is it somehow possible to display an error from client on the same JSP in case it throws AuthenticationException?

Not sure if that's the super clean and right way to do it, but the way I've managed to do that is using cookies.
All I had to do is to extend SimpleUrlAuthenticationFailureHandler, get there last authentication exception using request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)
and write my custom error code cookie. The code is in Scala, but is pretty straightforward:
class CookieAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
val clientErrorCookie = "clientError"
override def onAuthenticationFailure(request: HttpServletRequest, response: HttpServletResponse, exception: AuthenticationException): Unit = {
val authenticationException = SecurityUtility.getSessionAuthException(request).getOrElse(exception)
ClientErrors.values
.filter(clientError => clientError.exceptionClass.equals(authenticationException.getClass))
.foreach(clientError => response.addCookie(new Cookie(clientErrorCookie, clientError.errorCode)))
super.onAuthenticationFailure(request, response, authenticationException)
}
}
Then, on the CAS server side I've displayed error on JSP in the following way:
<c:set var="clientErrorCookie" value="clientError"/>
<c:if test="${cookie.containsKey(clientErrorCookie)}">
<div class="alert alert-danger">
<spring:message code="error.client.authentication.${cookie.get(clientErrorCookie).value}"
text="Client authentication error"/>
</div>
</c:if>
And after the page is loaded and error is shown, I've just removed that cookie in JS:
function deleteCookie(name) {
document.cookie = name + '=; Path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
$(document).ready(function(){
deleteCookie('${clientErrorCookie}');
}

Related

Create cross domain cookies for single sign on

I want to implement single sign on from my website(assume it as a.com) to vendor website(assume it as b.com)
My vendor is providing a service which will take user Id as input and returns token and cookies in response header. I need to call this service and redirect to vendor url with session token through post request and set cookies(which are received from service response).
In my code after making service call i am returning url and token to a jsp and cookies in httpservletresponse . Javascript in this jsp will autosubmit the form on page load to make post call. But when it is redirected, browser is not setting the b.com cookies in the request header.
Controller code :
#RequestMapping(value = "/sso", method = RequestMethod.GET)
public String ssoToVendor(final Model model, final HttpServletResponse response) {
/**
*Service call happens here and returns tok
*/
model.addAttribute("url","https:\\b.com");
model.addAttribute("tok",tok);
for (String cookie : cookies) {
response.addHeader("Set-Cookie", cookie);
}
return "dummyjsp"
}
JSP sample code :
<body>
<form id="redirect" action="${url}" name="redirect" method="POST">
<input type="hidden" name="tok" id="tok" value="${tok}"/>
</form>
</body>
<script type="text/javascript">
var redirect = document.getElementById("redirect");
redirect.submit();
</script>
I know that it is not possible to set cross domain cookies but some how there is another application which is implemented in c# is able to set those cookies.
Is there a way we can set b.com cookies in response header and that is created by browser and sent to b.com when redirected from a.com in java.

partial-response on session expire

We have a JSF 2.0, Primefaces 5.0, Spring Security 3.2.3.RELEASE application.
To handle session timeout, I am using primefaces idleMonitor and p:dialog & javascript to display a countdown popup and redirect them back to login page.
I have also implemented a custom CacheControlPhaseListener so that the pages are not cached. I set the no-cache in the response headers in the CacheControlPhaseListener.
<lifecycle><phase-listener id="nocache">com..filter.CacheControlPhaseListener</phase-listener></lifecycle>
I also have error handling configured in my web.xml:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/error.jsf</location></error-page>
I have also implemented a ViewExpiredHandler that extends ViewHandlerWrapper
#Override
public UIViewRoot restoreView(FacesContext ctx, String viewId)
{
UIViewRoot viewRoot = super.restoreView(ctx, viewId);
try
{
if (viewRoot == null)
{
viewRoot = super.createView(ctx, viewId);
ctx.setViewRoot(viewRoot);
}
}
catch (Exception e)
{
e.printStackTrace();
}
return viewRoot;
}
The problem I am still having is:
1. When the session expires on a idle page (E.g. Search page) and if some ajax action is triggered on a page, even though I logout, when I navigate back to the page (e.g. Login-> Home-> Search page). I see a partial-response xml error:
<partial-response><changes><update id="blGridId"><table id="blGridId" style="width:100%;">
<tbody>
<tr>
<td><div id="blTableId" class="ui-datatable ui-widget ui-datatable-scrollable ui-datatable-resizable"><div id="sublTableId_paginator_top" class="ui-paginator ui-paginator-top ui-widget-header ui-corner-top" role="navigation"><span class="ui-paginator-prev ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-prev">p</span></span><span class="ui-paginator-next ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-next">p</span></span></div><div class="ui-widget-header ui-datatable-scrollable-header"><div class="ui-datatable-scrollable-header-box"><table role="grid"><thead id="blTableId_head"><tr role="row"><th id="blTableId:j_idt101" class="ui-state-default ui-resizable-column" role="columnheader" style="width:34px; #width:37px;"><span class="ui-column-title"><span style="word-wrap: break-word;white-space: normal;">Client </span></span></th><th id="blTableId:j_idt104" class="ui-state-default
2. If I hit a browser refresh, it loads back the page and I can continue with the actions.
Please let me know what I need to do in addition to the above to resolve the partial-response error. Do I need to add a servlet filter to invalidate the session?
I would really appreciate any help and feedback on this as it is high priority.
I had got the same issue when session had been expired. I thought it was too late, but maybe would be helpful for others who has issues like me.
The root cause is Spring Security saves the last request before redirecting client to do the authentication. After then, Spring security would try to perform the request again when user visits the page of last request. Unfortunately, the request was ajax/partial and its view was expired -> partial xml content was returned.
Easy way to get rid of this issue is removing the saving behavior of Spring Security. SavedRequestAwareAuthenticationSuccessHandler class is used to handle these kind of behaviours. Configure as:
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:authenticationManager-ref="authenticationManager"
p:authenticationFailureHandler-ref="authenticationFailureHandler"
p:authenticationSuccessHandler-ref="authenticationSuccessHandler"
p:usernameParameter="username"
p:passwordParameter="password">
</bean>
...
<bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
p:defaultTargetUrl="/"
p:alwaysUseDefaultTargetUrl="true"/>
Hope it would help.

JSF AJAX and normal redirect to login and back to previous page

Current status :
If any session time out occurs, redirectToLogin function in FacesAjaxAwareUserFilter will be executed. From there I can redirect to any page I need. I get the URI in all cases.
It's all working fine and big thanks to BalusC. :)
Now the second part which makes the issue
Redirect to login and come back to the previous page.
For Eg :
Page 5----> Login------> Page 5
I have appended the redirect URI to the login URI and retrieved the values from the bean.
But the problem is that I have 2 pages before the user login. A login mode selection page (ie; google authentication or default login) and the page that reads the username and password.
How can I pass the redirect URI through both the pages.?
This is the code I have used to redirect at Ajax time out and in normal session time out.
Class FacesAjaxAwareUserFilter
if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
res.setContentType("text/xml");
res.setCharacterEncoding("UTF-8");
res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl()+"?redirectUrlPattern="+request.getRequestURI());
}
else {
if (request.getRequestedSessionId()!=null && (!SecurityUtils.getSubject().isAuthenticated() || !request.isRequestedSessionIdValid())) {
response.sendRedirect(getLoginUrl()+"?redirectUrlPattern="+request.getRequestURI());
return;
}
super.redirectToLogin(req, res);
}
Method used is FullAjaxExceptionHandlerFactory in Omniface.
I have used a method of appending the values to the URI, but it fails to identify whether the session is expired or a session not created (when a user logs in at first).
Problem Code
if (request.getRequestedSessionId()!=null && (!SecurityUtils.getSubject().isAuthenticated() || !request.isRequestedSessionIdValid()))
I am looking for a way to identify the expired session from new session created before login.
Wish to implement this in a Better Way.
A method which will not append present URI with the redirect URL is most welcomed.
I have founded out a solution but still i don't find it as the best solution. Any other answer for doing it is greatly appreciated.
Step 1
Configure the application to detect Ajax Timeout and normal timeout. This is how i did it..!!
Step 2
Finding the URI when session timeout occurs, using this.
FacesAjaxAwareUserFilter will grab the ServletRequest and ServletResponse for you.
form that you can convert that to HttpServletRequest and HttpServletResponse using this
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response= (HttpServletResponse) res;
The request.getRequestURI() will get you the redirect URI
String redirectURI=request.getRequestURI();
Now you want to save this to some storage so that you can use it in any place you want. The only way i found is to append it with URL using this.
res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + "/sessionexpired"+"?redirectUrlPattern="+request.getRequestURI());
or
response.sendRedirect("/sessionexpired"+"?redirectUrlPattern="+request.getRequestURI());
Now on the page load of my sessionExpiry page bean, i used this to grab the value passed through the URL
redirectUrl=FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("redirectUrlPattern");
Now using JavaScript i set that value of redirectUrl to localStorage. This is how you can save values to local storage.
This is my sessionexpiry.xhtml page.
<script type="text/javascript">
function setRedirectURIToLocalStorage(){
if(typeof(Storage)!=="undefined")
{
localStorage.redirectURI=document.getElementById("redirectUrl").value;
window.location.href="/login";
}
}
</script>
</h:head>
<h:body id="body" onload="setRedirectURIToLocalStorage()">
<f:view contentType="text/html">
<h:form prependId="false" >
<h:inputHidden id="redirectUrl" value="#{sessionExpBean.redirectUrl}" />
<h:outputText value="Session Expired... Redirecting...!!"></h:outputText>
</h:form>
</f:view>
</h:body>
The page will call setRedirectURIToLocalStorage() function onLoad
and then it set the values to the localStorage. Thus the redirect
value is available all across the browser. You can use that in any
page you need.!! All you need to do is to check in this variable
localStorage.redirectURI

How to handle authentication in Angular JS application

I am implementing an auth system in my angular js app.
What I am planning it like below:
Get user info(name and pass from login form)
Check whether user exists or not
if exists server respond with a session cookie and frontend will redirect to a certain page.
then user will do some task which will generate API request
API request should have cookie information that was sent on step 3
server check whether the cookie was generated or not and if cookie was found then respond with the API request results. And in my service I am doing something like
MyApp.service('myAuth', function($http, $q) {
this.authHeader = null;
this.checkAuth = function(){
//do api call and if success sets this.authHeader = response
}
this.isAuthenticaed = function(){
this.authHeader ? return this.authHeder : return false;
}
After submitting the login form I will call checkAuth and get my session cookie back from my server, how I can add the cookie information while doing the next REST call and also when user will navigate throughout the application after log in I do want to check each time isAuthenticaed true or false, in Angularjs when it will navigate to another page does it resets after setting it true from the first call? And is my approach 1-6 good or do you have any specific suggestions?
Btw I checked previous so entries but those are not what I want to know.
I am not sure about your backend, but this is how I would do it
Create a separate login page (dedicated url not angular sub view or
modal dialog).
If the user is not authenticated redirect to this login
page. This is done by server redirects. This page may or may not use
angular framework, as it just involves sending a user\password to
server.
Make a POST (not AJAX request) from the login page, and verify on server.
On the server set the auth cookie. (Different frameworks do it differently. ASP.Net sets form authentication cookie.)
Once the user is authenticated redirect user to the actual angular app and load all its components.
This saves any code require to manage authentication on client side in Angular. If the user lands on this page he is authenticated and has the cookie.
Also default browser behavior is to send all cookies associated with a domain with each request, so you don't have to worry if angular is sending some cookie or not.
I use the http-auth-interceptor. http://ngmodules.org/modules/http-auth-interceptor
In my backend (asp.net mvc) I build a simple Authentication Service and return an http error 401 if the user is not authenticated.
Then I handle the error with a login-view in the SPA site.
The ideas put forth by the previous answers will work, but I think they're overkill. You don't need anything this complex.
how I can add the cookie information while doing the next REST call
Turn on withCredentials by default inside $httpProvider like so:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]);
Then remove the wildcard (if you had one) from the CORS-related headers, and set allow-credentials, on the server side. In my case, using Python + Flask + Flask-Restful, it's super easy and looks like this:
import Flask
from flask_restful import Api
app = Flask(__name__)
api = Api(app)
api.decorators = [cors.crossdomain(origin='http://localhost:8100', credentials=True)]
Now cookies will be set and returned automatically and transparently by the browser. See these threads for more info:
$http response Set-Cookie not accessible
Angularjs $http does not seem to understand "Set-Cookie" in the response
when user will navigate throughout the application after log in I do want to check each time isAuthenticaed true or false
As suggested above, have the server return 401 if the auth session expires or is deleted, and use $httpInterceptor in Angular to catch this like so:
app.config(function($httpProvider) {
var interceptor =
function($q, $rootScope) {
return {
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status==401) {
// Modify this part to suit your needs.
// In my case I broadcast a message which is
// picked up elsewhere to show the login screen.
if (!rejection.config.url.endsWith('/login'))
{
$rootScope.$broadcast('auth:loginRequired');
}
}
return $q.reject(rejection)
}
}
};
$httpProvider.interceptors.push(interceptor);
});
(Disclosure: I'm one of the developers of UserApp)
You could use the third-party service UserApp for this, together with the AngularJS module.
Check out the getting started guide, or take the course on Codecademy. Here's some examples of how it works:
Login form with error handling:
<form ua-login ua-error="error-msg">
<input name="login" placeholder="Username"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Log in</button>
<p id="error-msg"></p>
</form>
User info is accessed using the user service: user.current.email
Or in the template: <span>{{ user.email }}</span>
Signup form with error handling:
<form ua-signup ua-error="error-msg">
<input name="first_name" placeholder="Your name"><br>
<input name="login" ua-is-email placeholder="Email"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Create account</button>
<p id="error-msg"></p>
</form>
ua-is-email means that the username is the same as the email.
How to specify which routes that should be public, and which route that is the login form:
$routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});
The .otherwise() route should be set to where you want your users to be redirected after login. Example:
$routeProvider.otherwise({redirectTo: '/home'});
Log out link:
<a href="#" ua-logout>Log Out</a>
Hide elements that should only be visible when logged in:
<div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>
And to authenticate to your back-end services, just use user.token() to get the session token and send it with the AJAX request. At the back-end, use the UserApp API to check if the token is valid or not.
If you need any help, just let me know :)

Servlet --x--> Ajax: Ajax code not receiving servlet response

I'm unable to figure out what is going on here in this ultra simple example.
Problem Summary: I have a simple servlet that appears to run just fine if I drive it manually... by issuing its URL from the browser. By 'just fine' I mean: I can see in the browser HTML page whatever I write in the servlet response.
However, if I issue the very same URL via Ajax code, the servlet processes the request fine and even 'appears' to be writing out the response fine ... but, just that I do not see any response on the Ajax client code side and thus neither in my browser HTML page.
Further, if I make my XHR request syncrhonous, the browser error console shows the following exception:
Error: uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIXMLHttpRequest.send]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: file:///home/sd/Desktop/test.html :: callServlet :: line 35" data: no]
Environment:
Browser: Firefox 3.5.3
Servlet container: Tomcat 6.0.20
OS: Linux / Fedora 11
Ajax code:
<!-- test.html -->
<html>
<head>
<script>
var req;
function $(id) {
return document.getElementById(id);
}
function servletCallback() {
var field = $("debugHtmlId");
field.innerHTML += "readyState='" + req.readyState + "'<br> ";
field.innerHTML += "status='" + req.status + "'<br> ";
field.innerHTML += "responseText='" + req.responseText + "' | <br> ";
}
req = new XMLHttpRequest();
req.onreadystatechange = servletCallback;
function callServlet() {
// With async mode off, I get the
// Exception listed above.
// req.open("GET", "http://localhost:8080/aaa/bbb?f=test", false);
req.open("GET", "http://localhost:8080/aaa/bbb?f=test", true);
req.send(null);
}
</script>
</head>
<body>
<input id="callserv" type="submit" value="Call Servlet" onclick="callServlet();" />
<span id="debugHtmlId"></div>
</body>
</html>
Servlet code:
// servlet code
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse rsp)
throws ServletException, IOException {
rsp.setContentType("text/html");
String val = req.getParameter("f");
if(val.equals("test")) {
// Increment value.
++_count;
// Return value.
PrintWriter out = rsp.getWriter();
out.printf("%d\n", _count);
out.close();
// This shows up fine in servlet log.
System.out.printf("%d\n", _count);
}
}
// This variable is incremented and returned on each call to doGet().
private int _count = 0;
}
EDIT:
Including the result: Here's what I see for example as the value of my innerHTML of my debugHtmlId element.
readyState='1'
readyState='1'
readyState='2'
status='0'
responseText='' |
readyState='4'
status='0'
responseText='' |
Strange behavior: Notice also that my readystatechange handler is getting re-entered! I mean, I was expecting to see readyState='...' status='...' responseText='...' triads for every state change...
The problem was:
I had loaded the above HTML in my browser not from the Tomcat/web server but from my local file system. I thought, for development I won't need to deploy the HTML to the server.
Since what I originally wanted works fine now, I'm not so worried now about the exception I was getting in the sync mode of ajax.
I was working through this same problem. It's definitely a cross domain issue and in my case the javascript file was the same server as the servlet and i was downloading and and calling the script from an html page that was not. Just like Harry I was loading the html locally and getting a 0 status.
The solution for me was to add
resp.addHeader("Access-Control-Allow-Origin", "*");
to the SERVLET code - this allows the javascript to call the servlet from outside the domain. It was killing me since i knew the servlet was getting hit, the server debugger was logging the GET - but the response was empty and the status was 0. all of the code on the question will work fine, if you add that header to the servlets response.
Try this: Don't call out.close(); on the PrintWriter. Instead, call out.flush();.
I look at the code and did two things to work on my CentOS 5 Linux dev box with Tomcat5:
Modified the code and rename my servlet to eServlet:
req.open("GET", "http://localhost:8080/aaa/bbb?f=test", true);
to
req.open("GET", "eServelet?f=test", true);
Modified WEB-INF/web.xml and added servlet mapping
<servlet>
<servlet-name>eServletApps</servlet-name>
<servlet-class>eServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>eServletApps</servlet-name>
<url-pattern>/eServlet</url-pattern>
</servlet-mapping>
Compiled with command:
# javac -classpath $CATALINA_HOME/common/lib/servlet-api.jar eServlet.java
Loaded it and it Works without any problems

Resources