Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example analysis of login limit, login judgment redirection and session time setting of springboot+shiro

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article will explain in detail the example analysis of springboot+shiro login limit, login judgment redirection and session time setting. The editor thinks it is very practical, so I share it with you for reference. I hope you can get something after reading this article.

Login Control Project of springboot + shiro

Spring boot + mybatis + layui + shiro background rights management system: https://blog.51cto.com/wyait/2082803

This article is based on spring boot + mybatis + layui + shiro background rights management system, with new features:

Shiro concurrent login number control (exceed the maximum number of login users, clean up users) function

Resolve the problem of redirecting to the nested display of the login page after judging that the user is not logged in in the parent-child page

Resolve the ajax request to determine how to redirect to the login page after the user has not logged in

Resolve the problem of session valid time conflict caused by the use and completion of function 1.

The second part:

Springboot + shiro dynamically updates user information: https://blog.51cto.com/wyait/2112200

Springboot + shiro permission comments, unified exception handling, request garbled resolution: https://blog.51cto.com/wyait/2125708

Project source code

Project source code: (including database source code)

Github source code: https://github.com/wyait/manage.git

Ma Yun: https://gitee.com/wyait/manage.git

Github corresponding project source code directory: wyait-manage-1.2.0

Code cloud corresponding to the project source directory: wyait-manage-1.2.0

Scene

For the same user, log in at A × × × first; then log out of the login status of A × × × when B × × × log in, and vice versa. Or limit the number of users online on different devices at the same time.

Technical realization

Implementation based on shiro and ehcache

Solution idea

Spring security directly provides the corresponding functions.

Shiro does not provide a default implementation, but you can add this feature to Shiro. Is to use shiro's powerful custom access control interceptor: AccessControlFilter, integrate this interface to implement the following two methods.

/ * * Returns true if the request is allowed to proceed through the filter normally, or false * if the request should be handled by the * {@ link # onAccessDenied (ServletRequest,ServletResponse,Object) onAccessDenied (request,response,mappedValue)} * method instead. * * @ param request the incoming ServletRequest * @ param response the outgoing ServletResponse * @ param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings. * @ return true if the request should proceed through the filter normally, false if the * request should be processed by this filter's * {@ link # onAccessDenied (ServletRequest,ServletResponse,Object)} method instead. * @ throws Exception if an error occurs during processing. * / protected abstract boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;... / * Processes requests where the subject was denied access as determined by the * {@ link # isAccessAllowed (javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed} * method. * * @ param request the incoming ServletRequest * @ param response the outgoing ServletResponse * @ return true if the request should continue to be processed; false if the subclass will * handle/render the response directly. * @ throws Exception if there is an error processing the request. * / protected abstract boolean onAccessDenied (ServletRequest request, ServletResponse response) throws Exception

Look at the abstract class AccessControlFilter:

IsAccessAllowed: indicates whether access is allowed; mappedValue is the interceptor parameter part of the [urls] configuration. If access is allowed, true is returned, otherwise false

OnAccessDenied: indicates whether it has been processed when access is denied; if true is returned, it needs to be processed; if false is returned, the interceptor instance has been processed, and it will be returned directly.

OnPreHandle: these two methods are automatically called to determine whether to continue processing

In addition, AccessControlFilter also provides the following methods to handle such as login success / redirect to the previous request:

Void setLoginUrl (String loginUrl) / / used for authentication. Default / login.jsp String getLoginUrl () Subject getSubject (ServletRequest request, ServletResponse response) / / get Subject instance boolean isLoginRequest (ServletRequest request, ServletResponse response) / / whether the current request is a login request void saveRequestAndRedirectToLogin (ServletRequest request, ServletResponse response) throws IOException / / Save the current request and redirect it to the login page void saveRequest (ServletRequest request) / / Save the request Redirect the request void redirectToLogin (ServletRequest request, ServletResponse response) / / to the login page if the login is successful.

For user access control, you can inherit AccessControlFilter.

Train of thought:

a. When the login is successful, the user is saved to the session provided by shiro and added to the ehcache cache at the same time

B. After getting the session, KickoutSessionFilter first determines whether the value can be obtained through the cache, and if so, it matches the server-side session (user's name (each user's name must be different))

c. If there is a match, the system will create a new session; for the newly logged-in user, and the previous session confirmation will be invalidated and kicked out, and the old user will be unable to continue the operation and will be forced to go offline.

Shiro technology implementation process

