It all started when a friend asked Songgo a question on wechat:
He used Spring Security to log in as a user and failed to obtain the user information. Songgo has written about this before. But he didn’t seem to understand. Since this is a very common question, I thought I’d talk about it from a different Angle today.
In Spring Security, how do you give extra permits to resources?
1. Two ideas
In Spring Security, there is a resource that you want users to access without logging in. Generally speaking, you have two configuration strategies:
The first is to configure the release in the configure(WebSecurity Web) method, as follows:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**"."/js/**"."/index.html"."/img/**"."/fonts/**"."/favicon.ico"."/verifyCode");
}
Copy the code
The second way is to configure in the configure(HttpSecurity HTTP) method:
http.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
Copy the code
The biggest difference between the two methods is that the first method does not go through the Spring Security filter chain, while the second method goes through the Spring Security filter chain, where the request is allowed.
When we used Spring Security, some resources could be released in the first way without validation, such as static resources on front-end pages.
If some resources are allowed, you must use the second method, for example, login interface. As you know, the login interface also has to be exposed and can be accessed without a login, but we can’t expose the login interface in the first way. The login request has to go through the Spring Security filter chain because there are other things that need to be done in the process.
Next, I will use the login interface as an example to analyze the difference between using Spring Security filter chain.
2. Login request analysis
First of all, when we use Spring Security, there are two ways to obtain user login information after the user has logged in successfully:
SecurityContextHolder.getContext().getAuthentication()
- In the Controller method, add the Authentication parameter
In both cases, you can obtain the information of the current login user. Spring Security: How to dynamically update logged-in user information .
So the data that you’re getting in either of these methods is coming from SecurityContextHolder, which is essentially stored in a ThreadLocal, which thread is storing the data in it, Which thread can access it.
This creates a problem. When the user logs in successfully, the user user data is stored in the SecurityContextHolder (thread1), and the next request (thread2) to retrieve the user’s login information from the SecurityContextHolder, Only to find it impossible to get! Why? Because they’re not the same Thread.
But what about the fact that, normally, after a successful login with Spring Security, we can get the login user information every time?
That we’re going to introduce the Spring Security SecurityContextPersistenceFilter.
As you know, both Spring Security and Shiro use filters to perform a number of functions. In Spring Security, In front of the scene with you chatted UsernamePasswordAuthenticationFilter filter, before the filter, and a filter is SecurityContextPersistenceFilter, Request before reaching the UsernamePasswordAuthenticationFilter will pass SecurityContextPersistenceFilter first.
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()); }}}Copy the code
The original method was long, but here are the key parts:
- SecurityContextPersistenceFilter inherited from GenericFilterBean, GenericFilterBean is the realization of the Filter, So SecurityContextPersistenceFilter as a filter, it is inside the most important way to the doFilter.
- In doFilter method, it will first read a SecurityContext from the repo, the repo is actually HttpSessionSecurityContextRepository here, Read SecurityContext operations into readSecurityContextFromSession method, here we see the core method of reading
Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
The value of the springSecurityContextKey object is SPRING_SECURITY_CONTEXT, and the read object will eventually be turned into a SecurityContext object. - SecurityContext is an interface that has a unique implementation class, SecurityContextImpl, which is essentially the value of the user information stored in the session.
- After get SecurityContext, through SecurityContextHolder setContext method set the SecurityContext into ThreadLocal, so, in the current request, Following Spring Security operations, we can now retrieve user information directly from the SecurityContextHolder.
- Next, chain. DoFilter continues the request down (this is when it enters
UsernamePasswordAuthenticationFilter
Filter is in). - After the filter chain is finished and the data is responded to the front end, there is a crucial step in finally. So we get the SecurityContext from the SecurityContextHolder, and when we get it, we empty the SecurityContextHolder, We then call the repo.savecontext method to store the fetched SecurityContext into the session.
At this point, the process is clear.
When each request reaches the server, the SecurityContext is retrieved from the session and set to the SecurityContextHolder for subsequent use. When the request leaves, The SecurityContextHolder is emptied and the SecurityContext is put back into the session for the next request.
When the login request comes in, there is no login user data, but when the login request goes out, the user login data is stored in the session, and when the next request comes in, it can be directly retrieved and used.
Looking at the above analysis, we can draw at least two conclusions:
- If we expose the login interface using the first method mentioned above, and do not go through the Spring Security filter chain, the login user information will not be stored in the session after successful login. As a result, subsequent requests cannot obtain login user information (subsequent requests are also unauthenticated requests in the eyes of the system).
- If your login request is normal and goes through the Spring Security filter chain, but A subsequent request does not go through the filter chain (using the first method mentioned above), then A request will not be able to obtain the login user information through the SecurityContextHolder. Because it is the beginning without SecurityContextPersistenceFilter filter chain.
3. Summary
In short, the front-end static resource release can be done without the Spring Security filter chain, as follows:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**"."/js/**"."/index.html"."/img/**"."/fonts/**"."/favicon.ico");
}
Copy the code
If the back-end interface is to be released in an additional way, you need to carefully consider the scenario, but in general, this approach is not recommended. Instead, the following approach is recommended for the reasons mentioned above:
http.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
Copy the code
These are the two resource release strategies to share with your friends. Make no mistake
If you have a harvest, remember to point to the songge oh ~