本文正在参与「金石计划 . 分割6万现金大奖」

铢积寸累,积习沉舟

前言

现已写了好几篇关于 Spring Security 的文章了,相信许多读者仍是对 Spring Security 的云里雾里的。这是因为对 Spring Security 中的目标还不了解。本文就来介绍介绍一下常用目标。

认证流程

SecurityContextHolder

用户认证经过后,为了避免用户的每次操作都进行认证,可将用户的信息保存在会话中。Spring Security 供给会话办理,认证经过后将身份信息放入 SecurityContextHolder 上下文,SecurityContext 与当时线程进行绑定,方便获取用户身份。

// 获取当时登录的用户信息
Authentication authentication = 
SecurityContextHolder.getContext().getAuthentication();

AuthenticationManager

认证办理器,AuthenticationManager 是认证相关的中心接口,是建议认证的进口,用于处理认证恳求。接口只供给了一个认证办法,办法接纳一个未经过认证 Authentication 目标,回来一个经过认证的 Authentication 目标。最常见的完结是ProviderManager

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

ProviderManager

供给商办理器,ProviderManagerAuthenticationManager 的一个完结类,供给了基本的认证逻辑和办法。它其间包括了一个 List 的 AuthenticationProvider 的特点,该特点寄存多种认证办法!为什么需求这个特点呢?当Spring Security默许供给的认证办法不能满意需求时,就能够经过 AuthenticationProvider 接口来扩展出其他认证办法,比方邮箱+验证码,手机号码+验证码登录。

AuthenticationProvider

AuthenticationProvider(身份验证供给者),能够将多个AuthenticationProvider实例添加到ProviderManager中。其每个AuthenticationProvider能够履行特定的 Authentication (身份验证)类型。例如:DaoAuthenticationProvider支撑依据用户名+暗码的 UsernamePasswordAuthenticationToken 身份验证。也能够自界说认证办法,比方自界说EmailVerificationCodeAuthenticationProvider支撑邮箱 + 验证码的 EmailVerificationCodeAuthenticationToken 身份验证。

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    boolean supports(Class<?> authentication);
}

该接口中有两个办法,如下:

  • authenticate() 办法接纳一个未经过认证 Authentication 目标,回来一个经过认证的 Authentication 目标。能够完结 authenticate() 办法来自界说身份验证逻辑。

  • supports(Class<?> authentication) 办法接纳一个 Authentication(身份验证) 目标,如果 AuthenticationProvider 支撑指定的身份验证目标,则回来 true。 可是回来 true 并不保证 AuthenticationProvider 能够对供给的 Authentization 类实例进行身份验证。它仅仅标明它能够支撑对其进行更深化的验证。AuthenticationProvider 仍能够从 authenticate() 办法回来 null,以测验其他的 AuthentitationProvider 进行验证。

Authentication

Authentication(身份验证) 接口是 Spring Security 中身份验证流程的顶级接口,该接口界说了如下办法:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

办法意义如下:

办法 描绘
getAuthorities 获取登录用户的权限列表
getCredentials 获取凭据。用户暗码登录,这个字段便是暗码信息,在认证过后一般会被移除,用于保障安全。如果是手机号验证码登录,那这个字段存的便是验证码
getDetails 包括了一些认证时的信息,默许的完结为 WebAuthenticationDetails,记录了拜访者的长途地址和sessionId的值。
getPrincipal 身份信息,默许情况下回来的是 UserDetails的实例
isAuthenticated 是否经过认证,经过认证为 true
setAuthenticated 设置是否已认证
getName 用户名

详细呼应内容能够参阅如下:

{
	"authorities": [
		{
			"authority": "ROLE_admin"
		},
		{
			"authority": "ROLE_user"
		}
	],
	"details": {
		"remoteAddress": "0:0:0:0:0:0:0:1",
		"sessionId": "D77AF630A476DEE7A2A75B1D751C4CF1"
	},
	"authenticated": true,
	"principal": {
		"password": null,
		"username": "cxyxj",
		"authorities": [
			{
				"authority": "ROLE_admin"
			},
			{
				"authority": "ROLE_user"
			}
		],
		"accountNonExpired": true,
		"accountNonLocked": true,
		"credentialsNonExpired": true,
		"enabled": true
	},
	"credentials": null,
	"name": "cxyxj"
}

