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

#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;
	// 是否已验证,默认为false
	private 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接口来抽象用户()。- 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属性,只能设置为false
	public 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
	@Override
	public 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的hashCode
	private 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
	 */
	@Override
	public Object getCredentials() {<!-- -->
		return "";
	}

    // 返回keyHash 
	public int getKeyHash() {<!-- -->
		return this.keyHash;
	}

    // 返回主体
	@Override
	public 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 implements
		Serializable {<!-- -->

	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的hashCode
	private static Integer extractKeyHash(String key) {<!-- -->
		Assert.hasLength(key, "key cannot be empty or null");
		return key.hashCode();
	}
	
	/**
	 * 总是返回一个空String
	 */
	@Override
	public Object getCredentials() {<!-- -->
		return "";
	}

    // 返回keyHash
	public int getKeyHash() {<!-- -->
		return this.keyHash;
	}

    // 返回主体
	@Override
	public 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);
	}

    // 返回凭证
	@Override
	public Object getCredentials() {<!-- -->
		return this.credentials;
	}
    
    // 返回keyHash属性
	public int getKeyHash() {<!-- -->
		return this.keyHash;
	}

    // 返回原Authentication对象的类型
	public Class<? extends Authentication> getOriginalAuthentication() {<!-- -->
		return this.originalAuthentication;
	}

    // 返回主体
	@Override
	public Object getPrincipal() {<!-- -->
		return this.principal;
	}

	@Override
	public 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 LoginContext
	private 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 LoginContext
	public 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: kaven
      password: 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 {<!-- -->

    @Override
    protected 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分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值