Spring Security身份认证Authentication
文章目录
- 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中,其处理流程为:
- 用户提交信息后,AbstractAuthenticationProcessingFilter 或从HttpServletRequest 提取信息并创建Authentication,Authentication的类型是由过滤器的类型决定的(AbstractAuthenticationProcessingFilter的子类),例如 UsernamePasswordAuthenticationFilter 创建 UsernamePasswordAuthenticationToken
- Authentication 被传入 the AuthenticationManager进行认证
- 如果认证失败
(1)SecurityContextHolder 清除掉
(2)RememberMeServices.loginFail 被调用,如果remember me 没有配置,则此方法为空方法
(3)AuthenticationFailureHandler 被调用 - 认证成功
SessionAuthenticationStrategy 被通知用户登陆.
Authentication 存入 SecurityContextHolder. 之后 SecurityContextPersistenceFilter 会将SecurityContext存入HttpSession.
RememberMeServices.loginSuccess 被调用,如果remember me没有配置,则改方法为空方法
ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent.
用户密码验证 DaoAuthenticationProvider
DaoAuthenticationProvider实现了接口AuthenticationProvider,并且会使用UserDetailsService和PasswordEncoder来验证用户和密码
- 身份认证的过滤器会提取请求信息中的用户密码信息,传递UsernamePasswordAuthenticationToken(包含用户名和密码)对象至AuthenticationManager,其中ProviderManager是AuthenticationManager接口的实现类
- ProviderManager 中注册了DaoAuthenticationProvider(AuthenticationProvider接口的一个实现类,用于用户密码认证)
- DaoAuthenticationProvider 调用UserDetailsService获取存储的用户信息,得到UserDetails对象,UserDetails提供了对用户信息的基本操作
- DaoAuthenticationProvider 调用PasswordEncoder对传入的密码进行加密,并与上一步得到的UserDetails中的密码进行比较
- 验证通过后,用户信息会被存储于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相关推荐
- 最简单易懂的Spring Security 身份认证流程讲解
最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...
- spring security 自定义认证登录
spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...
- 浅析 Spring Security 的认证过程及相关过滤器
前言 上一篇文章 浅析 Spring Security 核心组件 中介绍了Spring Security的基本组件,有了前面的基础,这篇文章就来详细分析下Spring Security的认证过程. S ...
- Spring Security用户认证和权限控制(默认实现)
1 背景 实际应用系统中,为了安全起见,一般都必备用户认证(登录)和权限控制的功能,以识别用户是否合法,以及根据权限来控制用户是否能够执行某项操作. Spring Security是一个安全相关的框架 ...
- Spring Security 0auth2 认证服务器和资源服务器实现
一,OAuth2开放授权协议/标准 OAuth(开放授权)是⼀个开放协议/标准,允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者 上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所 ...
- Spring Security CAS认证
13.7 CAS认证 13.7.1概述 JA-SIG生产一种称为CAS的企业级单点登录系统.与其他计划不同,JA-SIG的中央身份验证服务是开源的,广泛使用的,易于理解,独立于平台,并支持代理功能.S ...
- jwt token注销_【原创精选】OAuth 2.0+JWT+spring security完成认证授权-生产级-附带源码...
前言导读 分析一下为什么要用OAuth2和JWT来做 1. **单点登录(SSO)**方案单击登录方案是最常见的解决方案,但单点登录需要每个与用户交互的服务都必须与认证服务进行通信,这不但会造成重复, ...
- spring security:自定义认证成功处理器
使用spring认证登录,登录之后,一般还需要进行其他处理,例如:保存登录时间.登录ip到数据库,缓存用户信息到redis数据库等等,这些操作可以通过自定义一个登录成功处理器来处理. 自定义认证成功处 ...
- Spring Security——自定义认证错误提示信息及自适应返回格式解决方案
解决方案 package com.hailiu.web.handler;import com.hailiu.model.Log; import com.hailiu.web.bean.Response ...
最新文章
- 三星 Nexus S刷MIUI ROM最新图文刷机教程
- 【Linux系统编程】Linux 进程调度浅析
- 阿里云linux上安装与配置Mysql
- arm linux 开机电路_【技术角度看问题之一】ARM到底是个啥?
- 年度总结 | 小小的年度大总结!太精辟!
- c++ signal 信号退出
- 从高级程序员的角度来看,Rust 基础知识
- 京瓷打印机p5026cdn_京瓷ECOSYS P5026cdn驱动
- android 分区 PT,Android:pt 、sp、dp之间的换算
- 推荐10款实用必备的Windows10软件
- 计算机中sqrt函数是什么意思,sqrt是什么函数
- deepfake 图片_找到那张假照片!对抗Deepfake之路
- 试用74LS194加74151设计一个从Q0端输出100111序列信号的序列信号发生器. 要求电路能自启动, 且越简单越好
- jira后台统计数据的一些sql(包含reopen计算,时效)
- 神盾局第二季麦克机器人_神盾局特工第四季分集剧情介绍(1-22集)大结局
- matlab 中阿拉伯字母,常用阿拉伯字母念法
- ISO SAE 21434-2021 要求、建议、允许(RQ、RC、PM)整理
- HihoCoder 1245:王胖浩与三角形 三角形边长与面积
- Burp Suite 工具 目录
- 富文本编辑器 CKeditor 配置使用+上传图片