Memo - Spring Security 4.x

Citations

Spring Security project

BlogSpring Security Authentication Architecture

BlogSpring Security Column

illustrate

This article is suitable for source code analysis of Spring Security. It is written in accordance with my own source code analysis process, and is explained along the source code analysis idea, so there will not be a lot of conclusive text (for the summary text, you can refer to other blog, you need to analyze and summarize by yourself), and also point the way for later viewing the source code to troubleshoot errors.

Here, the core classes of Spring Security will be introduced step by step according to the process of source code analysis. If you forget them later, you can remember them by viewing the source code in this order. IDEA shortcut keys are used, Ctrl-H (view the implementation class), Ctrl-Shift-B (view the implementation class) Implementation method), Ctrl+Alt+Left arrow key (Right arrow key) (return, advance to the last position) so as not to get lost in the sea of ​​source code. When reading the source code, use note down the connection between classes and draw a Figure (right brain memory association). Otherwise, you will feel sleepy when you look at it, lost in the code, and don't know what you are doing.

The source code analysis must be carried out after knowing the usage method and knowing some concepts. I can read a lot of tutorials on how to use Spring Security from various blogs before, but I don’t know much about the interface of the whole framework, and there are problems. Scratching your ears. At this time, you need to read the source code. Through the source code, you can understand what unknown tricks are constructed by the code you wrote.

core component class

Start with SecurityContextHolder

SecurityContextHolder holds SecurityContext according to certain strategies, these strategies are

  • ThreadLocalSecurityContextHolderStrategy: 保存在 ThreadLocal
  • InheritableThreadLocalSecurityContextHolderStrategy: Saved in ThreadLocal, but can be extended to child threads
  • GlobalSecurityContextHolderStrategy : Globally share a single SecurityContext instance, usually used in rich clients, such as Swing programs

SecurityContext means security context, which is used to obtain Authentication authentication information

Authentication is an abstract interface to represent identity information, used to save GrantedAuthority collection, Credentials login credentials, Details details, Principal identity, and to determine whether it has been authenticated isAuthenticated()

GrantedAuthority represents permission abstraction, and there is an implementation class that SimpleGrantedAuthority uses to represent the string role permission of ROLE_***

AbstractAuthenticationToken implements Authentication, which is used to represent the user entity information accessed, and then the Token information for custom authentication can be implemented by inheriting it.

What does AbstractAuthenticationToken do?

  • Construct an immutable GrantedAuthority collection, obviously the identity authority after each authentication should be unchanged
  • Erase sensitive information eraseCredentials()
  • Get the identity string name
  • equals comparison

Principal is an abstraction of identity, the default is UserDetail , if you want to extend it, implement Principal .

Let's take a look at the implementation classes of AbstractAuthenticationToken:

UsernamePasswordAuthenticationToken is used to implement username/password login form.

Observe the design of UsernamePasswordAuthenticationToken. It has two constructors, one will set authenticated to false, and the other will set authenticated to true after passing in the permission information. It will be observed later that an unauthenticated UsernamePasswordAuthenticationToken will be constructed by the filter when the user initially accesses Instance, after authentication, a UsernamePasswordAuthenticationToken instance containing permission information will be generated. Custom Tokens also need to follow this logic

AnonymousAuthenticationToken is used to represent anonymous users without credential information and can have permissions

Let's not look at the other Token implementation classes.

When looking at the source code, I found the UserDetail class, which is an unsafe class used to store user information/permissions, and the actual content will be dumped to Authentication.

UserDetail has an implementation class User that implements some auxiliary methods for constructing identity information and authorization information

There is a UserDetailsService under the same package, the core interface for loading (retrieving) user information, and a method loadUserByUsername to query the UserDetails instance by user name

Look at the implementation of the UserDetailsManager interface and add several methods of adding, deleting, modifying and checking.

Let's take a look at JdbcDaoImpl , JdbcUserDetailsManager , these two classes and User class, which should be the out-of-the-box database-based security information retrieval implementation providers provided by Spring Security.

Continue to observe that there are also CachingUserDetailsService and UserCache, which should be used in programs that do not maintain server-side information, such as remote clients or web services. UserCache needs to be implemented by itself.

InMemoryUserDetailsManager is a memory-based user information retrieval implementation, which is used in the example in the official text

AuthenticationManager is the core interface related to authentication and the starting point of all authentication. How to authenticate is implemented by AuthenticationProvider.

Take a look at its implementation class ProviderManager , this class contains:

  • AuthenticationEventPublisher Publisher of authentication success/failure events
  • AuthenticationProvider collection
  • a parent AuthenticationManager

Let's see what it does by implementing AuthenticationManager.authenticate(): iterate the provider set, call the provider's authenticate() and return if there is a result, otherwise call the parent AuthenticationManager's authenticate() logic and return with a result. If there is a result, the authentication is successful, it will Publish a successful authentication event, otherwise publish an authentication failure event and throw an exception

To summarize: AuthenticationManager's authentication is delegated to one or more instances of AuthenticationProvider that support AuthenticationAuthenticationToken

