Spring Security's two resource release strategies, don't use it wrong!

Posted May 28, 20206 min read

The reason for this is this, some friends asked Songge a question on WeChat:

That is, he used Spring Security to log in as a user. After success, he could not obtain the logged-in user information. Songge wrote related articles before( Strange, Spring Security always fails to obtain the logged-in user information after successful login? ), but he does not seem to understand. Considering that this is a very common problem, I would like to talk about this topic with everyone from another angle today.

In Spring Security, how should resources be released in the end?

  1. Two ideas

In Spring Security, there is a resource. If you want users to be able to access without logging in, then in general, you have two configuration strategies:

The first is to configure the release in the configure(WebSecurity web) method, like this:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring(). antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon .ico ","/verifyCode ");
}

The second way is to configure in the configure(HttpSecurity http) method:

http.authorizeRequests()
        .antMatchers("/hello"). permitAll()
        .anyRequest(). authenticated()

The biggest difference between the two methods is that the first method is not to take the Spring Security filter chain, and the second method is to take the Spring Security filter chain. In the filter chain, the request is released.

When we use Spring Security, some resources can be released in the first way without verification. For example, static resources on the front-end page can be configured and released in the first way.

For some resources to be released, you must use the second method, such as the login interface. As you know, the login interface must also be exposed and can be accessed without logging in, but we ca n t expose the login interface in the first way. The login request must go through the Spring Security filter chain, because in this In the process, there are other things to do.

Next, I will use the login interface as an example to analyze the differences between the Spring Security filter chain and the friends.

  1. Login request analysis

First of all, everyone knows that when we use Spring Security, after a user logs in successfully, there are two ways to obtain user login information:

  1. SecurityContextHolder.getContext(). GetAuthentication()
  2. In the Controller method, add the Authentication parameter

Both methods can obtain the information of the currently logged in user. For specific operation methods, you can look at the tutorial released by Songge before: How does Spring Security dynamically update the logged-in user information? .

The data obtained by these two methods are from the SecurityContextHolder. The data in the SecurityContextHolder is essentially stored in ThreadLocal. The characteristic of ThreadLocal is the data stored in it. Which thread is stored and which thread can access it .

This brings about a problem. After the user logs in successfully, the user user data is stored in the SecurityContextHolder(thread1). When the next request comes(thread2), I want to obtain the user login information from the SecurityContextHolder, but I can't find it! Why? Because they are not the same Thread.

But in fact, under normal circumstances, after we use Spring Security to log in successfully, we can get the logged-in user information every time in the future, what's going on?

This we will introduce SecurityContextPersistenceFilter in Spring Security.

As everyone knows, whether it is Spring Security or Shiro, a series of functions are actually completed by filters. In Spring Security, Songge talked to you before the UsernamePasswordAuthenticationFilter filter, in this filter Before, there was another filter called SecurityContextPersistenceFilter, and the request would go through SecurityContextPersistenceFilter before it reached UsernamePasswordAuthenticationFilter.

Let's take a look at its source code(part):

public class SecurityContextPersistenceFilter extends GenericFilterBean {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request =(HttpServletRequest) req;
        HttpServletResponse response =(HttpServletResponse) res;
        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
                response);
        SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
        try {
            SecurityContextHolder.setContext(contextBeforeChainExecution);
            chain.doFilter(holder.getRequest(), holder.getResponse());
        }
        finally {
            SecurityContext contextAfterChainExecution = SecurityContextHolder
                    .getContext();
            SecurityContextHolder.clearContext();
            repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                    holder.getResponse());
        }
    }
}

The original method is very long, I have listed a few key parts here:

  1. SecurityContextPersistenceFilter inherits from GenericFilterBean, and GenericFilterBean is the implementation of Filter, so SecurityContextPersistenceFilter as a filter, the most important method in it is doFilter.
  2. In the doFilter method, it will first read a SecurityContext from the repo. The repo here is actually HttpSessionSecurityContextRepository. The operation of reading the SecurityContext will enter the readSecurityContextFromSession method. Here we see the core method of readingObject contextFromSession = httpSession.getAttribute(springSecurityContextKey);, the value of the springSecurityContextKey object here is SPRING \ _SECURITY \ _CONTEXT, and the read object will eventually be converted into a SecurityContext object.
  3. SecurityContext is an interface, it has a unique implementation class SecurityContextImpl, this implementation class is actually the value of user information saved in the session.
  4. After getting the SecurityContext, set this SecurityContext to ThreadLocal through the SecurityContextHolder.setContext method, so that in the current request, the follow-up operation of Spring Security, we can get user information directly from SecurityContextHolder.
  5. Next, use chain.doFilter to keep the request going down(this time you will enter the UsernamePasswordAuthenticationFilter filter).
  6. After the filter chain is completed, after the data response is sent to the front end, there is another finalization operation in the finally, which is critical. Here, the SecurityContext is obtained from the SecurityContextHolder. After it is obtained, the SecurityContextHolder will be emptied, and then the repo.saveContext method is called to store the obtained SecurityContext in the session.

At this point, the entire process is very clear.

When each request arrives at the server, first find the SecurityContext from the session, and then set it to the SecurityContextHolder to facilitate subsequent use. When the request leaves, the SecurityContextHolder will be cleared, and the SecurityContext will be put back into the session to facilitate the next one. Get it when the request comes.

When the login request comes, there is no user data yet, but when the login request is gone, the user login data will be stored in the session. When the next request comes, you can directly take it out and use it.

After reading the above analysis, we can draw at least two conclusions:

  1. If we use the first method mentioned above when we expose the login interface, without going through Spring Security and the filter chain, after the login is successful, the login user information will not be stored in the session, which will lead to Subsequent requests cannot obtain the logged-in user information(subsequent requests are also unauthenticated requests in the eyes of the system)

  2. If your login request is normal and you have taken the Spring Security filter chain, but the subsequent A request did not go through the filter chain(the first method mentioned above was released), then the A request cannot be obtained through the SecurityContextHolder To the logged-in user information, because it did not go through the SecurityContextPersistenceFilter filter chain at the beginning.

  3. Summary


In short, when the front-end static resources are released, you can directly leave the Spring Security filter chain, like this:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring(). antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon .ico ");
}

To allow additional access to the back-end interface, you need to carefully consider the scenario, but in general, it is not recommended to use the above method, the following method is recommended, because the reasons have been said before:

http.authorizeRequests()
        .antMatchers("/hello"). permitAll()
        .anyRequest(). authenticated()

Well, these are the two resource release strategies shared with friends, don't make mistakes ~

If you get something, remember to order and encourage Songge ~