Spring Security Authentication 官方文档分析

Spring Security Authentication 源码分析

1. Authentication

  *Spring Security provides comprehensive support for [Authentication](https://docs.spring.io/spring-security/reference/features/authentication/index.html#authentication). We start by discussing the overall [Servlet Authentication Architecture](https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html). As you might expect, this section is more abstract describing the architecture without much discussion on how it applies to concrete flows.*

2. Authentication Mechanisms

3. Servlet Authentication Architecture

This discussion expands on Servlet Security: The Big Picture to describe the main architectural components of Spring Security’s used in Servlet authentication. If you need concrete flows that explain how these pieces fit together, look at the Authentication Mechanism specific sections.

基于 servlet版本 Security做讨论(还有 webflux版本), 描述在 servlet认证中的主要的 spring security的组件架构。

组件如下:

TermDefinitionTranslate
SecurityContextHolderStores the details of who is authenticated in Spring Security.存储认证信息
SecurityContextObtained from the SecurityContextHolder, it contains the Authentication of the currently authenticated user.SecurityContextHolder中获取,包含当前认证用户的身份验证信息。
AuthenticationCan be the input to AuthenticationManager to provide the credentials a user has provided to authenticate or the current user from the SecurityContext.可以作为AuthenticationManager的输入,提供用户提供的凭据进行身份验证,或从SecurityContext获取当前用户。
GrantedAuthorityAn authority granted to the principal on the Authentication, such as roles, scopes, etc.在Authentication上授予给主体的权限,如角色、范围等。
AuthenticationManagerAPI that defines how Spring Security’s filters perform authentication.定义Spring Security过滤器如何执行身份验证的API。
ProviderManagerThe most common implementation of AuthenticationManager.AuthenticationManager的最常见实现。
AuthenticationProviderUsed by ProviderManager to perform a specific type of authentication.ProviderManager 用于执行特定类型身份验证的身份验证提供程序。
Request CredentialsUsed for requesting credentials from a client, such as redirecting to a log in page, sending a WWW-Authenticate response, etc., with AuthenticationEntryPoint.用于向客户端请求凭据,如重定向到登录页面、发送WWW-Authenticate响应等,与AuthenticationEntryPoint一起使用。
AbstractAuthenticationProcessingFilterA base Filter used for authentication. This also gives a good idea of the high-level flow of authentication and how pieces work together in Spring Security.用于身份验证的基本过滤器。 这也很好地说明了Spring Security中的身份验证高级流程以及如何组合各个部分。

4. SecurityContextHolder

At the heart of Spring Security’s authentication model is the SecurityContextHolder. It contains the SecurityContext.

身份认证的核心,包含了 SecurityContext.

在这里插入图片描述

The SecurityContextHolder is where Spring Security stores the details of who is authenticated. Spring Security does not care how the SecurityContextHolder is populated. If it contains a value, it is used as the currently authenticated user.

SecurityContextHolder 储存用户认证信息的地方,有值的话就当做当前用户的认证信息

The simplest way to indicate a user is authenticated is to set the SecurityContextHolder directly:

// 当前线程里面拿一个上下文
SecurityContext context = SecurityContextHolder.createEmptyContext(); 
// 认证信息
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);
// 上下文及认证信息绑定
SecurityContextHolder.setContext(context); 

To obtain information about the authenticated principal, access the SecurityContextHolder.

// 拿到获取到的上下文信息
SecurityContext context = SecurityContextHolder.getContext();
// 获取到线程绑定认证信息
Authentication authentication = context.getAuthentication();
// 获取用户认证信息
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  *By default, `SecurityContextHolder` uses a `ThreadLocal` to store these details, which means that the `SecurityContext` is always available to methods in the same thread, even if the `SecurityContext` is not explicitly passed around as an argument to those methods. Using a `ThreadLocal` in this way is quite safe if you take care to clear the thread after the present principal’s request is processed. Spring Security’s [FilterChainProxy](https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-filterchainproxy) ensures that the `SecurityContext` is always cleared.*