Here is the custom access control interceptor: KickoutSessionFilter:

Custom filter class KickoutSessionFilterpackage com.wyait.manage.filter;import java.io.Serializable;import java.util.ArrayDeque;import java.util.Deque;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import com.wyait.manage.pojo.User;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.session.Session;import org.apache.shiro.session.mgt.DefaultSessionKey;import org.apache.shiro.session.mgt.SessionManager;import org.apache.shiro.subject.Subject Import org.apache.shiro.web.filter.AccessControlFilter;import org.apache.shiro.web.util.WebUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.lyd.admin.pojo.AdminUser / * @ Project name: wyait-manager * @ Class name: KickoutSessionFilter * @ Class description: custom filter, user access control * @ Creator: wyait * @ creation time: April 24, 2018 5:18:29 * @ version: * / public class KickoutSessionFilter extends AccessControlFilter {private static final Logger logger = LoggerFactory .getLogger (KickoutSessionFilter.class); private String kickoutUrl / / address private boolean kickoutAfter = false; / / users logged in before / after kicking out default false users logged in before kicking out private int maxSession = 1; / / the maximum number of sessions of the same account defaults to 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 public void setCacheManager (CacheManager cacheManager) of the key of Cache {/ / must be consistent with the cache name in the ehcache cache configuration this.cache = cacheManager.getCache ("shiro-activeSessionCache");} @ 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); / / does not have login authorization and does not remember me if (! subject.isAuthenticated () & &! subject.isRemembered ()) {/ / if not logged in, proceed directly to the subsequent process return true } Session session = subject.getSession (); logger.debug ("= = session time setting:" + String.valueOf (session.getTimeout ()) + "="); try {/ / current user User user = (User) subject.getPrincipal (); String username = user.getUsername () Logger.debug ("= = current user username:==" + username); Serializable sessionId = session.getId (); logger.debug ("= current user sessionId:==" + sessionId); / / Deque deque = cache.get (username) without reading cache users; logger.debug ("= current deque:==" + deque) If (deque = = null) {/ / initialize queue deque = new ArrayDeque ();} / / if there is no such sessionId in the queue and the user is not kicked out Put in queue if (! deque.contains (sessionId) & & session.getAttribute ("kickout") = = null) {/ / put sessionId in queue deque.push (sessionId); / / cache user's sessionId queue cache.put (username, deque) } / / if the number of sessionId in the queue exceeds the maximum number of sessions, start kicking while (deque.size () > maxSession) {logger.debug ("= deque queue length: =" + deque.size ()); Serializable kickoutSessionId = null; / / whether to kick out the later login. Default is false. That is, the user logged in by the latter kicks out the user logged in by the former. If (kickoutAfter) {/ / if you kick out the latter kickoutSessionId = deque.removeFirst ();} else {/ / otherwise kick out the former kickoutSessionId = deque.removeLast ();} / / update the cache queue cache.put (username, deque) after kick out Try {/ / get the session object Session kickoutSession = sessionManager .getSession (new DefaultSessionKey (kickoutSessionId)) of the kicked out sessionId If (kickoutSession! = null) {/ / sets the kickout property of the session to indicate that kickoutSession.setAttribute ("kickout", true) has been kicked out }} catch (Exception e) {/ / ignore exception}} / / ajax request / / if kicked out, (the former or the latter) exit directly Redirect to the address if ((Boolean) session.getAttribute ("kickout")! = null & (Boolean) session.getAttribute ("kickout") = = true) {/ / session was kicked out of try {/ / logon subject.logout () } catch (Exception e) {/ / ignore} saveRequest (request); logger.debug ("= = the path of user redirection after kick out kickoutUrl:" + kickoutUrl); / / redirect WebUtils.issueRedirect (request, response, kickoutUrl); return false } return true;} catch (Exception e) {/ / ignore / / redirect to login interface WebUtils.issueRedirect (request, response, "/ login"); return false;}} set ShiroConfig configuration class

SessionDAO the CRUD; used for the session to view the source code of this API:

Public interface SessionDAO {/ * for example, DefaultSessionManager will call this method after creating the session; for example, saving to a relational database / file system / NoSQL database can achieve session persistence; return the ID.equals (session.getId ()) returned here mainly by the session ID;. * / Serializable create (Session session); / / get the session Session readSession (Serializable sessionId) throws UnknownSessionException; / / update the session according to the session ID; call void update (Session session) throws UnknownSessionException; / / delete the session if you update the last access time of the session / stop the session / set the timeout / set the removal attribute Void delete (Session session) is called when the session expires / stops (such as when the user exits); / / gets all the current active users. If the number of users is large, this method affects the performance of Collection getActiveSessions ();}

SessionDAO implementation class:

A. AbstractSessionDAO provides the basic implementation of SessionDAO, such as generating session ID, etc.; B. CachingSessionDAO provides a session cache transparent to developers, and you only need to set the corresponding CacheManager; c. MemorySessionDAO maintains the session directly in memory; d. EnterpriseCacheSessionDAO provides session maintenance of the cache function, which is implemented by default using MapCache, and internally uses ConcurrentHashMap to save cached sessions.

EnterpriseCacheSessionDAO configuration in the ShiroConfig configuration class:

/ * the implementation of the EnterpriseCacheSessionDAO shiro sessionDao layer; * provides session maintenance for caching, which is implemented by default using MapCache, and internally uses ConcurrentHashMap to save cached sessions. * / @ Beanpublic EnterpriseCacheSessionDAO enterCacheSessionDAO () {EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO (); / / add cache manager / / enterCacheSessionDAO.setCacheManager (ehCacheManager ()); / / add ehcache active cache name (must be the same as ehcache cache name) enterCacheSessionDAO.setActiveSessionsCacheName ("shiro-activeSessionCache"); return enterCacheSessionDAO;}

SessionManager configuration:

/ * * @ description: sessionManager add session cache operation DAO * @ founder: wyait * @ creation time: 8:13:52 on April 24, 2018 * @ return * / @ Bean public DefaultWebSessionManager sessionManager () {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager (); / / sessionManager.setCacheManager (ehCacheManager ()); sessionManager.setSessionDAO (enterCacheSessionDAO ()); return sessionManager;}

KickoutSessionFilter configuration

/ * * @ description: kickoutSessionFilter multiple device login restrictions for the same user * @ creator: wyait * @ creation time: 8:14:28 on April 24, 2018 * @ return * / public KickoutSessionFilter kickoutSessionFilter () {KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter (); / / use cacheManager to obtain the appropriate cache to cache the session logged in by the user Used to save the user-session relationship; / / here we still use the ehcache used by shiro to implement cacheManager () cache management / / We can also rewrite another to reconfigure the custom cache property kickoutSessionFilter.setCacheManager (ehCacheManager ()) such as cache time; / / used to get the session for kick-out operation according to the session ID KickoutSessionFilter.setSessionManager (sessionManager ()); / / whether to kick out the later login, the default is false;, that is, the login user of the latter kicks out the login user of the former; kicks out the order. KickoutSessionFilter.setKickoutAfter (false); / / the maximum number of sessions for the same user. Default is 1; for example, 2 means that the same user allows up to two people to log in at the same time; kickoutSessionFilter.setMaxSession (1); / / the address to be redirected after being kicked out; kickoutSessionFilter.setKickoutUrl ("/ toLogin?kickout=1"); return kickoutSessionFilter;}

Hand over SessionManager to SecurityManager management

/ * shiro security manager sets realm authentication, ehcache cache management, session manager, Cookie remember me manager * @ return * / @ Bean public org.apache.shiro.mgt.SecurityManager securityManager () {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager (); / / sets realm. SecurityManager.setRealm (shiroRealm ()); / / injection ehcache cache manager; securityManager.setCacheManager (ehCacheManager ()); / / injection session manager; securityManager.setSessionManager (sessionManager ()); / / injection Cookie remember me manager securityManager.setRememberMeManager (rememberMeManager ()); return securityManager;}

Configure filterChainDefinitionMap

... / add kickout authentication HashMap hashMap=new HashMap (); hashMap.put ("kickout", kickoutSessionFilter ()); shiroFilterFactoryBean.setFilters (hashMap);... filterChainDefinitionMap.put ("/ * *", "kickout,authc");... Resolve the sub-page. After redirection, the problem of page nesting occurs. Add login transfer page toLogin.htmlvar href=_window.location.href;if (href.indexOf ("kickout") > 0) {setTimeout ("top.location.href='/login?kickout';", 0);} else {setTimeout ("top.location.href='/login';", 0);} change the filterChainDefinitionMap configuration in shiro / / specify the link shiroFilterFactoryBean.setLoginUrl when login is required ("/ toLogin"). . / / configure links that will not be blocked to determine filterChainDefinitionMap.put ("/ login", "anon") from top to bottom.

The above two configurations can solve the nesting problem after page redirection.

Ajax request problem

If the number of users online is limited, user A who logged in before is kicked out; at this time, user A sends an ajax request in the system, and there will be problems such as a blank pop-up box.

Solution

Custom ShiroFilterUtils tool class determines whether the request is ajax

Package com.wyait.manage.utils;import javax.servlet.ServletRequest;import javax.servlet.http.HttpServletRequest;import org.slf4j.Logger;import org.slf4j.LoggerFactory / * @ Project name: wyait-manager * @ Class name: ShiroFilterUtils * @ Class description: shiro tool Class * @ creator: wyait * @ creation time: April 24, 2018 5:12:04 * @ version: * / public class ShiroFilterUtils {private static final Logger logger = LoggerFactory .getLogger (ShiroFilterUtils.class) / * * @ description: determine whether the request is ajax * @ founder: wyait * @ creation time: 5:00:22 * @ param request * @ return * / public static boolean isAjax (ServletRequest request) {String header = ((HttpServletRequest) request) .getHeader ("X-Requested-With") If ("XMLHttpRequest" .equalsIgnoreCase (header)) {logger.debug ("shiro tool class [wyait-manager-- > ShiroFilterUtils.isAjax] current request, Ajax request"); return Boolean.TRUE;} logger.debug ("shiro tool class [wyait-manager-- > ShiroFilterUtils.isAjax] current request, non-Ajax request"); return Boolean.FALSE;}}

Adjust KickoutSessionFilter filter to add ajax request judgment and response

Private final static ObjectMapper objectMapper = new ObjectMapper ();. / / ajax request / * to determine whether * 1 has been kicked. If it is an Ajax access, give the json a return value prompt. * 2. If it is a normal request, jump directly to the login page * / to determine whether the Ajax request ResponseResult responseResult = new ResponseResult (); if (ShiroFilterUtils.isAjax (request)) {logger.debug (getClass (). GetName () + "the current user is logged in somewhere else and it is an Ajax request!") ; responseResult.setCode (IStatusMessage.SystemStatus.MANY_LOGINS.getCode ()); responseResult.setMessage ("you have logged in elsewhere, please change your password or log in again"); out (response, responseResult);} else {/ / redirect WebUtils.issueRedirect (request, response, kickoutUrl) }. / * @ description: response output json * @ founder: wyait * @ creation time: 5:14:22 * @ param response * @ param result * / public static void out (ServletResponse response, ResponseResult result) {PrintWriter out = null; try {response.setCharacterEncoding ("UTF-8"); / / set the coding response.setContentType ("application/json") / / set the return type out = response.getWriter (); out.println (objectMapper.writeValueAsString (result)); / / output logger.error ("user online limit [wyait-manager-- > KickoutSessionFilter.out] responded to json message successfully");} catch (Exception e) {logger.error ("user online limit [wyait-manager-- > KickoutSessionFilter.out] response to json message error", e) } finally {if (null! = out) {out.flush (); out.close ();}

The front end compiles the public method isLogin to judge whether the user logs in or not.

/ * determine whether to log in or not. No login refreshes the current page, causing Shiro to block and jump to the login page * @ param result ajax request returned value * @ returns {if not logged in, refresh the current page} * / function isLogin (result) {if (result & & result.code & & result.code = = '1101') {_ window.location.reload (true); / / refresh the current page} return true;// returns true}

Ajax calls isLogin method in js

Post ("/ user/delUser", {"id": id}, function (data) {/ / determine whether the user logs in to if (isLogin (data)) {if (data== "ok") {/ / callback pop-up box layer.alert ("deleted successfully!" , function () {layer.closeAll (); / / load load method load (obj); / / Custom});} else {layer.alert (data); / / pop-up error prompt})

Only userList.js user list interface has been changed, other interfaces / / TODO

test

The same user is tested for online conflict, and then click one of the ajax methods in the login user interface first. If the background user has exited, the foreground isLogin refreshes the page, rerequests redirection to the / toLogin?kickout page, and finally jumps to the login interface.

Session valid time setting

Default validity time of session: 30 minutes (1800s)

Spring boot session time configuration:

# session timeout (seconds) 1 day server.session.timeout=86400

Session effective time problem

In the function of using shiro to limit the number of users online, after securityManager configures sessionManager, the session validity time configured in springboot is invalid (sessionManager manager overrides the configuration of session validity time in springboot).

Session expiration issu

Use shiro to limit the number of users online; after the user logs in, there is no operation for 2 minutes, and then the session expires.

Reason

Spring boot integrates shiro and reconfigures SessionManger when using shiro to limit the number of users online

/ inject session Manager; securityManager.setSessionManager (sessionManager ())

SessionManager, configure EnterpriseCacheSessionDAO:

SessionManager.setSessionDAO (enterCacheSessionDAO ())

The EnterpriseCacheSessionDAO class, when accessing session, operates through the ehcache cache.

Here, if you configure a cache, you need to configure a cache key similar to:

Shiro defaults to a default value of shiro-activeSessionCache. If it is different (the key value in the cache file), it needs to be replaced. Finally, the class for session access is CachingSessionDAO.

The cache manager uses org.apache.shiro.cache.ehcache.EhCacheManager, so eventually shiro will also call getCache when looking for session.

Ehcache.xml configuration

Here, the session cache time is configured to be 2 minutes, so there will be the problem of session invalidation after logging in for 2 minutes without operation.

After shiro gets the session in the ehcache cache, it matches the session check in the server. At this time, if the session of the server fails, there will be a problem.

Suppose you set the session of the current user on the server side to 30s [

SecurityUtils.getSubject (). GetSession (). SetTimeout (30000); / / millisecond

], the validity period of session in ehcache remains unchanged for 120 seconds. After 30 seconds of no operation, the request backend returns an error as follows:

Org.apache.shiro.session.ExpiredSessionException: Session with id [8aac0daf-c432-44b6-86cc-a618095ad2bd] has expired. Last access time: 18-4-24 11:32 Current time: 18-4-24 11:33 Session timeout is set to 30 seconds (0 minutes) at org.apache.shiro.session.mgt.SimpleSession.validate (SimpleSession.java:292) ~ [shiro-core-1.3.1.jar:1.3.1] at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doValidate (AbstractValidatingSessionManager.java:186) ~ [shiro-core-1.3.1.jar:1.3.1]. ...

Therefore, the valid time of session in ehcache cache must be consistent with that of server-side session.

Solution

Server session time settings:

/ / session valid for 1 day (milliseconds) SecurityUtils.getSubject () .getSession () .setTimeout (86400000)

The maximum time set can be positive or negative, and a negative number means that it will never time out.

SecurityUtils.getSubject (). GetSession (). SetTimeout (- 1000l)

Note: the time unit set here is: ms, but Shiro will convert this time to: s, and will drop the decimal part, so the setting is-1ms, which will be 0s after conversion to s, which will expire immediately. If it is still negative after dividing by 1000, it must be less than-1000.

Keep the Ehcache.xml time configuration consistent with the session validity time set by the server.

Check the session validity time in the code:

Logger.debug ("effective time of session setting:" + request.getSession (). GetMaxInactiveInterval ()); logger.debug ("effective time of session setting in shiro:" + SecurityUtils.getSubject (). GetSession (). GetTimeout ()); / / 86400 (seconds) / / 86400000 (milliseconds) summary

The specific implementation can be adjusted according to specific needs; a version of redis implementation is available in the near future.

Update for version 20180426

After the editing user succeeds, the exit is executed and the re-login information takes effect.

Prohibit users from deleting themselves

Tips for optimizing user list operation information

Role management list, by adding the parameter callback to achieve menu echo selection

Update for version 20180503

Add version version field of user table

Update user actions to ensure data consistency through the version field

Added dynamic update of user information through interceptor (synchronous update of online user information)

Default page home.html after a new login is successful

Optimize the details of page operation.

Spring boot + shiro dynamically updates user information

Link entry-- > spring boot + shiro dynamically update user information: https://blog.51cto.com/wyait/2112200

Update for version 20180606

Add shiro permission comment

Request to solve the garbled problem

Unified exception handling

Optimize the details of page operation.

Springboot + shiro permission comments, unified exception handling, request garbled resolution

Link entry-> springboot + shiro permission comments, unified exception handling, request garbled resolution: https://blog.51cto.com/wyait/2125708

TODO

Background method-level permission control, which can be realized through shiro configuration. Specific user management operations can be adjusted according to the actual needs of the business.

On "springboot+shiro login limit, login judgment redirection, session time setting example analysis" this article is shared here, I hope the above content can be of some help to you, so that you can learn more knowledge, if you think the article is good, please share it out for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report