When (in Spring Security / MVC) the access to a page is denied, as the user has not enough privileges (though he is authenticated), I need to offer to login as another user via showing the login page (instead of the standard behaviour of showing a 403 access denied page).
I can write an AccessDeniedHandler, that redirects to the login page. But how will Spring Security react, when it finds out, that there is alrady another user logged in? Can I somehow logout the old user when the new one has successfully authenticated?
- 692
- 10
- 29
3 Answers
I've tried to log in a new user when there is already another user logged in. It works - without logging out the first user. His authorization is replaced by the new one's. This is the simple answer to my own question.
If someone is interested, how to forward to login page in case of access denied - here is my solution:
First define a custom RequestCache:
@Component("myRequestCache")
public class MyRequestCache extends HttpSessionRequestCache {
public MyRequestCache() {
super();
}
}
Second define a custom AccessDeniedHandler:
@Component("myAccessDeniedHandler")
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Autowired
@Qualifier("myRequestCache")
private RequestCache myRequestCache;
public MyAccessDeniedHandler() {
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc)
throws IOException, ServletException {
if (!response.isCommitted()) {
//Save Target-Request
myRequestCache.saveRequest(request, response);
//Forward to the login page
request.getRequestDispatcher("/loginPage").forward(request, response);
}
}
}
Third configure these two into Spring Security:
@Configuration
@EnableWebSecurity
public class myConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("myRequestCache")
RequestCache myRequestCache;
@Autowired
@Qualifier("myAccessDeniedHandler")
AccessDeniedHandler myAccessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestCache()
.requestCache(myRequestCache)
.and()
.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler)
}
}
What is happening here?
The MyAccessDeniedHandler forwards in the case of an AccessDeniedException to the login page. As this forward is invoked by this self-programmed class and not by Spring in the filter chain, we have to tell spring, what the target request is - to where it has to redirect after succesful authentication. This we do via the RequestCache.
- 692
- 10
- 29
-
I'm surprised we need a custom AccessDeniedHandler to manage the requestCache as expected. – Remy Nov 20 '19 at 09:28
-
1By default ExceptionTranslationFilter handles the requestCache, but only if the user is not logged in: see https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java#L179. If the user is logged in with a wrong role, then by default requests are not saved in requestCache... – Remy Nov 20 '19 at 09:30
-
1What is the `shopRequestCache` in the second code snippet? Should that be `myRequestCache` instead? – tom_mai78101 Jul 09 '20 at 16:15
-
1@tom_mai78101 yes, you're right - I've corrected it – olivmir Jul 16 '20 at 09:46
-
You don't need a custom `RequestCache`. You can get the current one with `http.getSharedObject(RequestCache.class)` – OrangeDog Feb 16 '23 at 16:58
If you want to logout the old user, I would suggest using SessionRegistry (http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/core/session/SessionRegistry.html)
Some hints:
In Spring configuration class
@Configuration
class MyConfiguration{
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
In your AccessDeniedHandler:
@Autowired
private SessionRegistry sessionRegistry;
....
List<SessionInformation> sessionInformations = sessionRegistry.getAllSessions("an-user", false);
for(SessionInformation session: sessionInformations) {
session.expireNow();
}
- 2,350
- 1
- 21
- 30
-
Thank you - but my question has been: how does Spring react, when you log in if already someone is logged in - does it work without logout and if no - how to perform this logout after commit of the login page and the authentication process. I have found a simple answer to this question :) - please look at what I have written. – olivmir Jan 13 '17 at 11:27
You can add following tag in your spring security xml file.
<http auto-config="true" use-expressions="true">
<access-denied-handler error-page="/accessDenied" />
<intercept-url pattern="/publicIndex1" access="isAnonymous()"/>
<intercept-url pattern="/index1" access="isAnonymous()"/>
<!-- Restrict URLs based on role -->
<intercept-url pattern="/#/publicIndex" access="isAnonymous()" />
<form-login
login-page="/publicIndex1"
always-use-default-target="false"
default-target-url="/index"
authentication-failure-url="/publicIndex1"
username-parameter="username"
password-parameter="password" />
<logout logout-success-url="/index1"
delete-cookies="JSESSIONID"
logout-url="/logout"
invalidate-session="true" />
</http>
In your spring controller.
@RequestMapping(value = "/accessDenied", method = RequestMethod.GET)
public ModelAndView accessDenied() {
System.out.println("access denied page call");
return new ModelAndView("accessDenied");
}
@RequestMapping(value = "/#/publicIndex", method = RequestMethod.GET)
public ModelAndView login(@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "logout", required = false) String logout) {
System.out.println("inside /#/public index");
ModelAndView model = new ModelAndView();
if (error != null) {
model.addObject("error", "Invalid username and password!");
}
System.out.println("**********************" + error);
if (logout != null) {
model.addObject("msg", "You've been logged out successfully.");
}
model.setViewName("publicIndex");
return model;
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout1() {
System.out.println();
User user1 = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userService.addUserOffline(user1.getUserId());
}
@RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView index() {
System.out.println("Redirect Controller Call");
User user = new User();
try {
user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
return new ModelAndView("publicIndex1");
}
long userId = user.getUserId();
userService.addLastLoginDate(userId);
System.out.println("user id==" + userId);
return new ModelAndView("index");
}
@RequestMapping(value = "/index1", method = RequestMethod.GET)
public ModelAndView index1() {
System.out.println("inside logout index1");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
return new ModelAndView("/index");
} else {
return new ModelAndView("index1");
}
}
- 898
- 8
- 19
-
1Thank you - but this does not answer my question, how Spring reacts, when you log in if already someone is logged in - does it work without logout and if no - how to perform this logout after commit of the login page and the authentication process. I have found a simple answer to this question :) - please look at what I have written. – olivmir Jan 13 '17 at 11:25