默认 SecurityContextHolder 使用 ThreadLocal 来存储,这意味着 SecurityContext 总是对同一线程中的方法可用,即使 SecurityContext 没有显式地作为参数传递给这些方法。如果你注意在当前主体的请求处理后清除线程,以这种方式使用’ ThreadLocal ‘是相当安全的。Spring Security的FilterChainProxy确保’ SecurityContext '总是被清除。

  *Some applications are not entirely suitable for using a `ThreadLocal`, because of the specific way they work with threads. For example, a Swing client might want all threads in a Java Virtual Machine to use the same security context. You can configure `SecurityContextHolder` with a strategy on startup to specify how you would like the context to be stored. For a standalone application, you would use the `SecurityContextHolder.MODE_GLOBAL` strategy. Other applications might want to have threads spawned by the secure thread also assume the same security identity. You can achieve this by using `SecurityContextHolder.MODE_INHERITABLETHREADLOCAL`. You can change the mode from the default `SecurityContextHolder.MODE_THREADLOCAL` in two ways. The first is to set a system property. The second is to call a static method on `SecurityContextHolder`. Most applications need not change from the default. However, if you do, take a look at the JavaDoc for `SecurityContextHolder` to learn more.*

一些应用程序并不完全适合使用’ ThreadLocal ‘,因为它们使用线程的特定方式。例如,Swing客户机可能希望Java虚拟机中的所有线程使用相同的安全上下文。您可以在启动时使用策略配置“SecurityContextHolder”,以指定您希望如何存储上下文。对于一个独立的应用程序,您将使用“SecurityContextHolder”。MODE_GLOBAL”战略。其他应用程序可能希望由安全线程生成的线程也具有相同的安全标识。你可以通过使用’ SecurityContextHolder.MODE_INHERITABLETHREADLOCAL '来实现这一点。您可以从默认的“SecurityContextHolder”更改模式。MODE_THREADLOCAL '有两种方式。首先是设置一个系统属性。第二种方法是在“SecurityContextHolder”上调用一个静态方法。大多数应用程序不需要更改默认值。但是,如果你需要,可以看看JavaDoc for“SecurityContextHolder”来学习

4.1 source analysis

Associates a given {@link SecurityContext} with the current execution thread.

// 储存用户认证信息的策略
private static SecurityContextHolderStrategy strategy;
// 可以通过系统参数配置
public static final String SYSTEM_PROPERTY = "spring.security.strategy";


/**
 * 获取认证信息
 * Obtain the current <code>SecurityContext</code>.
 * @return the security context (never <code>null</code>)
 */
public static SecurityContext getContext() {
	return strategy.getContext();
}

/**
 * 清除当前认证信息
 * Explicitly clears the context value from the current thread.
 */
public static void clearContext() {
	strategy.clearContext();
}


/**
 * 关联新的认证信息到当前线程
 * Associates a new <code>SecurityContext</code> with the current thread of execution.
 * @param context the new <code>SecurityContext</code> (may not be <code>null</code>)
 */
public static void setContext(SecurityContext context) {
	strategy.setContext(context);
}

4.1 For Example SecurityContextHolderStrategy

4.1.1 ThreadLocalSecurityContextHolderStrategy

ThreadLocal 实现, 都是些简单的认证信息操作方法

// 核心参数,保存当前线程与用户认证信息上下文	
private static final ThreadLocal<Supplier<SecurityContext>> contextHolder = new ThreadLocal<>();

	@Override
	public void clearContext() {
		contextHolder.remove();
	}

	@Override
	public SecurityContext getContext() {
		return getDeferredContext().get();
	}

5. SecurityContext

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

6. Authentication

The Authentication interface serves two main purposes within Spring Security:

主要两个用途

  • An input to AuthenticationManager to provide the credentials a user has provided to authenticate. When used in this scenario, isAuthenticated() returns false.

作为 AuthenticationManager 的输入,来认证用户信息,这种情况下,isAuthenticated 返回 false.

  • Represent the currently authenticated user. You can obtain the current Authentication from the SecurityContext.

The Authentication contains。

代表当前用的认证过得信息,包含下面几点

  • principal: Identifies the user. When authenticating with a username/password this is often an instance of UserDetails.
  • credentials: Often a password. In many cases, this is cleared after the user is authenticated, to ensure that it is not leaked.
  • authorities: The GrantedAuthority instances are high-level permissions the user is granted. Two examples are roles and scopes.

7. GrantedAuthority

GrantedAuthority instances are high-level permissions that the user is granted. Two examples are roles and scopes.

用户授权后的信息,角色或者权限范围

