Spring Security中,通过Authentication来封装用户的验证请求信息,Authentication可以是需要验证和已验证的用户请求信息封装。接下来,博主介绍Authentication接口及其实现类。

Authentication

Authentication接口源码(Authentication接口继承Principal接口,Principal接口表示主体的抽象概念,可用于表示任何实体):

package org.springframework.security.core;import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;public interface Authentication extends Principal, Serializable {/*** 由AuthenticationManager(用于验证Authentication请求)设置,用于指示已授予主体的权限* 除非已由受信任的AuthenticationManager设置,否则实现类不应依赖此值作为有效值* 实现应确保对返回的集合数组的修改不会影响Authentication对象的状态,或使用不可修改的实例* 返回:授予主体的权限,如果令牌尚未经过身份验证,则为空集合*/Collection<? extends GrantedAuthority> getAuthorities();/*** 证明主体的凭据* 通常是一个密码,但可以是与AuthenticationManager相关的任何内容* 调用者应填充凭据*/Object getCredentials();/*** 存储有关身份验证请求的其他详细信息* 可能是IP地址、证书序列号等*/Object getDetails();/*** 被认证的主体的身份* 在使用用户名和密码的身份验证请求情况下,这将是用户名* 调用者应填充身份验证请求的主体* AuthenticationManager实现通常会返回一个包含更丰富信息的Authentication作为应用程序使用的主体* 大多数身份验证提供程序将创建一个UserDetails对象作为主体*/Object getPrincipal();/*** 用于向AbstractSecurityInterceptor指示它是否应该向AuthenticationManager提供身份验证令牌* 通常,AuthenticationManager将在身份验证成功后返回一个不可变的身份验证令牌* 在这种情况下,该令牌的此方法可以安全地返回true* 返回true将提高性能,因为不再需要为每个请求调用AuthenticationManager* 出于安全原因,这个接口的实现应该非常小心地从这个方法返回true* 除非它们是不可变的,或者有某种方式确保属性自最初创建以来没有被更改*/boolean isAuthenticated();/*** 实现应始终允许使用false参数调用此方法* 可以使用它来指定不应信任的身份验证令牌*/void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication接口及其实现类如下图所示:

AbstractAuthenticationToken

它是Authentication接口的基类,使用此类的实现应该是不可变的(模板模式)。

public abstract class AbstractAuthenticationToken implements Authentication,CredentialsContainer {// 权限列表    private final Collection<GrantedAuthority> authorities;// 存储有关身份验证请求的其他详细信息,可能是IP地址、证书序列号等private Object details;// 是否已验证,默认为falseprivate boolean authenticated = false;/*** 使用提供的权限列表创建一个身份验证令牌*/public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> 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) {throw new IllegalArgumentException("Authorities collection cannot contain any null elements");}}ArrayList<GrantedAuthority> temp = new ArrayList<>(authorities.size());temp.addAll(authorities);// 不可修改的权限列表this.authorities = Collections.unmodifiableList(temp);}// 返回主体的权限列表public Collection<GrantedAuthority> getAuthorities() {return authorities;}// 返回主体的名称public String getName() {// 如果主体是UserDetails实例,返回实例的用户名if (this.getPrincipal() instanceof UserDetails) {return ((UserDetails) this.getPrincipal()).getUsername();}// 如果主体是AuthenticatedPrincipal实例,返回实例的名称if (this.getPrincipal() instanceof AuthenticatedPrincipal) {return ((AuthenticatedPrincipal) this.getPrincipal()).getName();}// 如果主体是Principal实例,返回实例的名称if (this.getPrincipal() instanceof Principal) {return ((Principal) this.getPrincipal()).getName();}// 如果主体为null,则返回"",否则返回实例的toString()return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();}// 返回主体是否已验证public boolean isAuthenticated() {return authenticated;}// 设置authenticated属性public void setAuthenticated(boolean authenticated) {this.authenticated = authenticated;}// 返回存储有关身份验证请求的其他详细信息public Object getDetails() {return details;}// 设置details属性public void setDetails(Object details) {this.details = details;}/*** 检查credentials、principal和details属性* 对任何CredentialsContainer实例调用eraseCredentials方法*/public void eraseCredentials() {eraseSecret(getCredentials());eraseSecret(getPrincipal());eraseSecret(details);}// 判断参数是否instanceof  CredentialsContainer// 如果是,则调用参数的eraseCredentials方法private void eraseSecret(Object secret) {if (secret instanceof CredentialsContainer) {((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抽象类,旨在简单地表示用户名和密码。principalcredentials属性应设置为通过其toString方法提供相应属性的Object,最简单的就是String类型。

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;private final Object principal;private Object credentials;/*** 任何希望创建UsernamePasswordAuthenticationToken实例的代码都可以安全地使用此构造函数* 因为isAuthenticated()将返回false*/public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {super(null);this.principal = principal;this.credentials = credentials;setAuthenticated(false);}/*** 此构造函数只能由满足生成可信(即isAuthenticated() = true )身份验证令牌的AuthenticationManager或AuthenticationProvider实现使用*/public UsernamePasswordAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true); // 必须使用super来设置}// 返回凭证(如密码)public Object getCredentials() {return this.credentials;}// 返回实体(如用户名)public Object getPrincipal() {return this.principal;}// 设置isAuthenticated属性,只能设置为falsepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {// 无法将此令牌设置为受信任的令牌// 需要使用有GrantedAuthority列表参数的构造函数if (isAuthenticated) {throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");}super.setAuthenticated(false);}// 重写eraseCredentials方法// 将凭证直接设置为null@Overridepublic void eraseCredentials() {super.eraseCredentials();credentials = null;}
}

