Strange, Spring Security after a successful login can not always get the user login information?

There are quite a few small partner junior partner had Song Ge help this problem.

At first I thought it might be a small probability BUG, ​​but when asked many people, I think this problem for the novice as well as a certain universality, it is necessary to write articles chat with you this question carefully, prevent small partners out of the pit.

1. reproduce the problem

If you are using Spring Security, when we log on, you can get the currently logged in user information through the following methods:

  1. SecurityContextHolder.getContext().getAuthentication()
  2. In the method of the Controller added Authentication parameter

Both approaches can obtain the current logged-on user information. Specific operational methods, we can look at the tutorial before the release of Song Ge: how Spring Security dynamically update user login information? .

Under normal circumstances, you can get us a message to the user has logged in by any of the above two ways.

Abnormal situation in which any one of these two ways, returns null.

Return null, means that the system received does not know you are logged in (because you did not leave any useful information in the system), this has two problems when the current request:

  1. Can not get to the currently logged on user information.
  2. When you send any request, the system will give you 401 return.

2. fiddling

To understand this issue, we have to understand that in the end is where the user information stored in Spring Security?

We said earlier data two data acquisition mode, but the two data acquisition mode, acquired and where it came from?

First Song Ge and we talked before, the data in SecurityContextHolder, in essence, is stored in ThreadLocal, the ThreadLocalfeature is the presence of data inside it, which thread of deposit, which thread to access to.

This creates a problem, when different request comes to the server, to deal with a different thread, the latter stands to reason that a request may not be able to get the data into the login request of the thread, such as a login request in the thread A the logged-on user information stored in ThreadLocalthe back of the request, the processing thread B, and that this time will not be able to get the user's login information.

But in fact, under normal circumstances, every time we are able to get to the login user information, this is how it happened?

We have introduced this Spring Security in SecurityContextPersistenceFilterthe.

Little friends all know, whether it is Spring Security or Shiro, it's a series of functions are actually done by the filter in Spring Security, the front Song Ge talk with you UsernamePasswordAuthenticationFilterfilter before the filter, also there is a filter SecurityContextPersistenceFilter, a request arrives UsernamePasswordAuthenticationFilterbefore will go through SecurityContextPersistenceFilter.

We 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 list here the more critical parts:

  1. SecurityContextPersistenceFilter inherited from GenericFilterBean, and GenericFilterBean is achieved Filter, so SecurityContextPersistenceFilter as a filter, the most important way it is doFilter the inside.
  2. In doFilter method, it first reads in a SecurityContext out from repo, repo here is actually HttpSessionSecurityContextRepository, SecurityContext read operation will enter into readSecurityContextFromSession approach, where we see the core methods of reading Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);, here springSecurityContextKey object's value is SPRING_SECURITY_CONTEXT, read out the object will eventually be converted to a SecurityContext object.
  3. SecurityContext is an interface, it has a unique implementation class SecurityContextImpl, the implementation class is actually the user information stored in the session value.
  4. After get SecurityContext, by this method SecurityContextHolder.setContext SecurityContext ThreadLocal disposed to go, so that, in the current request, the subsequent operation of Spring Security, we can directly obtain information from the user to the SecurityContextHolder.
  5. Next, make a request by chain.doFilter continue to go down (this time will enter into UsernamePasswordAuthenticationFilterthe filter a).
  6. After completed after the filter chain, the response data to the front end, the finally finishing operation in another step, this step is very critical. To get here from the SecurityContextHolder SecurityContext, after obtaining, will SecurityContextHolder empty, then call repo.saveContext method to get to the SecurityContext into the session.

At this point, the whole process is very clear.

Each request to reach the server, the first thing to find from the session out of the SecurityContext, and then provided to the SecurityContextHolder to facilitate subsequent use, when the request to leave, SecurityContextHolder will be cleared, the SecurityContext will be put back in the session, conveniently a when the acquisition request.

After thoroughly understand it, can not get to go to solve this problem after the currently logged on user login Spring Security, it is a very easy.

3. Problem Solving

After the above analysis, let's look at why the login happens after the current user can not obtain information about such a thing?

The simplest case is that you're in a new thread to execute SecurityContextHolder.getContext().getAuthentication(), it is certainly not obtain user information, needless to say. Such as the following:

@GetMapping("/menu")
public List<Menu> getMenusByHrId() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            System.out.println(authentication);
        }
    }).start();
    return menuService.getMenusByHrId();
}

This simple question I believe we are able to easily troubleshoot.

There is also a hidden deeper in the session is loaded into the user information, which led to SecurityContextHolder inside empty in doFilter method SecurityContextPersistenceFilter's not from.

In SecurityContextPersistenceFilter failed to load into the user information, the cause may be more and more, such as:

  • Leaving a request when there is no data stored in the session to go.
  • The current request did not come himself filter chain.

When this problem happens then? Some partners may be small when configuring SecurityConfig # configure (WebSecurity) method ignores an important point.

When we want Spring Security resources can be anonymous access, we have two options:

  1. Do not take the Spring Security filter chain.
  2. Continue to take the Spring Security filter chain, but anonymous access.

These two approaches correspond to two different configurations. The first configuration which may affect us for the logged-on user information, and the second is not affected, so here we look at the first focus.

Spring Security filter chain does not walk, we can generally arranged by:

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

Normally this configuration is not a problem.

If you're Unfortunately, the logon request to the address put, it is the gg. Although the login request can be accessed by everyone, but can not be placed here (and by the way it should be allowed anonymous access to the requesting release). If you go here, go logon request will not SecurityContextPersistenceFilterfilter, which means not logged-in user information into the session, which led to subsequent requests can not get to the login user information.

This is the beginning of problems encountered by small partners.

Well, little friends if you encounter similar problems when using Spring Security, provided herein may wish to ideas to resolve it. If you feel rewarding, remember to tap in the lower right corner Kane

Published 571 original articles · won praise 6801 · Views 4.7 million +

Guess you like

Origin blog.csdn.net/u012702547/article/details/105237938
Recommended