Spring Security小教程 Vol 5.核心组件AuthenticationManager专题

前言

咱们在某一期其实已经对Authentication身份验证中的主要组件进行过介绍,而且经过几期的分享让你们大体了解了Web应用大体利用核心进行身份验证的流程和相关的扩展点。 这一期咱们用一期的篇章把关注点放在Authentication身份验证核心最主要的几个服务AuthenticationManangerAuthenticationProviderUserDetailsService,三个顶层接口进行展开。经过Spring Security对这些接口服务的实现进行说明讲解。目的是为了未来客制化扩展核心服务作好知识储备。算法

第五期 核心组件AuthenticationManager专题

本期的任务清单

  1. AuthenticationMananger与ProviderMananger
  2. Authentication与AuthenticationProvider
  3. UserDetailsService接口和它的实现类

零、总体概述

本期的重点是Authentication身份验证几个核心服务接口:sql

  1. AuthenticationMananger
  2. AuthenticationProvide
  3. UserDetailsServic

额外的还包括两个负责封装用户身份信息的接口与类:数据库

  1. Authentication
  2. UserDetails

咱们回顾下前几期咱们在分享Spring Security核心组件时候曾用到过如下这张图比较重要的核心组件: bash

Spring Security核心组件

这一期的重点显而易见即是红框中身份验证部分的三个核心组件以及其相关的组件。网络

1、AuthenticationMananger与ProviderMananger

AuthenticationMananger做为整个身份验证核心最外层的封装负责与外部使用者进行交互。 AuthenticationMananger接口有且仅有一个对外的服务即是“身份验证”。这样是整个身份验证服务对外提供的服务接口。数据结构

Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
复制代码

外部使用者经过将身份验证的必要信息,好比用户名和密码封装一个Authentication传递、调用AuthenticationMananger的authenticate方法。若是没有返回异常和null值,那么验证服务即是完成。完成身份验证的Authentication不只包含了用户的身份验证信息,好比用户名,额外还会将该用户身份下全部对应的权限列表也一并封装返回。 框架

AuthenticationMananger的authenticate是整个验证服务的入口

身份信息交互的纽带:Authentication

在整个与外部使用交互的过程当中Authentication的职责有两个,第一个是封装了验证请求的参数,第二个即是封装了用户的权限信息。结合Authentication的接口设计便更加清晰了这样的设计意图:principal用于存放用户的身份标识信息,好比用户名,credentials用于存放用户的验证凭证好比密码,authorities用于存放用户的权限列表。而details则存放了除了用户名和密码其余可能会被用于身份验证的信息,好比应用限定用户的使用ip范围场景下,ip信息可能便会被存放在details作辅助的验证信息使用。 ide

Authentication设计

惟一的实现类:ProviderMananger

为了向外部提供身份验证服务,Spring Security中经过ProviderMananger实现了AuthenticationManager的身份验证接口。做为实现类ProviderMananger便不能和AuthenticationManager同样只关心惟一的抽象核心服务authenticate。在ProviderMananger为了管理外部输入与像外部返回的AuthenticationProviderMananger内部大体的工序以下:ui

  1. 首先,寻找能够进行验证当前外部输入Authentication形式的AuthenticationProvider;若是自身的providers中没法处理验证而且当前层次的Mananger还有父级的Mananger则向上传递,交由父层Mananger进行处理;
  2. 而后,由于details的信息是外部传入的,内部身份验证后的Authentication并不会从持久化或者其余数据源中携带,在返回前将details写入返回给外部的Authentication
  3. 最后,若是有必要则将外部身份验证请求中的敏感擦除,好比讲请求验证的密码置空。 了解了ProviderMananger完成的三件工做,大体明白了虽然整个验证框架只有一个ProviderManager暴露在外部,可是其内部多是有多个AuthenticationManangerAuthenticationProvider组成的网络,而且最终进行核心身份验证的仍是AuthenticationProvider。核心在叶子节点中依次寻找对验证当前Authentication形式的AuthenticationProvider。若是存在支持便将验证请求的Authentication传递给AuthenticationProvider,委托其进行验证。在处理输入的验证请求AuthenticationProviderMananger并不对其进行任何的处理,而是指在处理完后进行必要的加工和处理。
    一个验证服务可能的组件结构

2、 Authentication与AuthenticationProvider

相对AuthenticationMananger而言AuthenticationProvider的工做更加明确:针对特定的验证数据,提供特定的验证行为。在这个语境下,Authentication的设计目的是解决验证什么(What)的问题,而authenticate方法更像是在回答怎么验证的问题(How)。加密