Authentication 本身是一个接口,它有许多完结类:

Spring Security 中重要对象汇总
在众多的完结类中,咱们最常用的便是 UsernamePasswordAuthenticationToken(用户名暗码身份验证令牌),可是这个类就只有简略的50行左右的代码,其间有两个特点,principal 代表用户名,credentials 代表暗码。还有两个结构办法,一个是代表未认证的,一个是代表已认证的;三个set、get办法,一个擦除凭据办法。该类承继了 AbstractAuthenticationToken,其大部分逻辑在父类中,当然父类的逻辑也十分简略。 所以 UsernamePasswordAuthenticationToken(用户名暗码身份验证令牌)的作用便是将用户输入的用户名和暗码进行封装,并供给 AuthenticationManager 进行验证。

UserDetails

这个接口界说了用户的中心信息,比方用户名、暗码、账号是否过期、是否确认等!默许完结类org.springframework.security.core.userdetails.User。在 Spring Security 中,如果自界说认证逻辑时,需求完结该接口进行扩展,来保存自己体系的用户信息。接口界说如下办法:

办法 描绘
getAuthorities 获取用户权限
getPassword 获取用户暗码
getUsername 获取用户名
isAccountNonExpired 账户是否未过期,true:未过期,false:过期
isAccountNonLocked 账户是否未确认,true:未确认,false:确认
isCredentialsNonExpired 凭据(暗码)是否未过期,true:未过期,false:过期
isEnabled 账户是否启用,true:启用,false:禁用

一个正常能登录的账号,四个状态都是为 true 的。

UserDetailsService

在 Spring Security 中,什么也不进行装备时,账号和暗码是由 Spring Security 自动生成的。 但在实际的项目中账号、暗码是从数据库中查询出来的。所以咱们需求自界说认证逻辑。 此刻需求完结 UserDetailsService 接口。而 UserDetailsService 接口中只界说了一个办法,作用是依据用户名加载用户,取得 UserDetails 目标。

public interface UserDetailsService {
        // 按用户名加载用户
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

能够参阅自界说逻辑如下:

@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
    LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.eq(SysUser::getAccount, account);
    // 依据用户名查询用户
    SysUser sysUsers = sysUserMapper.selectOne(queryWrapper);
    if (Objects.isNull(sysUsers)) {
        Assert.isTrue(true,"用户名或许暗码错误");
    }
    // 取得用户人物信息
    List<String> roles = sysUserMapper.selectByUserId(sysUsers.getUserId());
    // 构建 SimpleGrantedAuthority 目标
    List<SimpleGrantedAuthority> authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    return new SysUserDetails(sysUsers, authorities);
}

除了需求手动完结 UserDetailsService 接口的办法外,Spring Security 也内置了几种办法。 咱们来看下 UserDetailsService 都有哪些完结类:

Spring Security 中重要对象汇总

  • InMemoryUserDetailsManager:内存用户,这种办法在学习 Spring Security 的时候,用的十分多。

  • JdbcUserDetailsManager:经过 JDBC 的办法将数据库和 Spring Security 连接起来。它自己供给了一个数据库脚本,脚本途径如下:org/springframework/security/core/userdetails/jdbc/users.ddl。脚本的内容呢,应该是不符合项目实际开发的,所以这个也是在学习的时候能够运用运用。

PasswordEncoder

PasswordEncoder 接口用于履行暗码的单向转化,以便安全地存储暗码。

InteractiveAuthenticationSuccessEvent

身份验证成功后,发布一个名为InteractiveAuthenticationSuccessEvent的事件告诉给到运用上下文,用于告知身份验证现已成功。

FilterChainProxy

在 Spring Security 的默许装备中,将创立一个名为 springSecurityFilterChain 的 servlet 过滤器作为bean。默许情况下,Spring Security 内置了一个过滤链,链中有 15 个过滤器。

