Application scenarios

  1. We often find that when user A logs in in Beijing and then logs in in Tianjin, user A needs to kick out the status of Beijing login. If the user re-logs in from Beijing, the user in Tianjin will be kicked out again, and so on. Or you need to limit the number of users that can be online at the same time. If the limit is exceeded, kick out the first or last user.

  2. In the first scenario, kicking out a user is triggered by the user, and sometimes it is necessary to manually kick out an online user, which is to manage the list of current online users.

· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · personal blog: z77z. Oschina. IO /

This program download address: git.oschina.net/z77z/spring… · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·

Implementation approach

Spring Security provides this functionality directly; Shiro does not provide a default implementation, but it can be easily incorporated into Shiro. Using shiro’s powerful custom access control interceptor, AccessControlFilter, integrate this interface to implement the following three methods.

abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;Copy the code

IsAccessAllowed: indicates whether access is allowed. MappedValue is the interceptor parameter part of the [urls] configuration that returns true if access is allowed, false otherwise;

OnAccessDenied: Indicates whether the access is denied. If true is returned, processing needs to continue. If returning false indicates that the interceptor instance has already been processed, it simply returns.

OnPreHandle: These two methods are automatically called to decide whether to continue processing;

AccessControlFilter also provides the following methods for handling requests such as successful login/redirection to the previous request:

Void setLoginUrl(String loginUrl) // Used for authentication, Default /login.jsp String getLoginUrl() Subject getSubject(ServletRequest Request, Boolean isLoginRequest(ServletRequest Request, ServletResponse response) / / if the current request is the login request void saveRequestAndRedirectToLogin (ServletRequest request, Throws IOException // Save the current request and redirect it to the login page void saveRequest(ServletRequest Request) // Save the request, Void redirectToLogin(ServletRequest Request, ServletResponse Response) // Redirects to the login pageCopy the code

Form-based authentication, for example, requires these features.

At this point the basic interceptor is done, and if we want access control we can inherit the AccessControlFilter; If we want to add some generic data we can just inherit PathMatchingFilter.

Below is my implementation of access control interceptor: KickoutSessionControlFilter:

/** * @author author z77z * @date Created time: March 5, 2017 1:16:38 * Read the current login user name, get the sessionId queue * 2 in the cache. If the length of the queue exceeds the maximum login limit, use the kickout rule * to store the session field in the previous session id to kickout: true and update the queue cache * 3. Check whether kickout in the session field is true and * wants to log it out. And then redirected to kick out the login prompt page * / public class KickoutSessionControlFilter extends AccessControlFilter {private String kickoutUrl; Private Boolean kickoutAfter = false; Private int maxSession = 1; private int maxSession = 1; // Maximum number of sessions for an account Default 1 private SessionManager SessionManager; private Cache> cache; public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } // Set the prefix of the Cache key public void setCacheManager(CacheManager CacheManager) {this. Cache = cacheManager.getCache("shiro_redis_cache"); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if(! subject.isAuthenticated() && ! Subject. IsRemembered ()) {// If there is no login, return true; } Session session = subject.getSession(); SysUser user = (SysUser) subject.getPrincipal(); String username = user.getNickname(); Serializable sessionId = session.getId(); Deque = cache.get(username); // If the sessionId does not exist in the queue and the user is not kicked out; Put into the queue if(! Deque. contains(sessionId) &&session. getAttribute(" kickOut ") == null) {// Store sessionId in queue deque.push(sessionId); // Put (username, deque); While (deque.size() > maxSession) {Serializable kickoutSessionId = null; If (kickoutAfter) {// kickoutSessionId = deque.removeFirst(); } else {kickoutSessionId = deque.removelast (); } // update the queue cache.put(username, deque); Try {/ / to get kicked out of the sessionId session object session kickoutSession = our sessionManager. GetSession (new DefaultSessionKey(kickoutSessionId)); if(kickoutSession ! = null) {/ / set kickout properties of the session said kicked off the kickoutSession. SetAttribute (" kickout ", true); }} catch (Exception e) {//ignore Exception}} // If ((Boolean)session. GetAttribute ("kickout")! =null&&(Boolean)session.getAttribute("kickout") == true) {// The session was kicked out of the try {// Exit the login subject.logout(); } catch (Exception e) { //ignore } saveRequest(request); // Redirect webutils. issueRedirect(request, response, kickoutUrl); return false; } return true; }}Copy the code

Configure this custom interceptor in the shiroconfig.java file:

Restrictions / * * * the same login and login number control * @ return * / public KickoutSessionControlFilter KickoutSessionControlFilter () { KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); // Use cacheManager to obtain a cache to cache user login sessions. Used to preserve the user-session relationship; // We can use shiro redisManager() to implement the cacheManager() cache management. Reconfigure the cache time custom caching attributes such as kickoutSessionControlFilter. SetCacheManager (cacheManager ()); // It is used to get the session and kick it out according to the session ID. kickoutSessionControlFilter.setSessionManager(sessionManager()); // Whether to kick the later login, the default is false; That is, the latter login user kicked out the former login user; Kick out of order. kickoutSessionControlFilter.setKickoutAfter(false); // Maximum number of sessions for a user. Default: 1; For example, "2" means a maximum of two users can log in at the same time. kickoutSessionControlFilter.setMaxSession(1); // Address redirected to after being kicked out; kickoutSessionControlFilter.setKickoutUrl("/kickout"); return kickoutSessionControlFilter; }Copy the code

Will this kickoutSessionControlFilter () into shiroFilterFactoryBean:

FiltersMap = new LinkedHashMap(); // Limit the number of concurrent online accounts. filtersMap.put("kickout", kickoutSessionControlFilter()); shiroFilterFactoryBean.setFilters(filtersMap);Copy the code

Since our link permissions are dynamically stored in the database, please refer to my previous blog post on dynamic permissions. Therefore, we also need to modify the link permissions in the database by setting the custom kickout permissions on the corresponding links. The diagram below:

Also write the corresponding jump page that was kicked out:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path; %> Kicked Out Kicked out, logged in elsewhere, or logged out when the login limit for this account has been reached.


Copy the code

The sessionDAO can be used to obtain the entire Shiro session List, and then display it in the front page. Kicked out corresponding user can use in the corresponding session of sessionId domain set key to kickout evaluates to true, the above KickoutSessionControlFilter will determine the session in the domain kickout value, do treatment response. I’m not going to do the code here, but you can try it out. Then synchronize the code to my code cloud for everyone to learn and exchange.

After dealing with this requirement, I found a problem. There was a premise that Ajax could not redirect and forward the page, so if the Ajax request was not logged in, the user would feel that there was no response, and the user would not know that the user had logged out. This is about optimizing ajax requests accordingly, and I have a solution that you can think about, and I will provide the code in the next blog.

In addition, I will improve the previous front-end page, for example, the following is my updated login page:

It has been updated to my code cloud.

Last Updated:



If you want to reprint, please indicate the source:
Z77z. Oschina. IO / 2017/03/05 /…