TestingAuthenticationToken类是设计用于单元测试,对应的身份验证提供程序是TestingAuthenticationProvider,这里就不过多介绍它了。

RememberMeAuthenticationToken

它是一种Authentication实现,继承AbstractAuthenticationToken抽象类,表示需要记住的Authentication,需要记住的Authentication必须提供完全有效的Authentication ,包括适用的GrantedAuthority

public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 主体private final Object principal;// 识别此对象是否由授权客户生成的key的hashCodeprivate final int keyHash;/*** 构造函数* 参数:* key – 识别此对象是否由授权客户生成* principal – 主体(通常是UserDetails)* authorities — 授予主体的权限*/public RememberMeAuthenticationToken(String key, Object principal,Collection<? extends GrantedAuthority> authorities) {super(authorities);if ((key == null) || ("".equals(key)) || (principal == null)|| "".equals(principal)) {throw new IllegalArgumentException("Cannot pass null or empty values to constructor");}this.keyHash = key.hashCode();this.principal = principal;setAuthenticated(true);}/*** 帮助Jackson反序列化的私人构造函数* 参数:* keyHash – 上面给定key的hashCode* principal – 主体(通常是UserDetails)* authorities — 授予主体的权限*/private RememberMeAuthenticationToken(Integer keyHash, Object principal, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.keyHash = keyHash;this.principal = principal;setAuthenticated(true);}/*** 总是返回一个空String*/@Overridepublic Object getCredentials() {return "";}// 返回keyHash public int getKeyHash() {return this.keyHash;}// 返回主体@Overridepublic Object getPrincipal() {return this.principal;}
}

PreAuthenticatedAuthenticationToken

它是一种Authentication实现,继承AbstractAuthenticationToken抽象类,用于预认证身份验证。有些情况下,希望使用Spring Security进行授权,但是在访问应用程序之前,用户已经被某个外部系统可靠地验证过了,将这种情况称为预认证场景,比如CSDN可以使用其他平台的账号进行登陆,如下图所示:

public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 主体private final Object principal;// 凭证private final Object credentials;/*** 用于身份验证请求的构造函数* isAuthenticated()将返回false */public PreAuthenticatedAuthenticationToken(Object aPrincipal, Object aCredentials) {super(null);this.principal = aPrincipal;this.credentials = aCredentials;}/*** 用于身份验证响应的构造函数* isAuthenticated()将返回true*/public PreAuthenticatedAuthenticationToken(Object aPrincipal, Object aCredentials,Collection<? extends GrantedAuthority> anAuthorities) {super(anAuthorities);this.principal = aPrincipal;this.credentials = aCredentials;setAuthenticated(true);}/*** 返回凭证*/public Object getCredentials() {return this.credentials;}/*** 返回主体*/public Object getPrincipal() {return this.principal;}
}

AnonymousAuthenticationToken

它是一种Authentication实现,继承AbstractAuthenticationToken抽象类,表示匿名Authentication

