Base classes/schema for Spring Security authentication

Table of contents

1. SecurityContextHolder core class

2. SecurityContext interface

3. Authentication user authentication information interface

4. GrantedAuthority has permission interface

5. AuthenticationManager Identity Authentication Manager Interface

6. Implementation of ProviderManager Identity Authentication Manager

7. AuthenticationProvider specific type of authentication interface

8. AuthenticationEntryPoint authentication scheme interface

9. AbstractAuthenticationProcessingFilter user credential verification filter


        // Spring Security Servlet-based authentication and basic building blocks for authentication

  • SecurityContextHolder (class) - Used to store authentication details . //Verification process core class
  • SecurityContext (interface) - Obtained from SecurityContextHolder, containing the authentication information of the currently authenticated user .
  • Authentication (interface)  - This object can be assigned by AuthenticationManager as the credentials of user authentication , and then get the detailed information of Authentication by SecurityContext.
  • GrantedAuthority (interface) – used to authorize users .
  • AuthenticationManager (interface) – API for defining how Spring Security filters perform authentication .
  • ProviderManager (class) – the most common implementation of AuthenticationManager. //regular implementation
  • AuthenticationProvider (interface) – Provided to the ProviderManager to perform a specific type of authentication . //The specific details of authentication are provided by xxxProvider
  • AuthenticationEntryPoint (interface) - used to request user credentials from the client (eg, redirect to login page, send WWW-Authenticate response, etc.)
  • AbstractAuthenticationProcessingFilter (abstract class) – Basic filter for authentication . This class is a good way to understand the authentication process and how the various pieces work together. //All authentication filters will implement this class

1. SecurityContextHolder core class

        The SecurityContextHolder class is the core of the Spring Security authentication model , which contains the context SecurityContext that stores user authentication information.

        The following is a containment diagram between objects, which is very important and very useful for understanding object relationships in Spring Security:

        SecurityContextHolder is used to store authentication details. Spring Security doesn't care how the SecurityContextHolder is populated. If it contains a value, use that value as the currently authenticated user information. //As long as the SecurityContextHolder has a value, it will be used

        So, the easiest way to indicate that the user is authenticated is to set the SecurityContextHolder directly:

SecurityContext context = SecurityContextHolder.createEmptyContext(); 

//1-创建Authentication -> Token接口
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
//2-设置用户信息
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 

        Use TestingAuthenticationToken because it is very simple. You can also use UsernamePasswordAuthenticationToken(UserDetails, Password, Permissions) , etc.

         // Define an Authentication -> Put into SecurityContext -> Put into SecurityContextHolder

After setting  the SecurityContext         on the SecurityContextHolder . Spring Security will use this information for authorization. If you want to get information about authenticated users, you can get it through SecurityContextHolder :

//1-获取Security上下文
SecurityContext context = SecurityContextHolder.getContext();

//2-获取用户通过身份验证的对象
Authentication authentication = context.getAuthentication();

//-通过Authentication获取用户名称(密码方式)
String username = authentication.getName(); 
//-通过Authentication被认证用户的身份信息。
Object principal = authentication.getPrincipal(); 
//-通过Authentication用户权限信息
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

        By default, SecurityContextHolder uses ThreadLocal to store  SecurityContext , which means that  SecurityContext  is always available to methods in the same thread, and there is no need to explicitly pass SecurityContext  as a parameter to these methods. // SecurityContext  is private to the thread, if we don't want to display the parameters, we can also use it like this

    //包含SecurityContext的成员变量
    private static SecurityContextHolderStrategy strategy; 

    //初始化方法
    private static void initialize() {
        //1-如果没有指定mode,使用MODE_THREADLOCAL
        if (!StringUtils.hasText(strategyName)) { 
            strategyName = "MODE_THREADLOCAL";
        }

        if (strategyName.equals("MODE_THREADLOCAL")) {
            //2-默认策略
            strategy = new ThreadLocalSecurityContextHolderStrategy(); 
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }
        ++initializeCount;
    }

    // MODE_THREADLOCAL策略
    final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

        // 使用ThreadLocal保存SecurityContext
        private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();

        ThreadLocalSecurityContextHolderStrategy() {
        }
        //省略...
    }