Take a look at one of its implementations, AbstractUserDetailsAuthenticationProvider , which is used to authenticate UsernamePasswordAuthenticationToken. See the authenticate() method, which delays the retrieval of UserDetails and the verification of credentials to subclass implementations.

The default implementation is DaoAuthenticationProvider, this class implements the authentication link of the identity certificate (can extend PasswordEncoder), and delegates the retrieval of UserDetails to the UserDetailsService implementation, which seems to be connected with the previous analysis.

To sort out: Taking UsernamePasswordAuthenticationToken as an example, AuthenticationManager calls AuthenticationProvider to perform user authentication according to the AuthenticationToken type. AuthenticationProvider delegates user information retrieval to UserDetailsService. Users only need to implement UserDetailsService to retrieve user information and permissions to integrate the services provided by Spring Security. Username and password based authentication system out of the box.

After seeing so much, what is the overall process like?

Well, there should be many arrows connected to the picture drawn from the source code. Review the process and go to the next section.

Spring Security's core filter class

For the source code analysis of the filter, it is recommended to use breakpoints. First write a demo , and then make a breakpoint in the doFilter method of a filter. Verify your own ideas.

The predecessors have studied it, and we can verify whether this is the case based on their research results. Just like the proof of 1+1=2, everyone knows that 1+1=2, and the argument is equal to 2.

The general process, taking form login as an example: the filter takes out the identity information from the session (if you have not logged in, you are anonymous), the security interceptor determines whether the identity can access resources, and if it can, pass it. If not, determine whether the identity is For anonymous, if it is an authenticated identity, throw an exception and return an insufficient permission, 403 or something. If it is an anonymous user, execute the access denied process, that is, redirect to the login page, submit the login information, and perform authentication Process. At this time, your identity is authenticated, and the session is stored, and then redirected to the resource page to be accessed. Then it is judged by the security interceptor.

Look at the output of Spring-boot's log ossweb.DefaultSecurityFilterChain to find those filters that have the security chain loaded:

Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1
[
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@462e1e64
org.springframework.security.web.context.SecurityContextPersistenceFilter@593a6726
org.springframework.security.web.header.HeaderWriterFilter@14f40030
org.springframework.security.web.csrf.CsrfFilter@1cdc1bbc
org.springframework.security.web.authentication.logout.LogoutFilter@36327cec
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@2121d1f9
inaction.ss.security.ipLogin.IpAuthenticationProcessingFilter@69afa141
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@596a7f44
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@63a28987
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@49f40c00
org.springframework.security.web.session.SessionManagementFilter@4c447c09
org.springframework.security.web.access.ExceptionTranslationFilter@43c87306
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2ab26378
]

For related analysis details, please refer to this blog Spring Security (4)--Core Filter Source Code Analysis

Core filter description:

  • SecurityContextPersistenceFilter: Responsible for loading SecurityContext to SecurityContextHolder when requesting, and cleaning up SecurityContext when the request ends. The specific storage acquisition is delegated to SecurityContextRepository for processing. The default here is HttpSessionSecurityContextRepository to obtain SecurityContext from Session. If your own program also needs to obtain SecurityContext, do not operate session directly, but is to use SecurityContextHolder.
  • UsernamePasswordAuthenticationFilter: A filter for form login authentication. If the request is for a login page, this filter will construct a Token and call AuthenticationManager for authentication. After successful authentication, the authentication information will be stored in the SecurityContextHolder. The specific authentication process is reviewed in the figure drawn in the previous section .
  • AnonymousAuthenticationFilter : Pay attention to its position in the filter chain, it is located after the login filter, that is to say, if all authentication filters are passed and there is still no identity, an anonymous identity is granted.
  • ExceptionTranslationFilter: Responsible for converting the exception thrown by the filter after it. There are mainly two types of AccessDeniedException access exception and AuthenticationException authentication exception. Authentication exception or anonymous user access exception will directly execute AuthenticationEntryPoint (jump to the login page). If it is an authenticated user The access exception will call AccessDeniedHandler to handle. AccessDeniedHandler has two processing methods, one is to jump to the error page, the other is as if the error status code.
  • FilterSecurityInterceptor: Responsible for authorization, that is, access decision management. AccessDecisionManager will be called to determine whether to access or throw AccessDeniedException

Filters are done, let's see how these filters are added to the filter chain

Our own implementation of SecurityConfig inherits from WebSecurityConfigurerAdapter, there will be an annotation @EnableWebSecurity

EnableWebSecurity imports WebSecurityConfiguration, SpringWebMvcImportSelector, and the annotation @EnableGlobalAuthentication imports AuthenticationConfiguration. So we need to analyze what the above three classes do

@EnableGlobalAuthentication 和 @EnableGlobalMethodSecurity

Mainly to enable method-level annotation security configuration, based on AOP proxy, refer to the official document Method Security

SpringWebMvcImportSelector

Used to determine whether the WebMvcSecurityConfiguration configuration needs to be loaded, see the class annotation for details

WebSecurityConfiguration

Locate the method springSecurityFilterChain(), which is to set the security filter chain, and setFilterChainProxySecurityConfigurer, which is used to load the security configuration SecurityConfigurer. Analyze webSecurity.build(), which is to call the configure operation once for each SecurityConfigurer in the SecurityConfigurer collection

