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.
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
- 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; (
- Since the user is not authenticated,
ExceptionTranslationFilterstarts Start Authentication and redirects the request to
AuthenticationEntryPoint, 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
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
- When a user submits their username and password, the
UsernamePasswordAuthenticationFiltertakes the username and password extracted from the
HttpServletRequestinstance and creates a
UsernamePasswordAuthenticationToken, which is an
- Next, the
UsernamePasswordAuthenticationTokenis passed into the
AuthenticationManagerinstance to be authenticated.
- Failure if authentication fails.
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 on
InteractiveAuthenticationSuccessEventevent. (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
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
AuthenticationManager, which is implemented by the
ProviderManageris configured to use an
PasswordEncoderto verify the password on the
UserDetailsreturned in the previous step.
- When authentication is successful, the
Authenticationreturned is of type
UsernamePasswordAuthenticationTokenand there is a
principalthat is the
UserDetailsreturned by the configured
UserDetailsService. Ultimately, the returned
UsernamePasswordAuthenticationTokenis set on the
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.
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.
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.
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
Authenticationimplementation used in this project is
UsernamePasswordAuthenticationTokenwith a custom
SecurityContextHolder. It wraps it in a
Supplierto delay the lookup.
- Next, it passes
- If the authorization is denied, an
AccessDeniedExceptionis thrown. In this case,
- If access is allowed,
AuthorizationFiltercontinues with FilterChain, allowing the application to process normally.
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
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.
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.
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
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.
BlogAuthorizationManager implementation class also uses the multi-threaded
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.