文章目录

  • Authentication(身份认证框架)的架构
    • SecurityContextHolder
    • AuthenticationManager
    • AbstractAuthenticationProcessingFilter
  • 关于密码存储
    • 密码存储的历史
    • Spring Scurity中的密码存储机制
  • 用户/密码认证
    • 根据HttpRequest提取用户密码的方式区分
      • FormLogin 页面表单登陆
        • 默认页面登陆认证
        • 自定义登陆页面
      • Basic Authentication
    • 根据用户密码不同的存储方式实现区分
      • In-Memory Authentication
      • JDBC Authentication
      • 自定义UserDetailsService

Authentication(身份认证框架)的架构

SecurityContextHolder

SpringSecurity会将用户的信息存储在SecurityContextHolder中,以下为SecurityContextHolder的模型

SecurityContextHolder中包含了SecurityContext对象,SecurityContext中包含了Authentication对象
Authentication对象的功能有两个:

  • 作为后续验证的入参
  • 获取当前验证通过的用户信息

Authentication包含三个属性:

  • principal:用户身份,如果是用户/密码认证,这个属性就是UserDetails实例
  • credentials:通常就是密码,在大多数情况下,在用户验证通过后就会被清除,以防密码泄露。
  • authorities:用户权限

AuthenticationManager

  • AuthenticationManager是用来实现身份认证的API接口,入参是Authentication,最常用的子类是ProviderManager
  • AuthenticationProvider是某种具体的认证实现,例如DaoAuthenticationProvider用来实现用户/密码认证,JwtAuthenticationProvider实现JWT Token认证
  • 支持多种类型AuthenticationProvider会注入到ProviderManager中,ProviderManager会根据Authentication的类型调用相应类型的AuthenticationProvider

AbstractAuthenticationProcessingFilter


AbstractAuthenticationProcessingFilter使用来进行用户认证的过滤器,会编排进SecurityFilterChain中,其处理流程为:

  1. 用户提交信息后,AbstractAuthenticationProcessingFilter 或从HttpServletRequest 提取信息并创建Authentication,Authentication的类型是由过滤器的类型决定的(AbstractAuthenticationProcessingFilter的子类),例如 UsernamePasswordAuthenticationFilter 创建 UsernamePasswordAuthenticationToken
  2. Authentication 被传入 the AuthenticationManager进行认证
  3. 如果认证失败
    (1)SecurityContextHolder 清除掉
    (2)RememberMeServices.loginFail 被调用,如果remember me 没有配置,则此方法为空方法
    (3)AuthenticationFailureHandler 被调用
  4. 认证成功
    SessionAuthenticationStrategy 被通知用户登陆.
    Authentication 存入 SecurityContextHolder. 之后 SecurityContextPersistenceFilter 会将SecurityContext存入HttpSession.
    RememberMeServices.loginSuccess 被调用,如果remember me没有配置,则改方法为空方法
    ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent.

用户密码验证 DaoAuthenticationProvider
DaoAuthenticationProvider实现了接口AuthenticationProvider,并且会使用UserDetailsService和PasswordEncoder来验证用户和密码

  1. 身份认证的过滤器会提取请求信息中的用户密码信息,传递UsernamePasswordAuthenticationToken(包含用户名和密码)对象至AuthenticationManager,其中ProviderManager是AuthenticationManager接口的实现类
  2. ProviderManager 中注册了DaoAuthenticationProvider(AuthenticationProvider接口的一个实现类,用于用户密码认证)
  3. DaoAuthenticationProvider 调用UserDetailsService获取存储的用户信息,得到UserDetails对象,UserDetails提供了对用户信息的基本操作
  4. DaoAuthenticationProvider 调用PasswordEncoder对传入的密码进行加密,并与上一步得到的UserDetails中的密码进行比较
  5. 验证通过后,用户信息会被存储于SecurityContextHolder中

关于密码存储

在介绍SpringSecurity认证机制之前,先说一些关于密码存储的内容。在SpringSecurity中PasswordEncoder接口用来提供单向加密机制,使密码存储更加安全。

密码存储的历史

在过去的多年时间里,密码存储机制进化了多次。最开始,密码就是明文文本存储,例如存储在数据库中,获取密码则需要使用数据库的用户与密码,因此人们则认为这种方式是安全的,但一些恶意用户却可以轻易的破解此方式,例如使用SQL注入等手段。