public class AnonymousAuthenticationToken extends AbstractAuthenticationToken implementsSerializable {private static final long serialVersionUID = 1L;// 主体private final Object principal;// 识别此对象是否由授权客户生成的key的hashCode private final int keyHash;/*** 构造函数* 参数:* key – 识别此对象是否由授权客户生成* principal – 主体(通常是UserDetails)* authorities — 授予主体的权限*/public AnonymousAuthenticationToken(String key, Object principal,Collection<? extends GrantedAuthority> authorities) {this(extractKeyHash(key), principal, authorities);}/*** 该构造函数有助于Jackson反序列化* 参数:* keyHash – 提供的Key的hashCode,由上面的构造函数提供* principal – 主体(通常是UserDetails)* authorities — 授予主体的权限*/private AnonymousAuthenticationToken(Integer keyHash, Object principal,Collection<? extends GrantedAuthority> authorities) {super(authorities);if (principal == null || "".equals(principal)) {throw new IllegalArgumentException("principal cannot be null or empty");}Assert.notEmpty(authorities, "authorities cannot be null or empty");this.keyHash = keyHash;this.principal = principal;setAuthenticated(true);}// 返回参数key的hashCodeprivate static Integer extractKeyHash(String key) {Assert.hasLength(key, "key cannot be empty or null");return key.hashCode();}/*** 总是返回一个空String*/@Overridepublic Object getCredentials() {return "";}// 返回keyHashpublic int getKeyHash() {return this.keyHash;}// 返回主体@Overridepublic Object getPrincipal() {return this.principal;}
}

RunAsUserToken

它是一种Authentication实现,继承AbstractAuthenticationToken抽象类,用于支持RunAsManagerImplAuthentication实现。

RunAsManagerImpl类是RunAsManager接口的基本实现,如果发现ConfigAttribute.getAttribute()RUN_AS_为前缀,它会生成一个新的RunAsUserToken实例,包含与原始Authentication实例相同的主体、凭证和授予权限列表等。

RunAsManager接口仅为当前安全对象调用创建一个新的临时Authentication实例,此接口允许实现替换,仅适用于当前安全对象调用的Authentication对象。

这是为了建立具有两层对象的系统,一层是面向公众的,并且具有正常的安全方法,授予的权限预计由外部调用者持有。 另一层是私有的,只希望由面向公众的层中的对象调用,此私有层中的对象仍然需要安全性(否则它们将是公共方法),需要防止被外部调用者直接调用,并且私有层中的对象被授予的权限从不授予外部调用者。

RunAsManager接口提供了一种以上述方式提升安全性的机制。预计实现将提供相应的具体AuthenticationAuthenticationProvider以便可以对替换的Authentication对象进行身份验证。需要实施某种形式的安全性,以确保AuthenticationProvider仅接受由RunAsManager授权的具体实现创建的Authentication对象。

public class RunAsUserToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 原Authentication对象的类型private final Class<? extends Authentication> originalAuthentication;// 凭证private final Object credentials;// 主体private final Object principal;// 识别此对象是否由授权客户生成的key的hashCode private final int keyHash;// 构造方法public RunAsUserToken(String key, Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities,Class<? extends Authentication> originalAuthentication) {super(authorities);this.keyHash = key.hashCode();this.principal = principal;this.credentials = credentials;this.originalAuthentication = originalAuthentication;setAuthenticated(true);}// 返回凭证@Overridepublic Object getCredentials() {return this.credentials;}// 返回keyHash属性public int getKeyHash() {return this.keyHash;}// 返回原Authentication对象的类型public Class<? extends Authentication> getOriginalAuthentication() {return this.originalAuthentication;}// 返回主体@Overridepublic Object getPrincipal() {return this.principal;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder(super.toString());String className = this.originalAuthentication == null ? null: this.originalAuthentication.getName();sb.append("; Original Class: ").append(className);return sb.toString();}
}

JaasAuthenticationToken

UsernamePasswordAuthenticationToken的扩展,用来携带用户登录的Jaas LoginContext

public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 用户登录的Jaas LoginContextprivate final transient LoginContext loginContext;// 构造函数public JaasAuthenticationToken(Object principal, Object credentials,LoginContext loginContext) {super(principal, credentials);this.loginContext = loginContext;}// 构造函数public JaasAuthenticationToken(Object principal, Object credentials,List<GrantedAuthority> authorities, LoginContext loginContext) {super(principal, credentials, authorities);this.loginContext = loginContext;}// 返回用户登录的Jaas LoginContextpublic LoginContext getLoginContext() {return loginContext;}
}

Debug分析

项目结构图:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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: kavenpassword: itkaven
logging:level:org:springframework:security: DEBUG

SecurityConfigSpring Security的配置类,不是必须的,因为会有默认的配置):

package com.kaven.security.config;import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {// 任何请求都需要进行验证http.authorizeRequests().anyRequest().authenticated().and()// 记住身份验证.rememberMe(Customizer.withDefaults())// 基于表单登陆的身份验证方式.formLogin(Customizer.withDefaults());}
}

