在
Spring Security
中,通过
Authentication
来封装用户的验证请求信息,
Authentication
可以是需要验证和已验证的用户请求信息封装。接下来,博主介绍
Authentication
接口及其实现类。
Authentication
Authentication
接口源码(
Authentication
接口继承
Principal
接口,
Principal
接口表示主体的抽象概念,可用于表示任何实体):
packageorg.springframework.security.core;importjava.io.Serializable;importjava.security.Principal;importjava.util.Collection;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.core.context.SecurityContextHolder;publicinterfaceAuthenticationextendsPrincipal,Serializable{/**
* 由AuthenticationManager(用于验证Authentication请求)设置,用于指示已授予主体的权限
* 除非已由受信任的AuthenticationManager设置,否则实现类不应依赖此值作为有效值
* 实现应确保对返回的集合数组的修改不会影响Authentication对象的状态,或使用不可修改的实例
* 返回:授予主体的权限,如果令牌尚未经过身份验证,则为空集合
*/Collection<?extendsGrantedAuthority>getAuthorities();/**
* 证明主体的凭据
* 通常是一个密码,但可以是与AuthenticationManager相关的任何内容
* 调用者应填充凭据
*/ObjectgetCredentials();/**
* 存储有关身份验证请求的其他详细信息
* 可能是IP地址、证书序列号等
*/ObjectgetDetails();/**
* 被认证的主体的身份
* 在使用用户名和密码的身份验证请求情况下,这将是用户名
* 调用者应填充身份验证请求的主体
* AuthenticationManager实现通常会返回一个包含更丰富信息的Authentication作为应用程序使用的主体
* 大多数身份验证提供程序将创建一个UserDetails对象作为主体
*/ObjectgetPrincipal();/**
* 用于向AbstractSecurityInterceptor指示它是否应该向AuthenticationManager提供身份验证令牌
* 通常,AuthenticationManager将在身份验证成功后返回一个不可变的身份验证令牌
* 在这种情况下,该令牌的此方法可以安全地返回true
* 返回true将提高性能,因为不再需要为每个请求调用AuthenticationManager
* 出于安全原因,这个接口的实现应该非常小心地从这个方法返回true
* 除非它们是不可变的,或者有某种方式确保属性自最初创建以来没有被更改
*/booleanisAuthenticated();/**
* 实现应始终允许使用false参数调用此方法
* 可以使用它来指定不应信任的身份验证令牌
*/voidsetAuthenticated(boolean isAuthenticated)throwsIllegalArgumentException;}
Authentication
接口及其实现类如下图所示:
AbstractAuthenticationToken
它是
Authentication
接口的基类,使用此类的实现应该是不可变的(模板模式)。
publicabstractclassAbstractAuthenticationTokenimplementsAuthentication,CredentialsContainer{// 权限列表 privatefinalCollection<GrantedAuthority> authorities;// 存储有关身份验证请求的其他详细信息,可能是IP地址、证书序列号等privateObject details;// 是否已验证,默认为falseprivateboolean authenticated =false;/**
* 使用提供的权限列表创建一个身份验证令牌
*/publicAbstractAuthenticationToken(Collection<?extendsGrantedAuthority> authorities){// 权限列表为null,会将authorities属性设置为AuthorityUtils.NO_AUTHORITIES// List<GrantedAuthority> NO_AUTHORITIES = Collections.emptyList()// Collections.emptyList()会返回一个空列表,并且不可变if(authorities ==null){this.authorities =AuthorityUtils.NO_AUTHORITIES;return;}for(GrantedAuthority a : authorities){// 权限列表中存在权限为null,抛出异常if(a ==null){thrownewIllegalArgumentException("Authorities collection cannot contain any null elements");}}ArrayList<GrantedAuthority> temp =newArrayList<>(
authorities.size());
temp.addAll(authorities);// 不可修改的权限列表this.authorities =Collections.unmodifiableList(temp);}// 返回主体的权限列表publicCollection<GrantedAuthority>getAuthorities(){return authorities;}// 返回主体的名称publicStringgetName(){// 如果主体是UserDetails实例,返回实例的用户名if(this.getPrincipal()instanceofUserDetails){return((UserDetails)this.getPrincipal()).getUsername();}// 如果主体是AuthenticatedPrincipal实例,返回实例的名称if(this.getPrincipal()instanceofAuthenticatedPrincipal){return((AuthenticatedPrincipal)this.getPrincipal()).getName();}// 如果主体是Principal实例,返回实例的名称if(this.getPrincipal()instanceofPrincipal){return((Principal)this.getPrincipal()).getName();}// 如果主体为null,则返回"",否则返回实例的toString()return(this.getPrincipal()==null)?"":this.getPrincipal().toString();}// 返回主体是否已验证publicbooleanisAuthenticated(){return authenticated;}// 设置authenticated属性publicvoidsetAuthenticated(boolean authenticated){this.authenticated = authenticated;}// 返回存储有关身份验证请求的其他详细信息publicObjectgetDetails(){return details;}// 设置details属性publicvoidsetDetails(Object details){this.details = details;}/**
* 检查credentials、principal和details属性
* 对任何CredentialsContainer实例调用eraseCredentials方法
*/publicvoideraseCredentials(){eraseSecret(getCredentials());eraseSecret(getPrincipal());eraseSecret(details);}// 判断参数是否instanceof CredentialsContainer// 如果是,则调用参数的eraseCredentials方法privatevoideraseSecret(Object secret){if(secret instanceofCredentialsContainer){((CredentialsContainer) secret).eraseCredentials();}}...}
UserDetails
:Spring Security
使用UserDetails
接口来抽象用户(Spring Security:用户UserDetails源码与Debug分析)。AuthenticatedPrincipal
:一旦Authentication
请求已通过AuthenticationManager.authenticate(Authentication)
方法成功验证,则表示经过身份验证的Principal
(实体)。实现者通常提供他们自己的Principal
表示,其中通常包含描述Principal
实体的信息,例如名字、地址、电子邮件、电话以及ID
等,此接口允许实现者公开其自定义的特定属性以通用方式表示Principal
。Principal
:该接口表示主体的抽象概念,可用于表示任何实体,是java.security
包下的接口,并非由Spring Security
提供。
UsernamePasswordAuthenticationToken
它是一种
Authentication
实现,继承
AbstractAuthenticationToken
抽象类,旨在简单地表示用户名和密码。
principal
和
credentials
属性应设置为通过其
toString
方法提供相应属性的
Object
,最简单的就是
String
类型。
publicclassUsernamePasswordAuthenticationTokenextendsAbstractAuthenticationToken{privatestaticfinallong serialVersionUID =SpringSecurityCoreVersion.SERIAL_VERSION_UID;privatefinalObject principal;privateObject credentials;/**
* 任何希望创建UsernamePasswordAuthenticationToken实例的代码都可以安全地使用此构造函数
* 因为isAuthenticated()将返回false
*/publicUsernamePasswordAuthenticationToken(Object principal,Object credentials){super(null);this.principal = principal;this.credentials = credentials;setAuthenticated(false);}/**
* 此构造函数只能由满足生成可信(即isAuthenticated() = true )身份验证令牌的AuthenticationManager或AuthenticationProvider实现使用
*/publicUsernamePasswordAuthenticationToken(Object principal,Object credentials,Collection<?extendsGrantedAuthority> authorities){super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true);// 必须使用super来设置}// 返回凭证(如密码)publicObjectgetCredentials(){returnthis.credentials;}// 返回实体(如用户名)publicObjectgetPrincipal(){returnthis.principal;}// 设置isAuthenticated属性,只能设置为falsepublicvoidsetAuthenticated(boolean isAuthenticated)throwsIllegalArgumentException{// 无法将此令牌设置为受信任的令牌// 需要使用有GrantedAuthority列表参数的构造函数if(isAuthenticated){thrownewIllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");}super.setAuthenticated(false);}// 重写eraseCredentials方法// 将凭证直接设置为null@OverridepublicvoideraseCredentials(){super.eraseCredentials();
credentials =null;}}
TestingAuthenticationToken
类是设计用于单元测试,对应的身份验证提供程序是
TestingAuthenticationProvider
,这里就不过多介绍它了。
RememberMeAuthenticationToken
它是一种
Authentication
实现,继承
AbstractAuthenticationToken
抽象类,表示需要记住的
Authentication
,需要记住的
Authentication
必须提供完全有效的
Authentication
,包括适用的
GrantedAuthority
。
publicclassRememberMeAuthenticationTokenextendsAbstractAuthenticationToken{privatestaticfinallong serialVersionUID =SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 主体privatefinalObject principal;// 识别此对象是否由授权客户生成的key的hashCodeprivatefinalint keyHash;/**
* 构造函数
* 参数:
* key – 识别此对象是否由授权客户生成
* principal – 主体(通常是UserDetails)
* authorities — 授予主体的权限
*/publicRememberMeAuthenticationToken(String key,Object principal,Collection<?extendsGrantedAuthority> authorities){super(authorities);if((key ==null)||("".equals(key))||(principal ==null)||"".equals(principal)){thrownewIllegalArgumentException("Cannot pass null or empty values to constructor");}this.keyHash = key.hashCode();this.principal = principal;setAuthenticated(true);}/**
* 帮助Jackson反序列化的私人构造函数
* 参数:
* keyHash – 上面给定key的hashCode
* principal – 主体(通常是UserDetails)
* authorities — 授予主体的权限
*/privateRememberMeAuthenticationToken(Integer keyHash,Object principal,Collection<?extendsGrantedAuthority> authorities){super(authorities);this.keyHash = keyHash;this.principal = principal;setAuthenticated(true);}/**
* 总是返回一个空String
*/@OverridepublicObjectgetCredentials(){return"";}// 返回keyHash publicintgetKeyHash(){returnthis.keyHash;}// 返回主体@OverridepublicObjectgetPrincipal(){returnthis.principal;}}
PreAuthenticatedAuthenticationToken
它是一种
Authentication
实现,继承
AbstractAuthenticationToken
抽象类,用于预认证身份验证。有些情况下,希望使用
Spring Security
进行授权,但是在访问应用程序之前,用户已经被某个外部系统可靠地验证过了,将这种情况称为预认证场景,比如
CSDN
可以使用其他平台的账号进行登陆,如下图所示:
publicclassPreAuthenticatedAuthenticationTokenextendsAbstractAuthenticationToken{privatestaticfinallong serialVersionUID =SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 主体privatefinalObject principal;// 凭证privatefinalObject credentials;/**
* 用于身份验证请求的构造函数
* isAuthenticated()将返回false
*/publicPreAuthenticatedAuthenticationToken(Object aPrincipal,Object aCredentials){super(null);this.principal = aPrincipal;this.credentials = aCredentials;}/**
* 用于身份验证响应的构造函数
* isAuthenticated()将返回true
*/publicPreAuthenticatedAuthenticationToken(Object aPrincipal,Object aCredentials,Collection<?extendsGrantedAuthority> anAuthorities){super(anAuthorities);this.principal = aPrincipal;this.credentials = aCredentials;setAuthenticated(true);}/**
* 返回凭证
*/publicObjectgetCredentials(){returnthis.credentials;}/**
* 返回主体
*/publicObjectgetPrincipal(){returnthis.principal;}}
AnonymousAuthenticationToken
它是一种
Authentication
实现,继承
AbstractAuthenticationToken
抽象类,表示匿名
Authentication
。
publicclassAnonymousAuthenticationTokenextendsAbstractAuthenticationTokenimplementsSerializable{privatestaticfinallong serialVersionUID =1L;// 主体privatefinalObject principal;// 识别此对象是否由授权客户生成的key的hashCode privatefinalint keyHash;/**
* 构造函数
* 参数:
* key – 识别此对象是否由授权客户生成
* principal – 主体(通常是UserDetails)
* authorities — 授予主体的权限
*/publicAnonymousAuthenticationToken(String key,Object principal,Collection<?extendsGrantedAuthority> authorities){this(extractKeyHash(key), principal, authorities);}/**
* 该构造函数有助于Jackson反序列化
* 参数:
* keyHash – 提供的Key的hashCode,由上面的构造函数提供
* principal – 主体(通常是UserDetails)
* authorities — 授予主体的权限
*/privateAnonymousAuthenticationToken(Integer keyHash,Object principal,Collection<?extendsGrantedAuthority> authorities){super(authorities);if(principal ==null||"".equals(principal)){thrownewIllegalArgumentException("principal cannot be null or empty");}Assert.notEmpty(authorities,"authorities cannot be null or empty");this.keyHash = keyHash;this.principal = principal;setAuthenticated(true);}// 返回参数key的hashCodeprivatestaticIntegerextractKeyHash(String key){Assert.hasLength(key,"key cannot be empty or null");return key.hashCode();}/**
* 总是返回一个空String
*/@OverridepublicObjectgetCredentials(){return"";}// 返回keyHashpublicintgetKeyHash(){returnthis.keyHash;}// 返回主体@OverridepublicObjectgetPrincipal(){returnthis.principal;}}
RunAsUserToken
它是一种
Authentication
实现,继承
AbstractAuthenticationToken
抽象类,用于支持
RunAsManagerImpl
的
Authentication
实现。
RunAsManagerImpl
类是
RunAsManager
接口的基本实现,如果发现
ConfigAttribute.getAttribute()
以
RUN_AS_
为前缀,它会生成一个新的
RunAsUserToken
实例,包含与原始
Authentication
实例相同的主体、凭证和授予权限列表等。
RunAsManager
接口仅为当前安全对象调用创建一个新的临时
Authentication
实例,此接口允许实现替换,仅适用于当前安全对象调用的
Authentication
对象。
这是为了建立具有两层对象的系统,一层是面向公众的,并且具有正常的安全方法,授予的权限预计由外部调用者持有。 另一层是私有的,只希望由面向公众的层中的对象调用,此私有层中的对象仍然需要安全性(否则它们将是公共方法),需要防止被外部调用者直接调用,并且私有层中的对象被授予的权限从不授予外部调用者。
RunAsManager
接口提供了一种以上述方式提升安全性的机制。预计实现将提供相应的具体
Authentication
和
AuthenticationProvider
以便可以对替换的
Authentication
对象进行身份验证。需要实施某种形式的安全性,以确保
AuthenticationProvider
仅接受由
RunAsManager
授权的具体实现创建的
Authentication
对象。
publicclassRunAsUserTokenextendsAbstractAuthenticationToken{privatestaticfinallong serialVersionUID =SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 原Authentication对象的类型privatefinalClass<?extendsAuthentication> originalAuthentication;// 凭证privatefinalObject credentials;// 主体privatefinalObject principal;// 识别此对象是否由授权客户生成的key的hashCode privatefinalint keyHash;// 构造方法publicRunAsUserToken(String key,Object principal,Object credentials,Collection<?extendsGrantedAuthority> authorities,Class<?extendsAuthentication> originalAuthentication){super(authorities);this.keyHash = key.hashCode();this.principal = principal;this.credentials = credentials;this.originalAuthentication = originalAuthentication;setAuthenticated(true);}// 返回凭证@OverridepublicObjectgetCredentials(){returnthis.credentials;}// 返回keyHash属性publicintgetKeyHash(){returnthis.keyHash;}// 返回原Authentication对象的类型publicClass<?extendsAuthentication>getOriginalAuthentication(){returnthis.originalAuthentication;}// 返回主体@OverridepublicObjectgetPrincipal(){returnthis.principal;}@OverridepublicStringtoString(){StringBuilder sb =newStringBuilder(super.toString());String className =this.originalAuthentication ==null?null:this.originalAuthentication.getName();
sb.append("; Original Class: ").append(className);return sb.toString();}}
JaasAuthenticationToken
UsernamePasswordAuthenticationToken
的扩展,用来携带用户登录的
Jaas LoginContext
。
publicclassJaasAuthenticationTokenextendsUsernamePasswordAuthenticationToken{privatestaticfinallong serialVersionUID =SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 用户登录的Jaas LoginContextprivatefinaltransientLoginContext loginContext;// 构造函数publicJaasAuthenticationToken(Object principal,Object credentials,LoginContext loginContext){super(principal, credentials);this.loginContext = loginContext;}// 构造函数publicJaasAuthenticationToken(Object principal,Object credentials,List<GrantedAuthority> authorities,LoginContext loginContext){super(principal, credentials, authorities);this.loginContext = loginContext;}// 返回用户登录的Jaas LoginContextpublicLoginContextgetLoginContext(){return loginContext;}}
Debug分析
项目结构图:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kaven</groupId><artifactId>security</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies></project>
application.yml
:
spring:security:user:name: kaven
password: itkaven
logging:level:org:springframework:security: DEBUG
SecurityConfig
(
Spring Security
的配置类,不是必须的,因为会有默认的配置):
packagecom.kaven.security.config;importorg.springframework.security.config.Customizer;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{// 任何请求都需要进行验证
http.authorizeRequests().anyRequest().authenticated().and()// 记住身份验证.rememberMe(Customizer.withDefaults())// 基于表单登陆的身份验证方式.formLogin(Customizer.withDefaults());}}
MessageController
(定义接口):
packagecom.kaven.security.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassMessageController{@GetMapping("/message")publicStringgetMessage(){return"hello spring security";}}
启动类:
packagecom.kaven.security;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class);}}
Debug
方式启动应用,访问
http://localhost:8080/message
。请求会被
AnonymousAuthenticationFilter
处理,该过滤器会创建
Authentication
实例。
创建的便是
AnonymousAuthenticationToken
实例。
创建完
AnonymousAuthenticationToken
实例之后,请求会继续被其他过滤器处理,这就是
Spring Security
提供的过滤器链。在访问接口前,
Spring Security
会检查该请求的客户端是否具有访问该接口的权限。很显然是没有权限的,因此访问被拒绝了。
并且请求会被重定向到登录页,填入用户名和密码(配置文件中定义的)。
登陆请求会被
UsernamePasswordAuthenticationFilter
处理,该过滤器会创建
UsernamePasswordAuthenticationToken
实例,该实例将用于验证。
如果该
UsernamePasswordAuthenticationToken
实例验证成功,将会创建一个新的
UsernamePasswordAuthenticationToken
实例,表示身份验证成功的令牌。
登陆请求验证成功后,又会进行重定向,重定向到原来想要访问的接口(资源),即
/message
。
再次重定向的请求又会被过滤器链进行处理,最后会验证通过。
接口便访问成功了。
再来回味一下这段话,就很容易理解
Authentication
的作用了,在
Spring Security
中,通过
Authentication
来封装用户的验证请求信息,
Authentication
可以是需要验证和已验证的用户请求信息封装。
把
Debug
分析中的整个流程搞明白,便很容易理解
Authentication
的作用,不同的
Authentication
实现用于不同的验证时机与场景。
身份验证令牌
Authentication
介绍与
Debug
分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。
版权归原作者 ITKaven 所有, 如有侵权,请联系我们删除。