Reputation: 37154
I retrofitted my GWT/GXT application with basic LDAP Authorization using basic HTTP authentication. It works well when I start new browser - I get the prompt and get authorized against corporate LDAP. My problem - I can't logout unless I close/reopen the browser. I can debug and see how SecurityContextLogoutHandler#logout
is called and the following code is executed
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
SecurityContextHolder.clearContext();
However it seemingly has no effect as site is reloaded and I never get another HTTP auth prompt unless I restart the browser (even clearing the cache/cookies won't help). Here's relevant portion of applicationContext.xml
<security:http auto-config='true'>
<security:intercept-url pattern="/reports/**" access="ROLE_USER" />
<security:http-basic />
<security:logout logout-url="/reports/logout"
logout-success-url="/reports/Application.html" />
</security:http>
I tried to define custom LogoutSuccessHandler
and do authentication.setAuthenticated(false);
but that also has no effect
Anything here I'm missing here? Your help will be much appreciated
Upvotes: 17
Views: 19333
Reputation: 339
One way I've found to do this is to return HTTP 401 responses. Some quick testing shows this works in the macOS versions of Safari, Chrome & Firefox. The code I use is essentially equivalent to this:
@RequestMapping(value="/logout",method=RequestMethod.POST)
public void logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
session.setAttribute(LOGOUT_SESSION_KEY, true);
response.setStatus(303);
response.addHeader("Location", URL_OF_APPLICATION_HOME_PAGE);
}
@RequestMapping("/")
public void home(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
if (Boolean.TRUE.equals(session.getAttribute(LOGOUT_SESSION_KEY))) {
if (request.getHeader("Authorization") != null) {
session.invalidate();
}
response.setStatus(401);
response.addHeader("WWW-Authenticate", "Basic realm=\"" + HTTP_BASIC_REALM + "\"");
return;
}
/* Generate home page */
}
The trick is that you usually need to return the 401 header twice - after the first one, the browser will retry with its cached credentials (sending the "Authorization" header, at which point we destroy the user's old session), the second one seems to clear them and prompts for a login.
Upvotes: 1
Reputation: 15799
Bostone is basically correct that you cannot handle this server-side. There is, however, a bit of a hack that's available.
The short answer is to redirect the user to the URL you want them to land on, but prefix it with a bad username followed by @
. So as an ugly example,
<security:logout logout-url="/reports/logout"
logout-success-url="http://BADNAME@localhost/reports/Application.html" />
Ideally you'd implement a handler that would do this for you, but for illustration purposes, I think that gets the concept across.
Upvotes: 4
Reputation: 584
I strongly believe that you are correct. You cannot invalidate a Basic or Digest authentication w/o doing it in the browser. Even there it is going to be extremely difficult if not impossible. Frankly I can't even think of a way to do it there (other than attempting to close the browser).
As you noted, the problem is that even though you invalidate everything on the server side, you have given the browser everything that it needs to automatically generate a new authentication session on its own. The only way to clear that is to clear the browser. Even restarting your server won't clear disable the ability of the browser to create a new authentication which is a pretty good indication that you're not going to be able to accomplish this on the server side without invalidating the Basic or Digest authentication contract.
Frankly this seems like a pretty substantial security hole in browsers in that you should never leave a browser running on an open access server after authenticating into a site with basic or digest authentication.
Form authentication may be your best bet because there you are not giving the browser information on how to resurrect an authentication unless you allow a rememberme cookie. Even there, you can clear the cookie on logout so that's easier.
Upvotes: 1
Reputation: 37154
OK. after spending way too much time with this I think I have the answer. It's simple - one cannot bail out of basic HTTP authentication using server-side technology. Basically authorization string is base-64 decoded in the HTTP header and when protected page is loaded to the browser the security token gets repopulated so no matter how often you erase it on the server it gets resurrected every time the page is called. I suppose it is possible to play some clever tricks on the browser side but that would be brittle and unreliable
For my case I will be switching to form-based authentication which gives much better control over login/logout process anyways.
I will hold on accepting my own answer in favor someone coming out with acceptable solution
Upvotes: 30
Reputation: 1262
<logout invalidate-session="true" logout-url="/reports/logout"
logout-success-url="/reports/Application.html" />
just set invalidate session to 'true' and see does it works.
check if this listner is there in your web.xml:
<!-- Security: to listen session requests -->
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
If above things do not work then you can try following solution. I think problem is with your following line,
<security:intercept-url pattern="/reports/**" access="ROLE_USER" />
Above line means that you added access ROLE_USER to yout '/reports/**' link. But after logout you are giving same url for logout success. then how it will work? Either change location of your logout success page or you can add followin line in http tag.
<intercept-url pattern="/reports/Application.html" filters="none" />
Upvotes: 0
Reputation: 40186
Uhm... strange. I don't see anything wrong with your configuration. You really don't need to define any custom logout handler because it should be handled by Spring Security.
Try this, instead of defining your logout-url
, use the default logout link:-
...
<security:logout logout-success-url="/reports/Application.html" />
...
Then, use /j_spring_security_logout
to logout.
Does this work at all?
Upvotes: 3