-
Notifications
You must be signed in to change notification settings - Fork 6.1k
SEC-977: Add support for CAS gateway feature #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Hello Rob, |
@miremond - Sorry I haven't had a chance to look at this in detail yet. I will look it over sometime this week though. |
@miremond I apologize, but I won't be able to get to this today. It will be the first thing I work on for Monday. |
* @author Michael Remond | ||
* | ||
*/ | ||
public class InitiateCasGatewayAuthenticationException extends |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent with the TriggerCasGatewayAuthenticationFilter and the other AuthenticationException objects (which don't necessarily contain Authentication in them), this might be better named TriggerCasGatewayException
I was able to get to it this week after all (just needed a bit of weekend work) :) In general I think this pull request looks pretty good....only some minor renaming and extracting some of the logic into a RequestMatcher. For details, see the comments inline. Please feel free to respond to the comments if you disagree or have other ideas. |
Hello Rob, Thank you so much for spending time on this pull request ; you should not 2013/8/24 Rob Winch [email protected]
|
The new filter TriggerCasGatewayAuthenticationFilter has been added to call the CasAuthenticationEntryPoint when we want try a silent CAS authentication (typically on a public page). The trigger criteria is done with a requestMatcher instance. The method unsuccessfulAuthentication has been overridden in CasAuthenticationFilter in order to redirect to the saved url if there was no SSO session (no service ticket sent from CAS). To avoid infinite loop, we use the DefaultGatewayResolverImpl from Jasig Cas Client.
Some comments on my last commit:
|
Hello Rob, no news from you since my last submission. Can you check it? |
Hello Rob, still no news. don't forget me :-) |
@miremond Thanks for your patience. I probably should have been more explicit about this, but this feature won't be included until the next release. The reason is that we have already put out a RC (which was out prior to the PR modifications) which means we shouldn't be including any new large features (especially that integrate with existing functionality). In the meantime, I am busy finishing up with 3.2. I assure you that this will be the first thing I integrate with the next version of Spring Security. |
Thank you for your response Rob. No problem for the targeted version ; I just wanted to have your feedback on the last version of my work if it sounds good for you. Thank you again for your interest. |
Understood...right now I am trying to focus as much on the next RC so we can have it out for feedback. I do apologize for this sitting so long, but at the moment getting out the next RC is the highest priority. With limited resources that means this will sit until we get the RC out. Again thank you very much for your contribution and I will look at it as soon as the next RC is out (likely around a week). |
I tried your implementation, it works fine except one thing. There is no support for gatewaying requests to applications where user is already authenticated. So when user logs out from cas these applications stay authenticated with this user and doesn't recognise that user have already logged out. This could lead to potential security risk. |
Hello Infrag, i'm not sure I understand the security issue you're talking about. The gateway mechanism is just something to silently authenticate to one application if the user is already CAS authenticated. |
public final boolean matches(HttpServletRequest request) { | ||
|
||
// Test if we are already authenticated | ||
if (isAuthenticated(request)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be omited for Single Logout to work
Hi, sorry for late reply. I have implemented Single Logout mechanism in my aplications. So if I log out from CAS it logs out also from all the other applications (I'm not sure how it exactly works in background). But the same thing doesn't work with gateway mode. See scenario below:
This occurs only in combination with gateway mode. If i don't use GW mode, and APP_2 doesn't have public section (whole APP_2 requires authentication), Single Logout works and I'm logged out from APP_2 in step 5 of scenario above. My current solution for this behavior is to omit lines
This way loged user is checked on CAS server and Single Logout works. It's not very scalable. Better understanding of Single Logout in CAS could help to find better solution. |
Hi and also sorry for my delay this time ;-) I still think there is no problem with my gateway implementation ; you can check that my implementation is similar to the standard Jasig CAS client. It seems you say when one authenticates in an app with the gateway mode (APP2 in your example), the CAS server won't handle this app in the single logout process. But as far as I know, as soon the CAS server generates a service ticket for one service (gateway mode or not), the service is saved and will be notified during logout to invalidate the local session (the mechanism is: the CAS server sends a logout request to each application that requested an authentication (server-to-server call)). |
@miremond Sorry this has taken so long. I'm looking into merging this now. I'm curious if it was intentional that every anonymous request hits the CAS server? This seems like it could place a large load on the CAS Server if there are many anonymous users. I realize that the request is a very quick one if no TGT is found, but it seems like a very large number of requests will be hitting the CAS Server. Perhaps the default should be trying once per session? Any thoughts would be appreciated. |
@rwinch Sorry for my delay, I was on Holidays :-) I will discuss the question on the CAS mailing list and come back with answers. |
Any news regarding this request? Really interesting topic. |
Hello, |
Thanks for your response. But, before the change to CAS client arrives, is any change to deploy your branch in a production service? |
I think you can use the code from this PR but be careful to how often you try a gateway request. By overriding the method peformGatewayRequest in DefaultCasGatewayRequestMatcher, you can fine tune when to issue a gateway request (e.g once per session, every XX minutes...) |
Thanks indeed. In my use case once per session is enough. |
+1 interest Otherwise i followed this approch to redirect after authentication on specific page : http://jlorenzen.blogspot.fr/2013/07/remember-target-url-with-spring.html |
I'm afraid this solution does not fit perfectly to my circumstances. In my opinion there are three pitfalls:
I have implemented a easy solution based on javascript in order to trigger a gateway aware request if and only if CAS query (CASTGC) is present. Let me know if you need any further information. |
@Julicrack I'm interested, because i used my specified approach to authenticate on a REST api for a web single page done with angularJS. And I found the CAS approach wasn't really easy/obvious (for this case) when we compare to other auth. |
My approach consists on including an iframe within client page pointing to CAS domain. This iframe would include a javascript in order to dinamically obtain CAS ticket granting cookie and if 'valid' reload page on top window using gateway mode. The aim is to avoid as much as possible gateway requests. This is the iframe content: <html>
<head>
<script src="/js/gateway.js"></script>
</head>
<body onload="CAS.triggerGatewayAuth()">
<form id="reloadParent" name="reloadParent" method="GET" target="_top">
<input type="hidden" name="gateway" value="true" />
</form>
</body id="body">
</html> And this is the included javascript: var CAS = {
// Regexp that matches an URL
urlRegexp : /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/,
// Regexp that matches the Query String Part (QSP)
qspRegexp : /([^?=&]+)(=([^&]*))?/g,
// Triggers a CAS gateway authentication if Ticket Granting Cookie (TGC) is present and has not already being
// processed (TGC == CTR)
triggerGatewayAuth : function() {
/*
* Parse iframe location to obtain a gateway aware redirect location
*
* i.e.: <iframe src="http://my.cas.com/gateway.html#http://mysite.com?foo=bar"></iframe>
*/
var triggerCasGatewayUrl = decodeURI(CAS.urlRegexp.exec(location.href)[7]); // i.e.: http://mysite.com?foo=bar
var triggerCasGatewayUrlParts = CAS.urlRegexp.exec(triggerCasGatewayUrl);
var triggerCasGatewayHst = triggerCasGatewayUrlParts[3]; // i.e.: mysite.com
var triggerCasGatewayQsp = triggerCasGatewayUrlParts[6]; // i.e.: ?foo=bar
var casCtrCookieName = 'CASCTR.' + triggerCasGatewayHst;
var casTgc = W3S.getCookie('CASTGC'); // CAS ticket granting cookie
var casCtr = W3S.getCookie(casCtrCookieName); // Last tested ticket granting cookie
// If ticket granting cookie exist, has not been tested and
if ((typeof casTgc != 'undefined') && (typeof triggerCasGatewayUrl != 'undefined')) {
if (casCtr != casTgc) {
var triggerCasGatewayQsp = CAS.urlRegexp.exec(triggerCasGatewayUrl)[6]; // i.e.: ?foo=bar
var triggerCasGatewayQspMap = {}; // https://remysharp.com/2008/06/24/query-string-to-object-via-regex
triggerCasGatewayQsp.replace(CAS.qspRegexp, function($0, $1, $2, $3) {
triggerCasGatewayQspMap[$1] = $3;
});
// Avoid infinite loop
W3S.setCookie(casCtrCookieName, casTgc);
// Trigger a gateway aware authentication request
CAS.decorateForm(document.forms[0], triggerCasGatewayUrl, triggerCasGatewayQspMap).submit();
}
}
},
// Decorated the form that triggers the authentication request
decorateForm : function(formElement, action, parameterMap) {
formElement.action = action;
for ( var key in parameterMap) {
var inputElement = document.createElement("input");
inputElement.setAttribute('type', 'hidden');
inputElement.setAttribute('name', key);
inputElement.setAttribute('value', parameterMap[key]);
formElement.appendChild(inputElement);
}
return formElement;
}
};
/*
* http://www.w3schools.com/js/js_cookies.asp
*/
var W3S = {
setCookie : function(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + "; " + expires;
},
getCookie : function(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ')
c = c.substring(1);
if (c.indexOf(name) == 0)
return c.substring(name.length, c.length);
}
// return "";
}
} Iframe src should be something like this: Gateway mode is enabled if and only if 'gateway' parameter is included and true. Do note external javascript frameworks are intentionally avoided. Any feedback would be really apprecitated. |
hello
But again, there is something missing to fulfill this PR; I will soon submit an update. |
Umm, I'm afraid that Google does (or will) take into account JavaScript and/or cookie during indexing. That is the reason why script above triggers a gateway authentication if and only if a cookie from other domain is present. Google will never keep track of this cookie (CASTGC) and gateway mode is avoided during crawling procceses. On other hand, the unique mission of this script is just to realize when is the more accurate moment to trigger a gateway request. In such a case the negotiation is done according with this branch. Further reading: |
Thanks for carrying this forward guys. As @miremond guessed, I'd prefer to allow this feature to work without JavaScript. I don't think there is necessarily a problem with using JavaScript in individual applications for customizations, I just don't thinks something like CAS Gateway should require JavaScript. With that in mind, I prefer the cookie approach. |
A cookie trigerring based solution is easier... and 90% of times easier means better. Supossing Google won't follow cookies this would be a valid approach from SEO point of view. However asumming every single anonymous request will carry out a 302 is too much IMHO. Aproach above not only reduce the number of hits to CAS server but also simplifies browsing and backward and forward button from cliente webapp point of view. Thanks indeed for your support. |
aa28ae7
to
aed288d
Compare
Hi all, I just wanted to share a server side implementation based on the feedback, patches and discussion here - in case others don't want to manually build spring security or modify cas. We of course want to use this patch once it landed, but this approch works for us so far:
The HTTP session attribute ssoCheckDone takes care of just using the CAS gateway feature once for each session.
|
@miremond Please sign the Contributor License Agreement! Click here to manually synchronize the status of this Pull Request. See the FAQ for frequently asked questions. |
@miremond Please sign the Contributor License Agreement! Click here to manually synchronize the status of this Pull Request. See the FAQ for frequently asked questions. |
Closing this given there hasn't been the updates (i.e. use cookies to prevent redirecting for every request) have not been made |
The opportunity and the implementation details of this new feature were discussed in Jira SEC-977.
The new filter TriggerCasGatewayAuthenticationFilter has been added to
call the CasAuthenticationEntryPoint when we want try a silent CAS
authentication (typically on a public page). The trigger criteria is
done with a requestMatcher instance.
The method unsuccessfulAuthentication has been overridden in
CasAuthenticationFilter in order to redirect to the saved url if there
was no SSO session (no service ticket sent from CAS).
To avoid infinite loop, we use the DefaultGatewayResolverImpl from Jasig
Cas Client.
I have signed and agree to the terms of the SpringSource Individual Contributor License Agreement.