You can obtain GrantedAuthority instances from the Authentication.getAuthorities() method. This method provides a Collection of GrantedAuthority objects. A GrantedAuthority is, not surprisingly, an authority that is granted to the principal. Such authorities are usually “roles”, such as ROLE_ADMINISTRATOR or ROLE_HR_SUPERVISOR. These roles are later configured for web authorization, method authorization, and domain object authorization. Other parts of Spring Security interpret these authorities and expect them to be present. When using username/password based authentication GrantedAuthority instances are usually loaded by the UserDetailsService.

通过 Authentication.getAuthorities() 方法获取,一般代表一组角色,这些角色可以配置 web 权限,方法权限等。使用 username/password 认证时,通常由 UserDetailsService 把权限信息 load 进去

Usually, the GrantedAuthority objects are application-wide permissions. They are not specific to a given domain object. Thus, you would not likely have a GrantedAuthority to represent a permission to Employee object number 54, because if there are thousands of such authorities you would quickly run out of memory (or, at the very least, cause the application to take a long time to authenticate a user). Of course, Spring Security is expressly designed to handle this common requirement, but you should instead use the project’s domain object security capabilities for this purpose.

该段文字的意思是,通常GrantedAuthority对象是应用程序范围内的权限,而不是针对特定域对象的权限。因此,您不太可能拥有一个GrantedAuthority来表示对某个特定Employee对象的权限,因为如果有数千个这样的权限,您很快就会耗尽内存(或者至少会导致应用程序花费很长时间来验证用户)。当然,Spring Security明确设计用于处理这种常见的要求,但您应该使用项目的域对象安全性能力来实现此目的。

换句话说,GrantedAuthority对象通常是用来表示全局权限的,比如“ADMIN”、“USER”等等。如果您想要授予用户对某个具体的域对象的权限,例如对某个特定Employee对象的权限,那么应该使用项目的域对象安全性能力来实现。这可以确保应用程序在授权过程中更有效地处理内存,并且更容易维护和管理授权。

8. AuthenticationManager

  *[`AuthenticationManager`](https://docs.spring.io/spring-security/site/docs/6.0.2/api/org/springframework/security/authentication/AuthenticationManager.html) is the API that defines how Spring Security’s Filters perform [authentication](https://docs.spring.io/spring-security/reference/features/authentication/index.html#authentication). The [`Authentication`](https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-authentication) that is returned is then set on the [SecurityContextHolder](https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-securitycontextholder) by the controller (that is, by [Spring Security’s `Filters` instances](https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-security-filters)) that invoked the `AuthenticationManager`. If you are not integrating with Spring Security’s `Filters` instances, you can set the `SecurityContextHolder` directly and are not required to use an `AuthenticationManager`.*

定义了过滤器怎么执行认证的 api

While the implementation of AuthenticationManager could be anything, the most common implementation is ProviderManager.

9. ProviderManager

ProviderManager is the most commonly used implementation of AuthenticationManager. ProviderManager delegates to a List of AuthenticationProvider instances. Each AuthenticationProvider has an opportunity to indicate that authentication should be successful, fail, or indicate it cannot make a decision and allow a downstream AuthenticationProvider to decide. If none of the configured AuthenticationProvider instances can authenticate, authentication fails with a ProviderNotFoundException, which is a special AuthenticationException that indicates that the ProviderManager was not configured to support the type of Authentication that was passed into it.

​ *ProviderManager 是 AuthenticationManager 最通用的实现。ProviderManager 代理了一个 AuthenticationProvider 的数组。

在这里插入图片描述

  *In practice each `AuthenticationProvider` knows how to perform a specific type of authentication. For example, one `AuthenticationProvider` might be able to validate a username/password, while another might be able to authenticate a SAML assertion. This lets each `AuthenticationProvider` do a very specific type of authentication while supporting multiple types of authentication and expose only a single `AuthenticationManager` bean.*

在 Spring Security 框架中,一个多步骤认证的过程通常会涉及多个 AuthenticationProvider 组件,负责不同方面的认证(例如验证凭据,检查多因素认证等等)。在这个过程中,每个 AuthenticationProvider 都可以通过返回特定的认证结果来指示认证是否成功、失败或者无法决定并允许下一个 AuthenticationProvider 组件来做出决策。这种机制使得多个认证提供者可以协同工作,以便实现更强大的认证方案。

ProviderManager also allows configuring an optional parent AuthenticationManager, which is consulted in the event that no AuthenticationProvider can perform authentication. The parent can be any type of AuthenticationManager, but it is often an instance of ProviderManager.

在 Spring Security 框架中,ProviderManager 组件还支持配置一个可选的父级 AuthenticationManager,当没有任何 AuthenticationProvider 组件能够进行认证时,会向父级 AuthenticationManager 发起请求。父级 AuthenticationManager 可以是任何类型的 AuthenticationManager,但通常是一个 ProviderManager 实例。这种机制使得认证可以按照特定的层次结构进行组织和管理,从而更好地管理不同类型的认证提供者和认证方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28fNPCKG-1677744643661)(/Users/qianyuhua/Documents/mine/painnote/source-code/spring-security/picture/providermanager-parent.png)]

  In fact, multiple `ProviderManager` instances might share the same parent `AuthenticationManager`. This is somewhat common in scenarios where there are multiple [`SecurityFilterChain`](https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-securityfilterchain) instances that have some authentication in common (the shared parent `AuthenticationManager`), but also different authentication mechanisms (the different `ProviderManager` instances).

