Spring Security:身份验证令牌Authentication介绍与Debug分析
在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();}}...
}
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
类型。
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
抽象类,用于支持RunAsManagerImpl
的Authentication
实现。
RunAsManagerImpl
类是RunAsManager
接口的基本实现,如果发现ConfigAttribute.getAttribute()
以RUN_AS_
为前缀,它会生成一个新的RunAsUserToken
实例,包含与原始Authentication
实例相同的主体、凭证和授予权限列表等。
RunAsManager
接口仅为当前安全对象调用创建一个新的临时Authentication
实例,此接口允许实现替换,仅适用于当前安全对象调用的Authentication
对象。
这是为了建立具有两层对象的系统,一层是面向公众的,并且具有正常的安全方法,授予的权限预计由外部调用者持有。 另一层是私有的,只希望由面向公众的层中的对象调用,此私有层中的对象仍然需要安全性(否则它们将是公共方法),需要防止被外部调用者直接调用,并且私有层中的对象被授予的权限从不授予外部调用者。
RunAsManager
接口提供了一种以上述方式提升安全性的机制。预计实现将提供相应的具体Authentication
和AuthenticationProvider
以便可以对替换的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
SecurityConfig
(Spring 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分析相关推荐
- Spring Security:身份验证入口AuthenticationEntryPoint介绍与Debug分析
ExceptionTranslationFilter ExceptionTranslationFilter(Security Filter)允许将AccessDeniedException和Authe ...
- Spring Security:密码编码器PasswordEncoder介绍与Debug分析
博主在之前已经介绍了Spring Security的用户UserDetails与用户服务UserDetailsService,本篇博客介绍Spring Security的密码编码器PasswordEn ...
- SmtpClient 身份验证失败(authentication failed) 的原因分析
最近,收到几位网友的咨询,都说使用 SmtpClient 通过 139,189,qq 邮箱的SMTP服务,发送邮件时,都不成功,返回的错误是 "身份验证失败",而同样的情况,使用o ...
- java spring 登录验证_浅析Spring Security登录验证流程源码
一.登录认证基于过滤器链 Spring Security的登录验证流程核心就是过滤器链.当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证,就可以访问API接口了. SpringS ...
- Spring Security OAuth2 Opaque 令牌的简单使用指南
Spring Security OAuth2 Opaque 令牌的简单使用指南 概述 JWT 是一种以广泛接受的 JSON 格式安全传输敏感信息的方法.包含的信息可能是关于用户的,也可能是关于令牌本身 ...
- 最简单易懂的Spring Security 身份认证流程讲解
最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...
- python调用api做用户登录认证_(二)Python调用Zabbix api之从入门到放弃——登录并获取身份验证令牌...
x.x.x.x可能是你的IP或者域名 访问流程概览: 1.首先登录 2.认证成功后zabbix server返回一个token 3.带着这个token去访问各种数据,做各种操作 4.完毕! 一.用RE ...
- eap aka_使用API密钥(aka身份验证令牌)部署到Maven Central
eap aka 如何在不使用未加密本地密码的情况下与Maven Central / Nexus通信(尤其是使用Gradle,但不仅限于此). 基本原理 不幸的是,Gradle(和许多其他构建工具)没有 ...
- 使用API密钥(aka身份验证令牌)部署到Maven Central
如何在不使用未加密本地密码的情况下与Maven Central / Nexus通信(尤其是使用Gradle,但不仅限于此). 基本原理 不幸的是,Gradle(和许多其他构建工具)没有提供任何机制来本 ...
最新文章
- etcd 指定配置文件启动_ETCD 简介 + 使用
- Android学习笔记(七):多个Activity和Intent
- boost::polygon模块voronoi相关的测试程序
- Redis 基本操作一
- 数据库概念 MySQL 库操作 表操作 记录操作
- 一 手游开发工具cocos2d-x editor初识
- Windows下编译配置Caffe的GPU版本
- 1000道Python题库系列分享20(43道填空与判断题)
- SylixOS中MTD调用底层接口流程分析
- java请求超时异常捕获_我异常了,快来捕获我,Java异常简述
- RHEL5中配置vsftpd搭建FTP服务器
- 支持移动触摸的jQuery图片Lightbox插件 1
- vscode安装旧版本插件_vscode安装和安装插件
- html日期选择限制,element-ui 日期选择器范围时间限制
- PicoNeo开发中遇到的问题(一)
- 当 SegmentFault 遇上呼伦贝尔
- mysql sql explain_MYSQL explain是用来分析sql语句
- python xls和xlsx互转
- 通用的一阶IIR数字高通滤波器的实现
- Virtualbox 启用USB 设备支持