AuthenticationProvider视角中的Authentication

那么咱们先对验证数据也就是Authentication的设计进行展开讨论。Authentication主要职责就是封装身份验证时候须要的信息数据,好比用户名场景下的用户名和密码,短信验证码下的手机号码和验证码,OAuth2场景下的ID和Code。总之每一个不一样验证协议使用的验证信息都须要被被封装成Authentication,更准确说在Spring Security把这种封装了用户身份验证信息的Authentication具体为了\color{red}{AuthenticationToken}的概念,毕竟一说token更容易理解。全部Spring Security中提供的各类协议的身份验证数据的封装都继承\color{red}{AbstractAuthenticationToken},基于用户名和密码的UsernamePasswordAuthenticationToken,基于OAuth2的OAuth2AuthorizationCodeAuthenticationToken,基于CAS的CasAssertionAuthenticationToken。 一般咱们使用用户名和密码的场景是最多,不管是使用基于数据库持久化的用户名密码方案仍是基于LDAP的用户名和密码方法。虽然验证在验证协实现有细微差异,可是不管使用验证LDAP仍是数据库进行身份验证比对,由于用户提交的验证身份信息几乎一致,咱们即可以复用通用结构的AuthenticationToken——将username赋值到principal属性并将password赋值到cencredentials属性中。这样就意味着咱们在AuthenticationToken设计上最须要考虑是数据的封装,而不是身份验证行为的实现。

各式各样的AuthenticationToken

咱们已经解决了第一个问题,在AuthenticationProvider验证数据放均可以经过传入Authentication的各类实现类AuthenticationToken进行获取。下一个问题即是AuthenticationProvider是如何进行身份验证的。 咱们假设的场景是须要对使用用户名和密码的UsernamePasswordAuthenticationToken进行验证。 在Spring Security针对UsernamePasswordAuthenticationToken进行身份验证的有主要有两个AuthenticationProvider:一个是基于Dao模型与数据层用户信息对比验证的DaoAuthenticationProvider,另一个是虽然一样使用用户名和密码,可是验证流程更加复杂,且用户数据是经过与LDAP服务进行用户验证的LdapAuthenticationProvider。在这里使用最普遍使用的基于Dao的DaoAuthenticationProvider进行说明,若是有对Ldap实现有兴趣的相信在看完对DaoAuthenticationProvider的分析以后再阅读LdapAuthenticationProvider部分的代码就会轻松许多。

DaoAuthenticationProvider与LdapAuthenticationProvider都基于UsernamePasswordAuthenticationToken的形式进行验证

DaoAuthenticationProvider

DaoAuthenticationProvider为了实现外部的验证请求便须要对外部传递身份信息——用户名和密码进行验证。咱们把这个任务进一步分解成两个独立的任务:

  1. 从数据层获取对应用户名在数据层的数据记录;
  2. 对外部的用户名、密码与数据层的用户名、密码进行比对。

为何在DaoAuthenticationProvider会将验证任务再分解成这两个独立任务,最大缘由即是,这两个任务一个感知外部资源,另外一个感知验证算法,两种都是不一样用户可能存在不一样的使用场景,框架并没有法控制具体的实现。 第一个任务,从数据层获取对应用户名在数据层的数据记录,咱们的目标是从数据层中查找到咱们须要比对的用户身份数据,可是在这个场景下咱们无非控制的是数据层的实现具体是什么?是经过JDBC访问Mysql仍是经过JPA访问Oracle,更或是直接经过内存访问一个存储了用户信息键值对的Map? 第二个任务,对外部的用户名、密码与数据层的用户名、密码进行比对,具体的加密算法是什么?如何实现的? 这两个问题在DaoAuthenticationProvider中都没法给出明确的实现。Spring Security便将这两种在DaoAuthenticationProvider没法肯定、存在变化的行为分别委托给了DaoAuthenticationProvider两个重要组件去完成:

  1. 经过用户名返回数据层中的用户信息的UserDetailsService;
  2. 经过特定加密算法处理用户密码的PasswordEncoder。
    DaoAuthenticationProvider中的两个组件

使用UserDetailsService获取内部用户身份信息

UserDetailsService接口定位从他的接口方法就能够明白,就是向核心组件们提供数据层的用户信息,而用户信息在这里被封装成了UserDetails。

UserDetails与UserDetailsService
UserDetailsService中只有一个方法即是loadUserByUsername方法,经过传入用户名返回数据层的用户身份记录。

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

复制代码