WebSecurity inherits AbstractConfiguredSecurityBuilder, look at the doBuild method

见: AbstractConfiguredSecurityBuilder

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

The method in the demo is actually to add a series of SecurityConfigurer

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/index").permitAll()
                .antMatchers("/login/**").permitAll()
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                //默认使用表单登录器
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll()
        ;
    }

Let's take a look at the specific implementation classes of SecurityConfigurer:

Enter image description

You can see the configurator added by default in the getHttp() method of WebSecurityConfigurerAdapter

Pick an ExceptionHandlingConfigurer and see how it adds filters

@Override
	public void configure(H http) throws Exception {
		AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
		ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
				entryPoint, getRequestCache(http));
		if (accessDeniedHandler != null) {
			exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
		}
		exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
		http.addFilter(exceptionTranslationFilter); //在这里
	}

Seeing this, it should be clear how Spring Security builds a security filter chain step by step through configuration.

The process analysis is clear, and it is very convenient to understand how to configure with the filter as the core.

Tips: How to quickly find the corresponding configuration of a security filter? Take SecurityContextPersistenceFilter as an example, enter SecurityContextPersistenceFilter.java in IDEA, select the class name, and then Ctrl+F7 (find the used class) As shown in the figure: Enter image descriptionThen select the SecurityContextConfigurer class Ctrl+ F7, jump to the configuration method, then execute if you want to configure SecurityContextRepository

...
.and()
            .securityContext().securityContextRepository(repo)

At this point, the overall security configuration process is over. After identity authentication is authority control, and the next step begins to analyze authority decision management.

AccessDecisionManager access decision management

Authorization Model Based on Voting Policy

Source code analysis: Start with FilterSecurityInterceptor

FilterSecurityInterceptor implements AbstractSecurityInterceptor

What does AbstractSecurityInterceptor do? The core operation is to call AccessDecisionManager.decide(Authentication) in beforeInvocation for authorization determination

AccessDecisionManager Access Decision Manager

AbstractAccessDecisionManager extends AccessDecisionManager , let's see what it holds:

  • AccessDecisionVoter collection access decision voter

AccessDecisionVoter defines three kinds of tickets:

  • ACCESS_GRANTED agree
  • ACCESS_DENIED means deny
  • ACCESS_ABSTAIN means abstain

Lower text quote from http://elim.iteye.com/blog/2247057

Spring Security内置了三个基于投票的AccessDecisionManager实现类,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased。

       AffirmativeBased的逻辑是这样的:

       (1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;

       (2)如果全部弃权也表示通过;

       (3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。

       ConsensusBased的逻辑是这样的:

       (1)如果赞成票多于反对票则表示通过。

       (2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。

       (3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true, 
         则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。

       (4)如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,
         如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

       UnanimousBased的逻辑与另外两种实现有点不一样,   
       另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter进行投票,
       而UnanimousBased会一次只传递一个ConfigAttribute给AccessDecisionVoter进行投票。
       这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,
       但是放到UnanimousBased中其投票结果就不一定是赞成了。UnanimousBased的逻辑具体来说是这样的:

       (1)如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException。

       (2)如果没有反对票,但是有赞成票,则表示通过。

       (3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出AccessDeniedException。

Spring Security uses AffirmativeBased decisions by default

RoleVoter judges the ConfigAttribute that starts with ROLE_ if it contains a role, it passes, otherwise it rejects. If there is no attribute starting with ROLE_, it abstains

RoleHierarchyVoter Role inheritance voter. If role A inherits role B, then role A has the permissions of role B. Use RoleHierarchy to build a role inheritance tree, use A > B to indicate that A inherits B

           <property name="hierarchy">
               <value>
                   ROLE_A > ROLE_B
                   ROLE_B > ROLE_AUTHENTICATED
                   ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
               </value>
           </property>

AuthenticatedVoter is mainly used to distinguish anonymous users, users authenticated by Remember-Me, and users who are fully authenticated. A fully authenticated user refers to a user who has successfully logged in and authenticated by the login portal provided by the system.

AfterInvocationManager invokes the post-decision maker, and performs permission determination after the AccessDecisionManager passes.

AfterInvocationManager delegates AfterInvocationProvider to determine

AfterInvocationManager is registered when @EnableGlobalMethodSecurity(prePostEnabled = true) is enabled

protected AfterInvocationManager afterInvocationManager() {
		if (prePostEnabled()) {
			AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
			ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(
					getExpressionHandler());
			PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(
					postAdvice);
			List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<AfterInvocationProvider>();
			afterInvocationProviders.add(postInvocationAdviceProvider);
			invocationProviderManager.setProviders(afterInvocationProviders);
			return invocationProviderManager;
		}
		return null;
	}

end

Reference a very important content WebSecurityConfigurerAdapter configuration scope related introduction

Multiple WebSecurityConfigurerAdapter configuration example and code https://gitee.com/zhwcong/inaction/tree/master/spring_secutiry

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325337947&siteId=291194637