在这里插入图片描述

​ By default, ProviderManager tries to clear any sensitive credentials information from the Authentication object that is returned by a successful authentication request. This prevents information, such as passwords, being retained longer than necessary in the HttpSession.

​ This may cause issues when you use a cache of user objects, for example, to improve performance in a stateless application. If the Authentication contains a reference to an object in the cache (such as a UserDetails instance) and this has its credentials removed, it is no longer possible to authenticate against the cached value. You need to take this into account if you use a cache. An obvious solution is to first make a copy of the object, either in the cache implementation or in the AuthenticationProvider that creates the returned Authentication object. Alternatively, you can disable the eraseCredentialsAfterAuthentication property on ProviderManager. See the Javadoc for the Javadoc class.

ProviderManager 组件默认情况下会清除 Authentication 对象中的敏感凭据信息(例如密码)以避免在 HttpSession 中保留这些信息的时间过长。这是出于安全考虑,但这样做可能会对使用用户对象缓存的应用程序造成影响。

如果在使用用户对象缓存时,Authentication 对象中包含有缓存对象的引用(例如 UserDetails 实例),并且其中的敏感凭据信息已被删除,那么就不再能够对缓存的对象进行身份验证。为了解决这个问题,可以在缓存实现或者创建 Authentication 对象的 AuthenticationProvider 中先复制缓存对象,以确保缓存对象的引用不会因为敏感凭据信息的清除而丢失。

另外,还可以通过设置 ProviderManagereraseCredentialsAfterAuthentication 属性来禁用敏感凭据信息的清除操作。如果应用程序使用缓存,需要注意这些问题,并选择合适的解决方案来确保安全性和可用性。

9.1 source analysis

// 保存了 AuthenticationProvider 多个,提供多种用户信息监测方案
private List<AuthenticationProvider> providers = Collections.emptyList();
// 父
private AuthenticationManager parent;

/**
* Attempts to authenticate the passed {@link Authentication} object.
* 尝试验证 Authentication 对象
* <p>
* The list of {@link AuthenticationProvider}s will be successively tried until an
* <code>AuthenticationProvider</code> indicates it is capable of authenticating the
* type of <code>Authentication</code> object passed. Authentication will then be
* attempted with that <code>AuthenticationProvider</code>.
* <p>
* If more than one <code>AuthenticationProvider</code> supports the passed
* <code>Authentication</code> object, the first one able to successfully
* authenticate the <code>Authentication</code> object determines the
* <code>result</code>, overriding any possible <code>AuthenticationException</code>
* thrown by earlier supporting <code>AuthenticationProvider</code>s.
* On successful authentication, no subsequent <code>AuthenticationProvider</code>s
* will be tried.
* If authentication was not successful by any supporting
* <code>AuthenticationProvider</code> the last thrown
* <code>AuthenticationException</code> will be rethrown.
*
* @param authentication the authentication request object.
*
* @return a fully authenticated object including credentials.
*
* @throws AuthenticationException if authentication fails.
*/
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;
	boolean debug = logger.isDebugEnabled();

  // 遍历所有 AuthenticationProvider
	for (AuthenticationProvider provider : getProviders()) {
    // 判断当前provider是否支持传入Authentication对象的类型
		if (!provider.supports(toTest)) {
			continue;
		}

		if (debug) {
			logger.debug("Authentication attempt using "
					+ provider.getClass().getName());
		}

		try {
      // 认证结果
			result = provider.authenticate(authentication);
			// 如果认证成功,则将认证结果的详细信息复制到传入的Authentication对象中,并结束循环
			if (result != null) {
				copyDetails(authentication, result);
				break;
			}
		}
		catch (AccountStatusException | InternalAuthenticationServiceException e) {
			prepareException(e, authentication);
			// SEC-546: Avoid polling additional providers if auth failure is due to
			// invalid account status
			throw e;
		} catch (AuthenticationException e) {
			lastException = e;
		}
	}

   // 如果上面循环中没有找到合适的provider并且存在parent AuthenticationManager,
  //  则向上递归调用parent的authenticate方法
		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
         // 调用parent的authenticate方法对传入的Authentication对象进行认证
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

    // 如果成功认证,则发布AuthenticationSuccessEvent事件并返回认证结果
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
        // 如果配置了在认证后清除凭证信息,则将凭证信息清除
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

