Using Spring Seucurity to implement login authentication and authorization management is a large part of the project, and a relatively difficult part. This project has improved on the original project by replacing the deprecated FilterSecurityInterceptor authorization API with the new AuthorizationFilter authorization API recommended by the new version, and by taking into account the concurrent security of authorization during coding. In addition, the project has integrated Spring Session to provide cluster session support, improved the authorisation of anonymously accessible interfaces, added the ability to disable roles, and made some code optimisations. This is a summary of the main frameworks used in the project, for more information on the frameworks please see the official Spring Security documentation.
Authentication
Authentication process
The project uses login authentication with a username and password form. This section summarises how the project’s login authentication works in Spring Security. First, we look at how the user is asked to log in.

The above diagram is built on the SecurityFilterChain.
- First, a user makes an unauthenticated request to its unauthorized resource (/private).
- Spring Security’s
FilterSecurityInterceptorindicates that the unauthenticated request has been rejected by throwing an AccessDeniedException; (AuthorizationFilterreplacesFilterSecurityInterceptor) - Since the user is not authenticated,
ExceptionTranslationFilterstarts Start Authentication and redirects the request toAuthenticationEntryPoint, where the user is asked to redirect to the landing page. - The browser requests to go to the login page to which it was redirected.
- The front end renders the login page. (This project has separate front and back ends, no
LoginController)
When the username and password are submitted, UsernamePasswordAuthenticationFilter will authenticate the username and password. Next, we look at how the user logs in.

The above diagram is built on the SecurityFilterChain.
- When a user submits their username and password, the
UsernamePasswordAuthenticationFiltertakes the username and password extracted from theHttpServletRequestinstance and creates aUsernamePasswordAuthenticationToken, which is anAuthenticationtype. - Next, the
UsernamePasswordAuthenticationTokenis passed into theAuthenticationManagerinstance to be authenticated. - Failure if authentication fails.
SecurityContextHolderis emptied.RememberMeServices.loginFailis called. (This project does not use remember me authentication)AuthenticationFailureHandleris called, entering the authentication failure handler.
- Success if authentication is successful.
SessionAuthenticationStrategyis notified of new logins. (Not used in this project)Authenticationis set onSecurityContextHolder.RememberMeServices.loginSuccessis called.ApplicationEventPublisherpublishesInteractiveAuthenticationSuccessEventevent. (Not used in this project)AuthenticationSuccessHandleris called to enter the authentication success handler.
Finally, let’s understand what happens to the AuthenticationManager authentication process. The implementation of AuthenticationManager is ProviderManager, and AuthenticationProvider is the authentication provider delegated to it. The DaoAuthenticationProvider implements the AuthenticationProvider which uses the UserDetailsService and the PasswordEncoder to authenticate a username and password. The diagram below explains how the AuthenticationProvider works.

UsernamePasswordAuthenticationFilterpassesUsernamePasswordAuthenticationTokento theAuthenticationManager, which is implemented by theProviderManager.- The
ProviderManageris configured to use anAuthenticationProviderof typeDaoAuthenticationProvider. - The
DaoAuthenticationProviderlooks upUserDetailsfrom theUserDetailsService. - The
DaoAuthenticationProvideruses thePasswordEncoderto verify the password on theUserDetailsreturned in the previous step. - When authentication is successful, the
Authenticationreturned is of typeUsernamePasswordAuthenticationTokenand there is aprincipalthat is theUserDetailsreturned by the configuredUserDetailsService. Ultimately, the returnedUsernamePasswordAuthenticationTokenis set on theSecurityContextHolderby theUsernamePasswordAuthenticationFilter.
Session Management
In addition to the original Spring Security session management project, this project integrates Spring Session to provide clustered session support. The implementation of the SessionRegistry interface is a registry for Spring Security sessions, and with the HttpSessionEventPublisher exposed as a Spring bean to publish session lifecycle events, the SessionRegistry interface can be used to fetch user session information through the SessionRegistry interface. The SpringSessionBackedSessionRegistry is Spring Session’s custom implementation of the SessionRegistry interface and can be used to retrieve session information for a cluster from Spring Session.
|
|
The SpringSessionBackedSessionRegistry has a limitation in that it does not support the getAllPrincipals() method, i.e. it cannot retrieve all session principals. However, the backend administration of this project implements the ability to display a list of online users and needs to retrieve all session principals. In the implementation of the getAllSessions() method of the SpringSessionBackedSessionRegistry, I found that it looks for all sessions of a session subject by username, so I only need to save the usernames of the online users to retrieve all the online sessions using the username collection.
|
|
The following diagram explains how to get session information from the SpringSessionBackedSessionRegistry by saving the username.