Spring Security 中重要对象汇总
想要了解更多请前往: 深化理解 FilterChainProxy【源码篇】

ExceptionTranslationFilter

ExceptionTranslationFilter 反常转化过滤器坐落整个 springSecurityFilterChain 的后方,用来转化整个链路中出现的反常。此过滤器本身不处理反常,而是将认证进程中出现的反常交给内部保护的一些类去处理,一般处理两大类反常:AccessDeniedException 已登录无权限拜访反常和 AuthenticationException 未认证拜访反常。

HeaderWriterFilter

用来给http呼应添加一些Header,比方X-Frame-Options, X-XSS- Protection*,X-Content-Type-Options.

CsrfFilter

用于避免csrf进犯(跨站点恳求伪造(Cross- site request forgery))。

LogoutFilter

处理注销的过滤器。

RequestCacheAwareFilter

内部保护了一个RequestCache,用于缓存request恳求。

SecurityContextHolderAwareRequestFilter

对ServletRequest进行了一次包装,使得request 具有愈加丰富的API。

SessionManagementFilter

和session相关的过滤器,内部保护了一个 SessionAuthenticationStrategy,两者组合运用,常用来避免会话固定进犯保护( session- fixation protection attack ),以及约束同一用户敞开多个会话的数量。

AnonymousAuthenticationFilter

匿名身份过滤器,spring security为了兼容未登录的拜访,也走了一套认证流程,只不过是一个匿名的身份。

UsernamePasswordAuthenticationFilter

表单提交了username和password参数,被封装成token进行一系列的认证,便是首要经过这个过滤器完结的。在表单认证的流程中,这是最最要害的过滤器。

AbstractAuthenticationProcessingFilter

翻译为:抽象身份验证处理过滤器,这是一个抽象类,界说了认证处理的进程。是一个模板类。默许的完结为 UsernamePasswordAuthenticationFilter,依据用户名、暗码进行身份验证,如果需求自界说身份验证,比方手机验证码登录,就需求承继该类。

授权

当用户拜访 Spring Security 中一个受保护的资源时,需求运用投票器和表决机制,投票器依据用户的人物投出赞成或许反对票,表决办法则依据投票器的结果进行表决。

AccessDecisionManager

拜访决议计划办理器,AccessDecisionManagerAbstractSecurityInterceptor调用。AccessDecisionManager 采用投票的办法来确认是否能够拜访受保护资源。 AccessDecisionManager 中包括的多个 AccessDecisionVoter,Voter 将会被用来对Authentication是否有权拜访受保护目标进行投票, AccessDecisionManager 依据投票结果,做出最终决议计划。

Spring Security 中重要对象汇总
AccessDecisionManager 拜访决议计划办理器还有三个子类决议计划器,分别是:

  • AffirmativeBased:存在多个投票器时,有一个投票器赞同,则恳求就答应拜访,也便是一票经过;默许运用的决议计划器。
  • UnanimousBased:存在多个投票器时,如果有投票器回绝,则恳求不答应拜访,也便是一票否决。
  • ConsensusBased:存在多个投票器时,大多数投票器赞同。则恳求就答应拜访,也便是少数服从多数。如果是平局,则看 allowIfEqualGrantedDeniedDecisions 的值来判别是否经过,在默许情况下allowIfEqualGrantedDeniedDecisions值是true,也便是说平票的情况下,恳求答应拜访。

留意:不论是哪个决议计划器,如果一切投票器全部放弃则表明经过。

AccessDecisionVoter

翻译为:投票机制/投票器。在 Spring Security 中,投票机制是由 AccessDecisionVoter 接口来界说的,它有许多的完结:

Spring Security 中重要对象汇总
完结有好多种,咱们能够选择其间一种或多种投票机制,也能够自界说投票机制,默许的投票机制是 WebExpressionVoter。

public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}
  • 三个常量意义分别为:1 表明赞同;0 表明放弃;-1 表明回绝。
  • 两个 supports 办法用来判别投票器是否支撑当时恳求。
    1. vote 则是详细的投票办法;在不同的完结类中完结。authentication 表明当时登录主体;object 表明正在调用的受保护接口;attributes 表明当时所拜访的接口所需求的人物调集。

