0


Spring Security:身份验证令牌Authentication介绍与Debug分析

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();}}...}
  • UserDetailsSpring 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

分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。

标签: spring java 安全

本文转载自: https://blog.csdn.net/qq_37960603/article/details/122448723
版权归原作者 ITKaven 所有, 如有侵权,请联系我们删除。

“Spring Security:身份验证令牌Authentication介绍与Debug分析”的评论:

还没有评论