Because ThreadLocal          is used  to store values, if the current user request is processed, the ThreadLocal in the thread needs to be cleared to avoid memory leaks. Spring Security 's FilterChainProxy will ensure that the SecurityContext is always cleared . // FilterChainProxy is the general agent of a filter chain, mentioned in the article on the overall architecture

    //在FilterChainProxy中的doFilter方法中清除
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
            this.doFilterInternal(request, response, chain);
        } else {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } catch (RequestRejectedException var9) {
                this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
            } finally {
                //处理完一个用户请求时,SecurityContext在这里被清除
                SecurityContextHolder.clearContext();   
                request.removeAttribute(FILTER_APPLIED);
            }
        }
    }

2. SecurityContext interface

        SecurityContext is obtained from SecurityContextHolder. SecurityContext contains an Authentication object .

// SecurityContext接口
public interface SecurityContext extends Serializable {

    //接口很简单,只包含对Authentication的get和set方法
    Authentication getAuthentication();

    void setAuthentication(Authentication var1);
}

        SecurityContext's implementation class SecurityContextImpl is also very simple, that is,  the implementation of the get and set methods of the Authentication object, so the focus is on  the Authentication object.

3. Authentication user authentication information interface

        In Spring Security, the Authentication interface has two main purposes:

  1. As an input parameter of the authenticate method of the AuthenticationManager object, it is used to authenticate the user. In this scenario, isAuthenticated() returns false.
  2. Get the authenticated user information, you can get the current user information from SecurityContext.

        Once the request passes AuthenticationManager #authenticate , Authentication is usually stored in the thread-local SecurityContext and managed by SecurityContextHolder. //Authentication->SecurityContext

        Note that unless Authentication has the isAuthenticated property set to true, it will still be authenticated by the security interceptor.

        Authentication interface details: //The comments of each method are worth reading carefully

public interface Authentication extends Principal, Serializable {

    //1-权限:由AuthenticationManager设置,用于指示已授予主体的权限。认证后不能为空(即无权限)
    Collection<? extends GrantedAuthority> getAuthorities();

    //2-密码:证明委托人正确的凭据。通常是密码
    Object getCredentials();

    //3-用户详情:存储有关身份验证请求的其他详细信息。如IP地址,证书序列号等
    Object getDetails();

    //4-用户名:被认证主体的身份。通常是用户名,许多身份验证提供程序将创建UserDetails对象作为主体
    Object getPrincipal();

    //用于指示AbstractSecurityInterceptor是否应该向AuthenticationManager提供身份验证令牌。
    //如果令牌已经过身份验证,并且AbstractSecurityInterceptor不需要再次向AuthenticationManager提供令牌以进行重新身份验证,则为true。
    boolean isAuthenticated();

    //如果令牌应该被信任(这可能导致异常),则为true;如果令牌不应该被信任,则为false
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

        Therefore, based on the above, Authentication includes three aspects of information: //Summary is: user information + permissions

  1. Principal : User principal information, which is usually an instance of UserDetails.
  2. Credentials: authentication credentials, usually a password. In many cases, this property is cleared by the ProviderManager after the user is authenticated to ensure that the credentials are not leaked.
  3. Authorities : User permissions, granted by GrantedAuthority.

4. GrantedAuthority has permission interface

        Represents the permissions granted to the authentication object . The content of this interface is provided to the AuthorizationManager to use to determine whether an authentication is authorized to access a particular object. //Save the user role

//GrantedAuthority接口
public interface GrantedAuthority extends Serializable {
    String getAuthority(); //返回的是一个字符串 String role
}

        //  So, GrantedAuthority, this thing is used for authorization, and stores the access rights that authenticated users have.

        An instance of GrantedAuthority can be obtained through the Authentication.getAuthorities() method. This method provides a collection of GrantedAuthority objects. GrantedAuthority is, of course, the authority granted to the user. These permissions are usually " role " information, such as ROLE_ADMINISTRATOR or ROLE_HR_SUPERVISOR . These roles will be configured for use with Web Authorization, Method Authorization and Domain Object Authorization. GrantedAuthority instances are typically loaded by UserDetailsService when username/password based authentication is used .

        Typically, a GrantedAuthority object is an application-wide authority. They are not specific to a given domain object. Therefore, it is unlikely to use a GrantedAuthority to represent a specific authority on an object, because if there are thousands of such authorities, it will quickly exhaust memory (or, cause the application to take a long time to authenticate). // It means that the authority in GrantedAuthority is a type of authority (role), rather than a specific function. For example, administrator authority can operate all modules, and there may be hundreds of functions of all modules together .

5. AuthenticationManager Identity Authentication Manager Interface

        The AuthenticationManager API defines how Spring Security's filters perform authentication. Used to handle authentication requests .

public interface AuthenticationManager {

