关于
- spring security 官网链接, 本次示例使用的版本为5.6.3:https://docs.spring.io/spring-security/reference/index.html
- 什么是认证:通俗点点理解来理解就是登录。随着互联网的发展,登录的方式也变得五花八门。较为常见的有账号密码登录,手机号验证码登录,三方登录等多种形式。
1. SpringSecurity中谁负责认证呢? AuthenticationProvider
事实上,上面的问题是不固定。用户可以通过很多组件来实现用户的认证。如通过Filter (Interceptor也类似)通过获取请求中传递的认证参数来做认证,或通过Controller中的接口来直接认证。
AuthenticationProvider 实际上是一个接口,具体实现要用户根据自身的需求来进行定制。
通过查看源代码很容易了解对应方法的逻辑。 一个是负责认证的业务逻辑。一个是决定当前 Provider是否可以对用户传递的认证主体进行认证。
package org.springframework.security.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public interface AuthenticationProvider {
/**
* authentication: 认证的主体,开发者需要将需要认证的数据封装其中
* @return 认证通过后,将真实的用户信息进行封装返回
**/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
/*
* 判断当前的authentication类型 本AuthenticationProvider是否可以处理
* */
boolean supports(Class<?> authentication);
}
2. 认证所需要的数据主体 Authentication
通过查看 Authentication 接口的源码发现很难看出一个所以然。所以我们可以通过其已有的实现类来查看其每个方法的用途。以及我们如何去进行自定义和拓展。
2.1 最熟悉以及经典的的实现 UsernamePasswordAuthenticationToken
如果你需要自定义认证主体,那么可以模仿 UsernamePasswordAuthenticationToken的实现,同时要注意我圈出来的位置。
首先,需要理解,为什么需要这样设计两个构造函数?
这和上面聊到的 AuthenticationProvider 中 authenticate 方法的 参数 和 返回值是对应的:
- 参数: 待认证的主体,调用的是第一个构造方法。
- 返回值: 认证完成后重新构造的主体,调用的是第二个构造方法。 通过注释也能看到,这个方法一般是在AuthenticationProvider中进行调用。
我们针对源码对几个重要对象进行分析:
/**
* An {@link org.springframework.security.core.Authentication} implementation that is
* designed for simple presentation of a username and password.
* <p>
* The <code>principal</code> and <code>credentials</code> should be set with an
* <code>Object</code> that provides the respective property via its
* <code>Object.toString()</code> method. The simplest such <code>Object</code> to use is
* <code>String</code>.
*
* @author Ben Alex
*/
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
/**
* This constructor can be safely used by any code that wishes to create a
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
* will return <code>false</code>.
*
*/
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
/**
* 这个构造器应该只能被 AuthenticationManager 或者 AuthenticationProvider 的实现来使用,创建一个
* 被认证的对象。
*
* This constructor should only be used by <code>AuthenticationManager</code> or
* <code>AuthenticationProvider</code> implementations that are satisfied with
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
* @param principal
* @param credentials
* @param authorities
*/
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated,
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
3 Authentication(未认证) 的构造时机 (过滤器)
首先我们来分析一下UsernamePasswordAuthenticationFilter 的实现逻辑:
其中本类中的核心逻辑都在attemptAuthentication方法中,下面我们可以仔细分析一下这个方法的具体流程。
在此处,我们立马就明白了,在未认证前, UsernamePasswordAuthenticationToken 中的两个属性分别代表:
// 用户名
private final Object principal;
// 密码
private Object credentials;
同时发现,按照我们前面的描述,认证的具体逻辑应该是由AuthenticationProvider 来来实现的,但此处为什么是AuthenticationManager 呢?
Spring Security 为了实现多种认证方式并存,于是抽象出一个 AuthenticationManager, 用来管理多个 AuthenticationProvider,每一个 AuthenticationProvider负责一种认证方式
下面我们就以Spring Security中默认的AuthenticationManager实现:ProviderManager 来看一下具体实现机制。
完全可以看出,其中认证的具体逻辑还是AuthenticationProvider进行实现。
4. AuthenticationManager
从语意上来说,这是不准确的。正确应该是 AuthenticationProviderManager,因为它实际上是来管理一些列 AuthenticaionProvider的。在其内部维护着一个AuthenticaionProvider数组。
在 引入 SpringSecurity的过程中,已经有一个AuthenticationManager对象被创建,如果我们需要使用它,一定要在配置类中将其以Bean的方式进行暴露。使得我们在使用的时候直接进行注入即可。