当咱们肯定咱们获取用户身份信息的方法以后,咱们即可以自行扩展UserDetailsService方法,告知框架如何获取用户身份信息。一般这个步骤是使用Spring Security中是必须完成的工做。 咱们在第一个章节中曾经写过如下代码用于配置咱们使用的UserDetailsService:

public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    //注入新的UserDetailsServiceBean
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        return manager;
    }
}

复制代码

那一次咱们使用了基于内存键值对的形式来存储和获取用户信息记录。一样的咱们也能够经过JDBC和JPA来获取用户身份信息,而Spring Security很贴心的已经提供了一个基于JDBC的UserServiceDetails实现和对应的模板DDL:

JBCD的UserServiceDetails实现JdbcImpl

create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
复制代码

而经过UserDetailsService返回的数据类型是UserDetails,UserDetails中封装了用户名、密码和受权信息同时还额外包括了一些过时和锁定的标识属性。咱们不难发现UserDetails封装的数据和Authentication很是的类似。没错,在身份验证成功后,DaoAuthenticationProvider便会将内部的UserDetails抽离必要的数据对应赋值到UsernamePasswordAuthenticationToken最终返回给外部调用者进行使用。咱们能够简单的把UserDetails理解为用户身份信息在数据层的封装。在客制化的过程当中,若是用户信息的数据结构是比较特殊的结构,好比Ldap,那么即可以自行扩展UserDetails客制化一个特殊的结构用于获取用户数据记录。 说到这里基本上咱们已经了解了DaoAuthenticationProvider两个组件中用于获取用户身份记录的UserDetailsService部分,下面咱们介绍下处理密码验证的PasswordEncoder部分。

使用PasswordEncoder来进行密码的比对

咱们继续追踪上面的场景来讲下关于验证算法部分。DaoAuthenticationProvider收到了外部提交的用户名和密码,一样的DaoAuthenticationProvider也查找到了对应用户名在数据库中的用户名和密码。一般状况下虽然都是密码,数据库中存储的密码一般会进行过必定的加密。DaoAuthenticationProvider便须要将外部提交的用户名和密码进行一次加密流程并进行比对。举个例子咱们当前使用的算法好比是MD5,加密明文的样本是用户的用户名拼接用户名。若有当前须要身份验证的请求中用户名是admin,密码是password。一样的在数据库中加密后的密码是9b02edfbc208a538。咱们便须要对外部传递的用户名和密码作一个MD5("passwordadmin")获得9b02edfbc208a538,再与数据库中的password字段进行对比,若是一致则认定验证成功。 在Spring Security中这种针对处理称为\color{red}{PasswordEncoder},PasswordEncoder接口主要的做用就是对明文密码进行加密与比对。

PasswordEncoder接口
经过PasswordEncoder比对密码的示意图

Spring Security中默认向DaoAuthenticationProvider提供的PasswordEncoder是BCryptPasswordEncoder。若是对BCrypt能够额外经过谷歌去了解加密流程。 若是咱们须要客制化本身的加密算法,只要实现PasswordEncoder接口,并从新经过Spring注入DaoAuthenticationProvider即可以了。

@Bean
public PasswordEncoder passwordEncoder() {
    //经过修改注入的实例,客制化本身的PasswordEncoder
    return new BCryptPasswordEncoder();
}
复制代码

PasswordEncoder 部分的功能相对较少,一般状况下使用默认提供的BCryptPasswordEncoder就足够完成任务。

结尾

在本期咱们花了很大篇幅介绍了身份验证核心中最主要个几个组件和基于一个使用用户名和密码验证场景下对应接口的实现类的具体职责:

  1. AuthenticationManager负责核心验证先后的处理,而且负责与外部调用者进行交互;
  2. AuthenticationProvider是验证服务的核心实现,其验证的数据形式是AuthenticationToken其中封装了验证使用的用户标识和用户验证凭证信息;
  3. AuthenticationProvider中获取内部用户身份信息是经过UserDetailsService完成的。如须要进行密码处理,则引入了PasswordEncoder;
  4. UserDetails封装了用户信息在内部的结构,在向外部返回Authentication以前,AuthenticationProvider一般会将UserDetails必要的数据复制到向外部返回的AuthenticationToken中。

经过几期的说明,整个Spring Security关于身份验证的组件、流程和特定场景的实现基本咱们都了解了一遍。从下一期开始,咱们会开始讲解访问控制部分、框架配置部分的设计与概念。同时也将不按期经过一些场景的实战强相关框架概念和设计理念。 谢谢你们,咱们下期再见。

相关文章
相关标签/搜索