    //尝试对传递的身份验证对象进行身份验证,
    //如果成功,则返回一个完全填充的身份验证对象(包括授予的权限)。
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

        The Filters instance of Spring Security will call the AuthenticationManager to obtain the Authentication object set on the SecurityContextHolder for authentication.

        If you set SecurityContextHolder directly, you don't need to use AuthenticationManager. // Fill in the identity information directly, skip the authentication step, so you can see that all the authentication processes are just to set an Authentication in the SecurityContextHolder

        AuthenticationManager can have many implementations, the most common implementation is ProviderManager .

6. Implementation of ProviderManager Identity Authentication Manager

        ProviderManager is the most commonly used implementation of AuthenticationManager. The ProviderManager delegates authentication of users to a list of AuthenticationProvider instances.

//具体进行认证操作的实例列表
private List<AuthenticationProvider> providers;

        Each AuthenticationProvider has the opportunity to indicate that authentication should have succeeded, failed, or indicated that it was unable to make a decision, allowing downstream AuthenticationProviders to make their decisions.

        If none of the configured AuthenticationProvider instances can perform authentication, the authentication fails and a ProviderNotFoundException is generated , which is a special AuthenticationException indicating that the ProviderManager does not configure a Provider  type that supports authentication of this Authentication object  .

        In practice, each AuthenticationProvider performs a specific type of authentication . For example, one AuthenticationProvider might be able to verify username/password, while another AuthenticationProvider might be able to verify SAML assertions. Spring Security supports multiple authentication types at the same time, and only exposes a common AuthenticationManager Bean . // Filter corresponding to various functions

        ProviderManager also allows configuring a parent AuthenticationManager (optional) , which is used when no AuthenticationProvider can perform authentication. The superclass can be any type of AuthenticationManager , usually a ProviderManager instance as well.

        Multiple ProviderManager instances can share the same parent AuthenticationManager . This approach is more common in scenarios where there are multiple SecurityFilterChains that have some common authentication (shared parent AuthenticationManager), but also have different authentication mechanisms (different ProviderManager instances).

        By default, the ProviderManager attempts to clear sensitive credential information from the Authentication object returned by a successful authentication request, preventing information such as passwords from being retained in the HttpSession for longer than required. // After the Authentication authentication is successful, the ProviderManager will clear the authentication credentials

        // Because ProviderManager will clear Authentication authentication credentials, you should pay attention to this problem when caching Authentication information.

        Two solutions to caching issues : create a copy of the Authentication object or disable the eraseCredentialsAfterAuthentication property on  the ProviderManager .

        [ ProviderManager.authenticate  source code analysis]:

//执行认证的AuthenticationProvider列表
private List<AuthenticationProvider> providers;  

//认证流程
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        //1-获取Provider列表迭代器
        Iterator var9 = this.getProviders().iterator(); 