MessageController(定义接口):

package com.kaven.security.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MessageController {@GetMapping("/message")public String getMessage() {return "hello spring security";}
}

启动类:

package com.kaven.security;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(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 Security:身份验证令牌Authentication介绍与Debug分析相关推荐

  1. Spring Security:身份验证入口AuthenticationEntryPoint介绍与Debug分析

    ExceptionTranslationFilter ExceptionTranslationFilter(Security Filter)允许将AccessDeniedException和Authe ...

  2. Spring Security:密码编码器PasswordEncoder介绍与Debug分析

    博主在之前已经介绍了Spring Security的用户UserDetails与用户服务UserDetailsService,本篇博客介绍Spring Security的密码编码器PasswordEn ...

  3. SmtpClient 身份验证失败(authentication failed) 的原因分析

    最近,收到几位网友的咨询,都说使用 SmtpClient 通过 139,189,qq 邮箱的SMTP服务,发送邮件时,都不成功,返回的错误是 "身份验证失败",而同样的情况,使用o ...

  4. java spring 登录验证_浅析Spring Security登录验证流程源码

    一.登录认证基于过滤器链 Spring Security的登录验证流程核心就是过滤器链.当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证,就可以访问API接口了. SpringS ...

  5. Spring Security OAuth2 Opaque 令牌的简单使用指南

    Spring Security OAuth2 Opaque 令牌的简单使用指南 概述 JWT 是一种以广泛接受的 JSON 格式安全传输敏感信息的方法.包含的信息可能是关于用户的,也可能是关于令牌本身 ...

  6. 最简单易懂的Spring Security 身份认证流程讲解

    最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...

  7. python调用api做用户登录认证_(二)Python调用Zabbix api之从入门到放弃——登录并获取身份验证令牌...

    x.x.x.x可能是你的IP或者域名 访问流程概览: 1.首先登录 2.认证成功后zabbix server返回一个token 3.带着这个token去访问各种数据,做各种操作 4.完毕! 一.用RE ...

  8. eap aka_使用API​​密钥(aka身份验证令牌)部署到Maven Central

    eap aka 如何在不使用未加密本地密码的情况下与Maven Central / Nexus通信(尤其是使用Gradle,但不仅限于此). 基本原理 不幸的是,Gradle(和许多其他构建工具)没有 ...

  9. 使用API​​密钥(aka身份验证令牌)部署到Maven Central

    如何在不使用未加密本地密码的情况下与Maven Central / Nexus通信(尤其是使用Gradle,但不仅限于此). 基本原理 不幸的是,Gradle(和许多其他构建工具)没有提供任何机制来本 ...

最新文章

  1. etcd 指定配置文件启动_ETCD 简介 + 使用
  2. Android学习笔记(七):多个Activity和Intent
  3. boost::polygon模块voronoi相关的测试程序
  4. Redis 基本操作一
  5. 数据库概念 MySQL 库操作 表操作 记录操作
  6. 一 手游开发工具cocos2d-x editor初识
  7. Windows下编译配置Caffe的GPU版本
  8. 1000道Python题库系列分享20(43道填空与判断题)
  9. SylixOS中MTD调用底层接口流程分析
  10. java请求超时异常捕获_我异常了,快来捕获我,Java异常简述
  11. RHEL5中配置vsftpd搭建FTP服务器
  12. 支持移动触摸的jQuery图片Lightbox插件 1
  13. vscode安装旧版本插件_vscode安装和安装插件
  14. html日期选择限制,element-ui 日期选择器范围时间限制
  15. PicoNeo开发中遇到的问题(一)
  16. 当 SegmentFault 遇上呼伦贝尔
  17. mysql sql explain_MYSQL explain是用来分析sql语句
  18. python xls和xlsx互转
  19. 通用的一阶IIR数字高通滤波器的实现
  20. Virtualbox 启用USB 设备支持

热门文章

  1. 网络编程(51)—— Windows下使用select进行IO复用
  2. preload与prefetch对比
  3. 爱情心理学3(程序员值得拥有)
  4. 我想要的,到底是什么
  5. 大数据实时计算Spark学习笔记(9)—— Spar SQL(1) 读取 json 文件
  6. vue 的生命周期函数
  7. canvas 擦除动画_用HTML5 Canvas 做擦除及扩散效果
  8. 异常检测算法分类总结
  9. Linux -- 利用IPS(入侵防御系统) 构建企业Web安全防护网
  10. 洛谷 P3868 [TJOI2009]猜数字【中国剩余定理】