此后,密码存储前会通过单向Hash加密,例如SHA-256,当一个用户申请认证时,系统会对用户输入的密码进行hash加密,然后与系统存储的加密密码进行比较,如此系统只需要存储加密后的密文,这样即使密码泄露,也不会泄露真实的密码。不幸的是,黑客们发明了彩虹表(Rainbow Tables),通过查询彩虹表,很多密码都可以轻易的破解。

为了降低彩虹表的能力,安全专家建议使用加盐密码(salted passwords),在密码进行单向hash加密时,加入一些随机字符,这些随机字符被称为盐(salt)。

但近些年来,随着计算机硬件的快速发展,hash运算速度已提升为每秒十亿级,这意味着单向hash加密机制已经不再安全。现在更加推荐使用自适应的单向加密机制,自适应的单向加密机制可以配置一个工作因子(work factor),该因子会跟随硬件设备性能自行调整,使得加密过程所需要的时间在一个固定的值,这个值推荐在1秒钟左右,如此破解密码变得更加困难,常见的自适应加密机制包括bcrypt, PBKDF2, scrypt, 和 argon2

Spring Scurity中的密码存储机制

在Spring Security 5.0以前,默认的PasswordEncoder是NoOpPasswordEncoder,即明文密码,现在Spring Security的默认PasswordEncoder是DelegatingPasswordEncoder,DelegatingPasswordEncoder可以代理多种加密机制,其实例化过程如下:

public class DelegatingPasswordEncoderTest {public static void main(String[] args) {String idForEncode = "bcrypt";Map encoders = new HashMap<>();encoders.put(idForEncode, new BCryptPasswordEncoder());encoders.put("noop", NoOpPasswordEncoder.getInstance());encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());encoders.put("scrypt", new SCryptPasswordEncoder());encoders.put("sha256", new StandardPasswordEncoder());PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);//对密码进行加密String encodedPassword = passwordEncoder.encode("password");System.out.println(encodedPassword); //{bcrypt}$2a$10$8nfdxLtGwvDu4MRI.CpCmOg56d7zNyq2xVJqaO4b.OPDFjlsu47Am//验证密码boolean res = passwordEncoder.matches("password", "{bcrypt}$2a$10$550FMm3qENbQtIWOa6qfsuqBftBF3BfVibViHv2ueQ3wGs45jEPWu");System.out.println("res = " + res); // res = true}
}

密码的存储格式为,其中{id}为对应的加密算法:

{id}encodedPassword

以下为对字符串"password"加密后的各种算法的结果:

{bcrypt}$2a$10$4lVeyJaX4NeUGLcAOG1KsuhguK30dwxMt9vO4woC.5elEC2xufWSu
{noop}password
{pbkdf2}8c3682ac6c8a7285060cc984fe3cf6da2af1a98410ad244b8b2b90fb90f60665c437a5cff05682e3
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}9b050796bcdab7d9f9009669fac770584071e4057f4edae723e28096fe21910e7e95420647a2749f

用户/密码认证

根据HttpRequest提取用户密码的方式区分

FormLogin 页面表单登陆

默认页面登陆认证

在Springboot中引入Spring Security依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

启动服务,Spring Scurity就会自动创建一个页面登陆身份认证,默认的用户为user,密码会在启动日志中打印出来

输入任何地址,就会跳转至默认的登陆页面,输入用户密码后可以实现登陆。

自定义登陆页面

自定义页页面配置:HttpSecurity .formLogin().loginPage("/login") 指定了自定义的登陆页面,关于授访问控制相关的配置会在以后的章节进行介绍

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login");}
}

创建登陆页面的Controller

@Controller
public class LoginController {@RequestMapping("/login")public String login() {return "login";}
}

创建登陆页面,这里我使用的Themeleaf

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Please Log In</title>
</head>
<body>
<div style="width:500px;margin:0 auto"><h1>Please Log In</h1><div th:if="${param.error}">Invalid username and password.</div><div th:if="${param.logout}">You have been logged out.</div><form th:action="@{/login}" method="post"><div><input type="text" name="username" placeholder="Username"></div><div><input type="password" name="password" placeholder="Password"></div><input type="submit" value="Login"></form>
</div>
</body>
</html>

Basic Authentication