10. AuthenticationProvider

You can inject multiple AuthenticationProviders instances into ProviderManager. Each AuthenticationProvider performs a specific type of authentication. For example, DaoAuthenticationProvider supports username/password-based authentication, while JwtAuthenticationProvider supports authenticating a JWT token.

可以配置多个 AuthenticationProviders 实例在 ProviderManager中,每个都执行了一个认证辑。DaoAuthenticationProvider 支持 用户名及密码认证, JwtAuthenticationProvider 支持 jwt 认证

11. Request Credentials with AuthenticationEntryPoint

AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client.

用于发送从客户端请求凭据的HTTP响应。

Sometimes, a client proactively includes credentials (such as a username and password) to request a resource. In these cases, Spring Security does not need to provide an HTTP response that requests credentials from the client, since they are already included.

​ 有时,客户端会携带证书(如用户名密码)请求资源。这些情况下,spring security 不需要提供一个认证响应,因为已经包含了。

In other cases, a client makes an unauthenticated request to a resource that they are not authorized to access. In this case, an implementation of AuthenticationEntryPoint is used to request credentials from the client. The AuthenticationEntryPoint implementation might perform a redirect to a log in page, respond with an WWW-Authenticate header, or take other action.

12. AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter is used as a base Filter for authenticating a user’s credentials. Before the credentials can be authenticated, Spring Security typically requests the credentials by using AuthenticationEntryPoint.

Next, the AbstractAuthenticationProcessingFilter can authenticate any authentication requests that are submitted to it.

在这里插入图片描述

number 1When the user submits their credentials, the AbstractAuthenticationProcessingFilter creates an Authentication from the HttpServletRequest to be authenticated. The type of Authentication created depends on the subclass of AbstractAuthenticationProcessingFilter. For example, UsernamePasswordAuthenticationFilter creates a UsernamePasswordAuthenticationToken from a username and password that are submitted in the HttpServletRequest.

当用户提供了认证凭据,AbstractAuthenticationProcessingFilter 通过 http 请求创建一个被认证过得 Authentication , 类型由子类决定。 例如 UsernamePasswordAuthenticationFilter会创建一个 UsernamePasswordAuthenticationToken

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    // 根据路径判断是不是要做认证
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		try {
      // 根据子类,创建一个被认证过得 Authentication
			Authentication authenticationResult = attemptAuthentication(request, response);
      // 认证失败
			if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
			}
      // 认证成功后的会话插件
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			// Authentication success
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
      // 认证成功的行为
			successfulAuthentication(request, response, chain, authenticationResult);
		}
		catch (InternalAuthenticationServiceException failed) {
			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, ex);
		}
	}

number 2 Next, the Authentication is passed into the AuthenticationManager to be authenticated.

Authentication 交给 AuthenticationManager 做认证, UsernamePasswordAuthenticationFilter为例

	@Override
	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());
		}
    // 校验请求中的账号密码
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
    // 生成一个未认证的 Authentication
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
    // 交给 manager 去做认证, 返回一个认证完的 Authentication
		return this.getAuthenticationManager().authenticate(authRequest);
	}

number 3 If authentication fails, then Failure.

认证失败了, 把认证上下文信息清空了,比如 ThreadLoca

number 4 If authentication is successful, then Success.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值