        //2-开始循环遍历Provider列表
        while(var9.hasNext()) { 
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {

                //省略...

                try {
                    //3-使用其中一个provider进行身份验证
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        //省略...(这里省略了付实例的认证,也很简单,可查看源码)

        //4-如果认证成功,在这里擦除密码/认证凭据等信息
        if (result != null) { 
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials(); //擦除认证凭据
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            //5-抛出providerNotFound异常
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

7. AuthenticationProvider specific type of authentication interface

        Unified interface for handling specific authentication.

Typically multiple AuthenticationProvider instances         can be injected into a ProviderManager . Each AuthenticationProvider performs a specific type of authentication . For example, DaoAuthenticationProvider supports username/password based authentication, while JwtAuthenticationProvider supports validating JWT tokens.

public interface AuthenticationProvider {
    //执行身份验证
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    //如果此AuthenticationProvider支持指定的Authentication对象,则返回true。
    boolean supports(Class<?> var1);
}

8. AuthenticationEntryPoint authentication scheme interface

        AuthenticationEntryPoint role: used by ExceptionTranslationFilter to start an authentication scheme .

public interface AuthenticationEntryPoint {
    //启动认证方案
    void commence(HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException;
}

        The ExceptionTranslationFilter will populate the HttpSession property of the AbstractAuthenticationProcessingFilter . Use the requested target URL before calling this method .

The implementation modifies the headers on the ServletResponse         as needed to initiate the authentication process. Implementations of AuthenticationEntryPoint may perform redirection to a login page , respond with a WWW-Authenticate header, or take other actions.

9. AbstractAuthenticationProcessingFilter user credential verification filter

        AbstractAuthenticationProcessingFilter  is used as the base filter for authenticating user credentials. Spring Security will normally use an AuthenticationEntryPoint to request credentials before authenticating them .

        [ AbstractAuthenticationProcessingFilter. doFilter source code] //General process

    //身份认证过滤器的处理流程
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            try {
                //1- 尝试进行身份验证
                Authentication authenticationResult = this.attemptAuthentication(request, response);
                if (authenticationResult == null) {
                    return;
                }
                //2-如果验证成功,创建新会话,更新sessionId
                this.sessionStrategy.onAuthentication(authenticationResult, request, response);
                if (this.continueChainBeforeSuccessfulAuthentication) {
                    chain.doFilter(request, response);
                }
                //3-调用认证成功后处理流程
                this.successfulAuthentication(request, response, chain, authenticationResult);
            } catch (InternalAuthenticationServiceException var5) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
                //4-调用认证失败后处理流程
                this.unsuccessfulAuthentication(request, response, var5);
            } catch (AuthenticationException var6) {
                //4-调用认证失败后处理流程
                this.unsuccessfulAuthentication(request, response, var6);
            }
        }
    }

        Next, the AbstractAuthenticationProcessingFilter can authenticate any authentication request submitted to it .

    //过滤器中尝试进行身份验证的抽象方法
    public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

    //一个具体实现类:UsernamePasswordAuthenticationFilter的实现逻辑
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username : "";
            username = username.trim();
            String password = this.obtainPassword(request);
            password = password != null ? password : "";

            //使用用户名和密码创建一个Authentication对象
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);

            //调用特定的认证管理器进行认证,通常为(ProviderManager)
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

        Flow diagram of AbstractAuthenticationProcessingFilter for authentication:

        When a user submits credentials, the AbstractAuthenticationProcessingFilter  creates an Authentication object from the HttpServletRequest to be authenticated . The type of authentication created depends on the subclass of AbstractAuthenticationProcessingFilter  . For example, UsernamePasswordAuthenticationFilter creates UsernamePasswordAuthenticationToken from username and password submitted in HttpServletRequest . //Look at the source code display above

        Next,  pass the Authentication object  into the AuthenticationManager for authentication.

        If the authentication fails, execute the Failure process: //failure process, there are many extension points

        (1) Call the SecurityContextHolder.clearContext method to clear the Security context. //ThreadLocal. remove

        (2) Call the RememberMeServices.loginFail method. There are two implementation classes NullRememberMe and RememberMe. //RememberMe will invalidate the cookie

        (3) Call the AuthenticationFailureHandler.onAuthenticationFailure method. //Throw an error message or redirect

        AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication 源码】

    //认证失败处理流程
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {

        //1-清除SecurityContextHolder
        SecurityContextHolder.clearContext();

        this.logger.trace("Failed to process authentication request", failed);
        this.logger.trace("Cleared SecurityContextHolder");
        this.logger.trace("Handling authentication failure");

        //2-调用RememberMeServices.loginFail
        this.rememberMeServices.loginFail(request, response);

        //3-调用AuthenticationFailureHandler
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }

        If the authentication is successful, execute the Success flow:

        (1) SessionAuthenticationStrategy.onAuthentication  will receive a new login notification.

        (2)  Set the authenticated user information in the SecurityContextHolder for subsequent access.

        (3) Call  the RememberMeServices.loginSuccess  method. //Through this method, you can customize the processing method after success.

        (4) Call the ApplicationEventPublisher.publishEvent method to publish an InteractiveAuthenticationSuccessEvent event. //The user can do further operations by listening to this event

        (5) Call  the AuthenticationSuccessHandler.onAuthenticationSuccess method. // Handle url redirection, etc.

        【AbstractAuthenticationProcessingFilter.successfulAuthentication 源码】

    //认证成功处理流程
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

        //1-在 SecurityContextHolder 中设置通过认证的用户信息,方便后续取用
        SecurityContextHolder.getContext().setAuthentication(authResult);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }

        //2-调用 RememberMeServices.loginSuccess 方法
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {

            //3-发布认证成功事件
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        //4-调用AuthenticationSuccessHandler.onAuthenticationSuccess方法
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

        Finally, familiarity with the user's authentication process is very important for proficient use of Spring Security. Need to learn more, more summary.

        At this point, the full text ends here.

Guess you like

Origin blog.csdn.net/swadian2008/article/details/131771696