Basic Authentication是针对服务接口的身份认证,当用户的请求中没有携带用户/密码信息,或者密码不匹配时,Spring Security会在Http的response中增加 WWW-Authenticate头信息,客户端根据这个头信息判断是否认证通过。
Basic Authentication开启配置

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login") //指定自定义登陆页面.and().httpBasic(); //开启Basic Authentication}
}

开发一个restful接口进行验证

@Controller
public class LoginController {@RequestMapping("/basic")@ResponseBodypublic String basic() {return "hello";}
}

使用Postman发送get请求至接口,当不填写用户密码或者错误密码时,可以看到返回信息头中包含WWW-Authenticate

当填写用户密码信息后则可以正常访问。

根据用户密码不同的存储方式实现区分

In-Memory Authentication

InMemoryUserDetailsManager实现接口UserDetailsManager, UserDetailsManager接口继承UserDetailsService接口,支持查询存储在内存中的用户密码信息,也提供了堆UserDetails的管理

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login") //指定自定义登陆页面.and().httpBasic(); //开启Basic Authentication}/*** 用户密码存储在内存中* @return*/@Beanpublic UserDetailsService inMemoryUserDetailsManager() {UserDetails user = User.builder().username("user").password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq").roles("USER").build();UserDetails admin = User.builder().username("admin").password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq").roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);}
}

以上代码中使用的是Spring Security默认的PasswordEncoder
此种方式在维护用户信息时需要修改源代码并编译,有些繁琐,所以往往被使用在项目初期的验证阶段,并不推荐使用在正式的产品中。

JDBC Authentication


JdbcDaoImpl 实现接口UserDetailsService,可以通过JDBC查询用户密码信息
JdbcUserDetailsManager 继承了JdbcDaoImpl ,同时实现了UserDetailsManager,提供对UserDetails的管理

(1)根据官方文档创建用户表,这里使用PostgreSQL数据库

create table users(username varchar(50) not null primary key,password varchar(200) not null,enabled boolean not null
);create table authorities (username varchar(50) not null,authority varchar(200) not null,constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);

如果使用了用户组,还需要创建组信息表

create table groups (id bigint generated by default as identity(start with 1) primary key,group_name varchar(50) not null
);create table group_authorities (group_id bigint not null,authority varchar(50) not null,constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);create table group_members (id bigint generated by default as identity(start with 1) primary key,username varchar(50) not null,group_id bigint not null,constraint fk_group_members_group foreign key(group_id) references groups(id)
);

(2)创建数据源
这里使用的PostgreSQL数据库
首先添加maven依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId>
</dependency>

在application.yml中配置数据库

spring:datasource:url: jdbc:postgresql://localhost:5432/postgresusername: postgrespassword: daiwldriver-class-name: org.postgresql.Driver

(3) 配置JdbcUserDetailsManager

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate DataSource dataSource; //注入数据源@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login") //指定自定义登陆页面.and().httpBasic(); //开启Basic Authentication}/*** 用户密码存储在内存中* @return*/
//    @Bean
//    public UserDetailsService inMemoryUserDetailsManager() {//        UserDetails user = User.builder()
//                .username("user")
//                .password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq")
//                .roles("USER")
//                .build();
//
//        UserDetails admin = User.builder()
//                .username("admin")
//                .password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq")
//                .roles("USER", "ADMIN")
//                .build();
//        return new InMemoryUserDetailsManager(user, admin);
//    }/*** JDBC Authentication* @param dataSource* @return*/@Beanpublic UserDetailsManager users(DataSource dataSource) {JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);//创建user和admin用户UserDetails user = User.builder().username("user").password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq").roles("USER").build();UserDetails jdbc= User.builder().username("jdbc").password("{bcrypt}$2a$10$XpaYxCM8xmpzfQYh/xw3ougC5MsADnED25UtGOxR3a3zbklhWpgtq").roles("USER", "JDBC").build();users.deleteUser("user");users.createUser(user);users.deleteUser("jdbc");users.createUser(jdbc);return users;}
}

以上代码在配置JdbcUserDetailsManager之后,立即创建了user和jdbc用户

自定义UserDetailsService

用户也可以通过实现UserDetailsService接口自定义身份认证,Spring Security提供了以上两种实现,In-memory和jdbc,如果不能满足需求,则可以自行定义:

@Bean
CustomUserDetailsService customUserDetailsService() {return new CustomUserDetailsService();
}

Spring Security身份认证Authentication相关推荐

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

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

  2. spring security 自定义认证登录

    spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...

  3. 浅析 Spring Security 的认证过程及相关过滤器

    前言 上一篇文章 浅析 Spring Security 核心组件 中介绍了Spring Security的基本组件,有了前面的基础,这篇文章就来详细分析下Spring Security的认证过程. S ...

  4. Spring Security用户认证和权限控制(默认实现)

    1 背景 实际应用系统中,为了安全起见,一般都必备用户认证(登录)和权限控制的功能,以识别用户是否合法,以及根据权限来控制用户是否能够执行某项操作. Spring Security是一个安全相关的框架 ...

  5. Spring Security 0auth2 认证服务器和资源服务器实现

    一,OAuth2开放授权协议/标准 OAuth(开放授权)是⼀个开放协议/标准,允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者 上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所 ...

  6. Spring Security CAS认证

    13.7 CAS认证 13.7.1概述 JA-SIG生产一种称为CAS的企业级单点登录系统.与其他计划不同,JA-SIG的中央身份验证服务是开源的,广泛使用的,易于理解,独立于平台,并支持代理功能.S ...

  7. jwt token注销_【原创精选】OAuth 2.0+JWT+spring security完成认证授权-生产级-附带源码...

    前言导读 分析一下为什么要用OAuth2和JWT来做 1. **单点登录(SSO)**方案单击登录方案是最常见的解决方案,但单点登录需要每个与用户交互的服务都必须与认证服务进行通信,这不但会造成重复, ...

  8. spring security:自定义认证成功处理器

    使用spring认证登录,登录之后,一般还需要进行其他处理,例如:保存登录时间.登录ip到数据库,缓存用户信息到redis数据库等等,这些操作可以通过自定义一个登录成功处理器来处理. 自定义认证成功处 ...

  9. Spring Security——自定义认证错误提示信息及自适应返回格式解决方案

    解决方案 package com.hailiu.web.handler;import com.hailiu.model.Log; import com.hailiu.web.bean.Response ...

最新文章

  1. 三星 Nexus S刷MIUI ROM最新图文刷机教程
  2. 【Linux系统编程】Linux 进程调度浅析
  3. 阿里云linux上安装与配置Mysql
  4. arm linux 开机电路_【技术角度看问题之一】ARM到底是个啥?
  5. 年度总结 | 小小的年度大总结!太精辟!
  6. c++ signal 信号退出
  7. 从高级程序员的角度来看,Rust 基础知识
  8. 京瓷打印机p5026cdn_京瓷ECOSYS P5026cdn驱动
  9. android 分区 PT,Android:pt 、sp、dp之间的换算
  10. 推荐10款实用必备的Windows10软件
  11. 计算机中sqrt函数是什么意思,sqrt是什么函数
  12. deepfake 图片_找到那张假照片!对抗Deepfake之路
  13. 试用74LS194加74151设计一个从Q0端输出100111序列信号的序列信号发生器. 要求电路能自启动, 且越简单越好
  14. jira后台统计数据的一些sql(包含reopen计算,时效)
  15. 神盾局第二季麦克机器人_神盾局特工第四季分集剧情介绍(1-22集)大结局
  16. matlab 中阿拉伯字母,常用阿拉伯字母念法
  17. ISO SAE 21434-2021 要求、建议、允许(RQ、RC、PM)整理
  18. HihoCoder 1245:王胖浩与三角形 三角形边长与面积
  19. Burp Suite 工具 目录
  20. 富文本编辑器 CKeditor 配置使用+上传图片

热门文章

  1. TCP/IP RFC如何查询
  2. Java 复制文件夹及文件
  3. Chrome浏览器的渲染原理
  4. 你是不是也被“羊了个羊”PUA了?
  5. xp系统怎么开启usb服务器,xp系统如何设置usb手机网络分享
  6. html视频静音代码,javascript – 如何使用静音道具取消静音html5视频
  7. 如何快速搭建私人博客
  8. BurpSuite2021系列(八)Intruder详解
  9. 【Rust 日报】2021-07-04 如何在面试中确定 Rust 程序员水平?
  10. eclipse 中文语言包的下载地址