- The user logs in successfully and enters the
AuthenticationSuccessHandler’s authentication success handler. - The user session is destroyed, causing the
SessionEventListenerto receive the user session destruction event. - When a user logs in successfully, the user name is saved in Redis; when the user session is destroyed, the user name is removed from Redis if the user has no other online sessions.
- The
SpringSessionBackedSessionRegistrygets the usernames of all online users from Redis and queries the session information with the usernames. - The session information is presented to the administrator.
Third Party Authentication
This project incorporates Spring Security to implement some third-party authentication features that can reduce user registration costs and improve the user experience. Please see the third party website for more information on how to integrate third party authentication. Only the core code of the Spring Security integration is presented here. AbstractLoginStrategy is an abstract third-party login strategy template, where the program obtains the third-party login information, constructs UsernamePasswordAuthenticationToken, and hands it over to Spring Security’s context SecurityContext to manage, and the user is then registered and logged in.
Authorization
Authorization process
This project has been improved to use the new version of the recommended AuthorizationFilter instead of the FilterSecurityInterceptor to implement authorization. AuthorizationFilter uses the simplified AuthorizationManager API instead of metadata sources, configuration properties, decision managers and voters. This simplifies reuse and customisation. The diagram below explains how authorization is performed by the AuthorizationManager.

The
Authenticationimplementation used in this project isUsernamePasswordAuthenticationTokenwith a customAuthorizationManagerimplementation
- First,
AuthorizationFiltergets anAuthenticationfromSecurityContextHolder. It wraps it in aSupplierto delay the lookup. - Next, it passes
Supplier<Authentication>andHttpServletRequestto theAuthorizationManager. - If the authorization is denied, an
AccessDeniedExceptionis thrown. In this case,ExceptionTranslationFilterhandles theAccessDeniedException. - If access is allowed,
AuthorizationFiltercontinues with FilterChain, allowing the application to process normally.
Authorization Core
The core of this project’s improved authorization core is a custom BlogAuthorizationManager implementation class that determines whether all requests are allowed or not, the key elements of this class are described in detail here.
As with the original design, the improved project still uses the locally cached authorization base resourceRoleList. The @PostConstruct annotated loadResourceRoleList() method will retrieve the authorization credentials from the database and write them to the cache when the application is started, and if there are no changes, the credentials will remain in memory, while if the credentials change, an external application will call updateAuthorizationCredentials() method to clear the cache until a new request for authorization is made and the cache is empty, then loadResourceRoleList() is called to read the data from the database and update it to the cache. This is also the most common caching strategy.
|
|
authorization is an important feature, and with the use of local caching, it was easy for me to think about thread safety. In a project of the magnitude of a personal blog, the issue of thread safety is basically non-existent, but if such a design is used in a production environment, thread safety must be considered, so I have rewritten the design to consider thread safety in Demo.
In a production environment, what could be the problem if thread safety is not taken into account? If the authorization basis changes and the cache is cleared, there will be a lot of requests for authorization, and each request will query the database, putting a lot of pressure on the database, when in fact it only needs to be queried once. If the request is important and needs to be updated in real time, the other requesting threads will not be aware of the change in authorization basis after the update, which may result in a false authorization.
There are certainly more problems than the two mentioned above, but the vast majority of these problems can be solved just as well as they can by locking. You cannot use normal locks, which only allow one thread to read or write at a time, which would greatly reduce throughput. authorization is usually based on a read-more-write less scenario and is best suited to using read-write locks. This project has been improved to use fair read/write locks and volatile variables to mark the availability of the cache, ensuring a thread-safe authorization process.
The reason for using fair locks is that if the request to be authorised is important and cannot be mis-authorised, non-fair locks may make previous authorization basis update requests later than later authorization requests, or even make authorization basis update requests late for processing, resulting in mis-authorization. The use of fair locks comes at the cost of reduced system throughput, but this side effect is nothing compared to the impact of a mis-authorization.
The volatile variable invalid is used to ensure visibility of the resourceRoleList cache between multiple threads, so that if one thread clears or updates the cache, the other threads will be aware of the change in time, avoiding problems such as mis-authorization or duplicate database queries caused by using expired cache data.
The clearResourceRoleList() method and the getAvailableAuthorities() method are central to thread safety. The former clears the cache with a write lock, while the latter uses double retrieval in conjunction with the volatile variable invalid, and also uses the lock degradation mechanism of ReentrantReadWriteLock. Here the double-ended search minimises the problem of repeated queries to the database, and the lock degradation mechanism ensures that the thread updating the cache can read the cached data. If the lock degradation mechanism is not used, and the thread updating the cache releases the write lock after the update is complete, and then some thread acquires the write lock and clears the cache, the thread that originally updated the cache will not be able to read the cache data, resulting in a mis-authorization.
|
|
The BlogAuthorizationManager implementation class also uses the multi-threaded ThreadLocal variable ANONYMOUS to refine the authorization of anonymously accessible resources. This variable is first set to FALSE by default in the check() method, indicating that anonymous access is not available by default, and then set to TRUE in the processResourceRoleList() method if anonymous access is determined to be available, and the request is then authorised in the check() method. One caveat to using the ThreadLocal variable is that its remove() method should be called to clean it up after use, otherwise it may cause a memory leak.
|
|
Reference:
https://insightblog.cn/articles/62