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

How to implement SpringSecurity to allow only one device to be online

2025-01-21 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

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

This article introduces the relevant knowledge of "how to allow only one device to be online in SpringSecurity". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

1. Demand analysis

In the same system, we may only allow one user to log in on one terminal, generally speaking, this may be due to security considerations, but there are also some cases due to business considerations. Song GE's previous requirement is that a user can only log in on one device for business reasons.

To realize that a user cannot log in on two devices at the same time, we have two ideas:

The subsequent login automatically kicks out the previous login, as you can see in QQ.

If the user is already logged in, newcomers are not allowed to log in.

This kind of thinking can achieve this function, and which one to use depends on our specific needs.

In Spring Security, both are easy to implement and can be done with a single configuration.

two。 Concrete realization

2.1 kick out the logged-in user

To kick out the old login with the new login, we just need to set the maximum number of sessions to 1, as follows:

@ Override protected void configure (HttpSecurity http) throws Exception {http.authorizeRequests () .anyRequest () .authenticated () .formLogin () .loginPage ("/ login.html") .permitAll () .and () .csrf () .disable () .sessionManagement ( ) .maximumSessions (1) }

MaximumSessions indicates that the maximum number of sessions is configured to 1, so that subsequent logins will automatically kick out previous logins. The other configurations here are what we talked about in the previous article, so I won't repeat them. You can download the complete code of the case at the end of the article.

After the configuration is complete, test it with Chrome and Firefox browsers respectively (or use the multi-user feature in Chrome).

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

After logging in successfully on Chrome, access the / hello interface.

After logging in successfully on Firefox, access the / hello interface.

When you access the / hello interface on Chrome again, you will see the following prompt:

This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).

As you can see, this session is said to have expired because it uses the same user for concurrent login.

2.2 prohibit new logins

If the same user is already logged in, and you don't want to kick him out, but you want to disable new login operations, it's also easy to configure as follows:

@ Override protected void configure (HttpSecurity http) throws Exception {http.authorizeRequests () .anyRequest () .authenticated () .formLogin () .loginPage ("/ login.html") .permitAll () .and () .csrf () .disable () .sessionManagement ( ) .maximumSessions (1) .maxSessionsPreventsLogin (true) }

Just add the maxSessionsPreventsLogin configuration. At this point, after one browser logs in successfully, the other browser cannot log in.

Isn't it easy?

But it's not over yet, we need to provide another Bean:

@ Bean HttpSessionEventPublisher httpSessionEventPublisher () {return new HttpSessionEventPublisher ();}

Why add this Bean? Because in Spring Security, it cleans up session records in a timely manner by listening for session destruction events. After the user logs in from different browsers, there will be a corresponding session. When the user logs out, the session will fail, but the default failure is achieved by calling the StandardSession#invalidate method, and this failure event cannot be perceived by the Spring container. As a result, when the user logs out, Spring Security does not clean up the session information table in time, thinking that the user is still online. As a result, users are unable to log in again (friends can try not to add the above Bean, and then ask the user to log out and then log in again).

To solve this problem, we provide a HttpSessionEventPublisher, which implements the HttpSessionListener interface. In this Bean, the events created and destroyed by session can be sensed in time, and the relevant creation and destruction events are published by calling the event mechanism in Spring, which is perceived by Spring Security. Part of the source code of this class is as follows:

Public void sessionCreated (HttpSessionEvent event) {HttpSessionCreatedEvent e = new HttpSessionCreatedEvent (event.getSession ()); getContext (event.getSession (). GetServletContext ()) .publishEvent (e);} public void sessionDestroyed (HttpSessionEvent event) {HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent (event.getSession ()); getContext (event.getSession (). GetServletContext ()) .publishEvent (e);}

OK, although there is one more configuration, it is still very simple!

3. Realization principle

How is the above function implemented in Spring Security? Let's analyze the source code a little bit.

First of all, we know that the user logs in through UsernamePasswordAuthenticationFilter, and the call to the filter method in UsernamePasswordAuthenticationFilter is triggered in AbstractAuthenticationProcessingFilter. Let's take a look at the call to the AbstractAuthenticationProcessingFilter#doFilter method:

Public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (! requiresAuthentication (request, response)) {chain.doFilter (request, response); return;} Authentication authResult; try {authResult = attemptAuthentication (request, response); if (authResult = = null) {return;} sessionStrategy.onAuthentication (authResult, request, response);} catch (InternalAuthenticationServiceException failed) {unsuccessfulAuthentication (request, response, response) Return;} catch (AuthenticationException failed) {unsuccessfulAuthentication (request, response, failed); return;} / / Authentication success if (continueChainBeforeSuccessfulAuthentication) {chain.doFilter (request, response);} successfulAuthentication (request, response, chain, authResult)

In this code, we can see that after calling the attemptAuthentication method to go through the authentication process, and coming back, the next step is to call the sessionStrategy.onAuthentication method, which is used to deal with the concurrency problem of session. Specifically, it is:

Public class ConcurrentSessionControlAuthenticationStrategy implements MessageSourceAware, SessionAuthenticationStrategy {public void onAuthentication (Authentication authentication, HttpServletRequest request, HttpServletResponse response) {final List sessions = sessionRegistry.getAllSessions (authentication.getPrincipal (), false); int sessionCount = sessions.size (); int allowedSessions = getMaximumSessionsForThisUser (authentication); if (sessionCount < allowedSessions) {/ / They haven't got too many login sessions running at present return;} if (allowedSessions =-1) {/ / We permit unlimited logins return } if (sessionCount = = allowedSessions) {HttpSession session = request.getSession (false); if (session! = null) {/ / Only permit it though if this request is associated with one of the / / already registered sessions for (SessionInformation si: sessions) {if (si.getSessionId (). Equals (session.getId () {return }} / / If the session is null, a new one will be created by the parent class, / / exceeding the allowed number} allowableSessionsExceeded (sessions, allowedSessions, sessionRegistry) } protected void allowableSessionsExceeded (List sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {if (exceptionIfMaximumExceeded | | (sessions = = null)) {throw new SessionAuthenticationException (messages.getMessage ("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object [] {allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));} / Determine least recently used sessions, and mark them for invalidation sessions.sort (Comparator.comparing (SessionInformation::getLastRequest)) Int maximumSessionsExceededBy = sessions.size ()-allowableSessions + 1; List sessionsToBeExpired = sessions.subList (0, maximumSessionsExceededBy); for (SessionInformation session: sessionsToBeExpired) {session.expireNow ();}

Let me explain this core code to you a little bit:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

First, call the sessionRegistry.getAllSessions method to get all the session of the current user. When this method is called, it passes two parameters, one is the current user's authentication, and the other parameter false means that it does not contain the expired session (after the user logs in successfully, the user's sessionid will be saved, where key is the user's principal, and value is a collection of sessionid corresponding to the topic).

Next, calculate how many valid session the current user already has, and get the number of session concurrency allowed.

If the current number of session (sessionCount) is less than the number of session concurrency (allowedSessions), no processing is done; if the value of allowedSessions is-1, there is no limit to the number of session.

If the current number of session (sessionCount) is equal to the number of session concurrency (allowedSessions), then first see whether the current session is not null, and already exists in sessions, if it already exists, it is your own family, do not do any processing; if the current session is null, it means that a new session will be created, at which time the current number of session (sessionCount) will exceed the number of session concurrency (allowedSessions).

If none of the previous code fails to return, then you will enter the policy determination method allowableSessionsExceeded.

In the allowableSessionsExceeded method, there will first be the exceptionIfMaximumExceeded attribute, which is the value of the maxSessionsPreventsLogin we configured in SecurityConfig. The default is false. If it is true, the exception will be thrown directly, so the login will fail this time (corresponding to the effect of section 2.2). If it is false, the sessions will be sorted by request time, and then the excess session will expire (corresponding to the effect of section 2.1).

This is the end of the content of "how to implement SpringSecurity to allow only one device to be online". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

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