Spring Security 中重要对象汇总

  • RoleVoter:判别当时恳求是否具有该接口所需求的人物。
  • RoleHierarchyVoter 是 RoleVoter 的一个子类,在 RoleVoter 人物判别的基础上,引入了人物分层办理,也便是人物承继。
  • WebExpressionVoter:依据表达式权限控制
  • Jsr250Voter:处理 Jsr-250 权限注解的投票器,如@PermitAll@DenyAll
  • PreInvocationAuthorizationAdviceVoter:运用 @PreFilter 和 @PreAuthorize 注解处理的权限,经过 PreInvocationAuthorizationAdvice 来授权。

AbstractSecurityInterceptor

依据注释翻译了一下:为安全目标完结安全阻拦的抽象类,干的事情说白点便是对未放行的资源,依据用户的权限来控制是否能拜访的阻拦器。因为这是一个抽象类,所以只界说了一些逻辑办法,详细履行都是子类去调用的。

Spring Security 中重要对象汇总

FilterSecurityInterceptor

从 FilterChainProxy 章节中的截图来看,FilterSecurityInterceptor 坐落 Spring Security Filter Chain 中的最终一个Filter。这是一个过滤器,它会阻拦HTTP恳求,进行鉴权处理。

MethodSecurityInterceptor

它还完结了 MethodInterceptor,所以这是一个办法阻拦器,依据 Spring AOP 完结了办法阻拦,对办法进行鉴权处理。比方在办法上标注了@RolesAllowed@PermitAll@PreAuthorize等等注解。

AspectJMethodSecurityInterceptor

承继了 MethodSecurityInterceptor,依据 Aspectj 完结办法阻拦。

区别

  • 一个是过滤器,一个是办法阻拦器,所以他们阻拦目标是不同,一个是阻拦Http恳求,一个是阻拦办法。
  • 两者都调用了父类的beforeInvocation办法,可是传入的参数是不一样的,FilterSecurityInterceptor传入的是FilterInvocation,MethodInterceptor传入的是MethodInvocation。
  • 两者保护的 SecurityMetadataSource 不一样,MethodSecurityInterceptor 中保护的是MethodSecurityMetadataSource,FilterSecurityInterceptor保护的是 FilterInvocationSecurityMetadataSource。

SecurityMetadataSource

获取授权装备的接口。能够自界说完结该接口,比方从数据库中加载ConfigAttribute。在 Spring Security中,给该接口供给了两个子类,承继图如下:

Spring Security 中重要对象汇总

  • MethodSecurityMetadataSource:由Spring Security Core界说,用于表明安全目标是办法调用(MethodInvocation)的安全元数据源;寄存的是一切类和办法,会依据当时履行的类和办法,去内存中遍历,查询到当时履行办法装备的权限注解,然后对其进行授权判别。

Spring Security 中重要对象汇总

  • FilterInvocationSecurityMetadataSource:由Spring Security Web界说,用于表明安全目标是Web恳求(FilterInvocation)的安全元数据源;寄存的是 HttpSecurity 装备类中装备的授权规矩。

Spring Security 中重要对象汇总
装备如下:

http.authorizeRequests()
    // 如果用户具有 admin 权限,就答应拜访。
    .antMatchers("/cxyxj/**").hasAuthority("ROLE_admin")
// 如果用户具有给定权限中某一个,就答应拜访。
    .antMatchers("/admin/demo").hasAnyAuthority("ROLE_admin", "ROLE_System")
// 如果用户具有 user 权限,就答应拜访。留意不需求手动写 ROLE_ 前缀,写了会报错
    .antMatchers("/security/**").hasRole("user")
//如果恳求是指定的 IP 就答应拜访。
    .antMatchers("/admin/demo").hasIpAddress("192.168.64.5")
    .anyRequest()   //其他恳求
    .authenticated(); //需求认证才干拜访

参阅文献

Spring Security 权限办理的投票器与表决机制


  • 如你对本文有疑问或本文有错误之处,欢迎谈论留言指出。如觉得本文对你有所帮助,欢迎点赞 + 保藏。