本文章对应视频可在B站查看SpringSecurity6对应视频教程,记得三连哦,这对我很重要呢!
温馨提示:
视频与文章相辅相成,结合学习效果更强哦!
系列文章链接
1、初识SpringSecurity,认识主流Java权限框架,SpringSecurity入门使用
2、SpringSecurity集成数据库,完成认证授权操作
3、SpringSecurity实现动态权限,OAuth2.0授权登录等
安全框架是什么
安全框架的本质就是一堆过滤器的组成,目的在于保护系统资源的访问是被允许的,所以在到达资源之前会做一系列的验证工作,这些验证工作通过一系列的过滤器完成。安全框架通常的功能有认证、授权、防止常见的网络攻击,以此为核心拓展其他功能。比如session管理,密码加密,权限管理等功能
Apache Shiro
起源
Shiro是 Apache 下的一个 开源安全框架,提供了身份验证、授权、 密码学 和会话管理等 关于安全的核心功能。它的前身是 JSecurity,2004 年,Les Hazlewood 和 Jeremy Haile 创办了 Jsecurity。当时他们找不到适用于应用程序级别的合适 Java 安全框架,同时又对 JAAS 非常失望,于是就搞了Shiro这个框架。
2004 年到 2008 年期间,JSecurity 托管在 SourceForge 上,贡献者包括 Peter Ledbrook、Alan Ditzel 和 Tim Veil。
2008 年,JSecurity 项目贡献给了 Apache 软件基金会( ASF ),并被接纳成为 Apache Incubator 项目,由导师管理,目标是成为一个顶级 Apache 项目。期间,Jsecurity 曾短暂更名为 Ki,随后因商标问题被社区更名为“Shiro”。随后项目持续在 Apache Incubator 中孵化,并增加了贡献者 KalleKorhonen。
2010 年 7 月,Shiro 社区发布了 1.0 版,随后社区创建了其项目管理委员会,并选举 Les Hazlewood 为主席。2010 年 9 月 22 日,Shrio 成为 Apache 软件基金会的顶级项目(TLP)。
功能
Shiro 干净利落地 处理身份认证,授权,会话管理和加密。 Apache Shiro 的首要目标是易于使用和理解。框架应该尽可能掩盖复杂的地方,暴露一个干净而直观的 API,来简化开发人员在应用程序安全上所花费的时间。
Apache Shiro 是一个拥有许多功能的综合性的安全框架,下图表展示了 Shiro 的核心功能:
Shiro 中有四大核心功能——身份验证,授权,会话管理和加密。
- Authentication :简称为“登录”,这是一个证明用户是谁的行为。
- Authorization :访问控制的过程,也就是决定“谁”可以去访问“什么”。
- Session Management : 管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
- Cryptography :通过使用加密算法保持数据安全同时易于使用。
除此之外,Shiro 也提供了额外的功能,来解决在不同环境下所面临的安全问题,尤其是以下这些:
- Web Support: web 支持的 API 能够轻松地保护 Web 应用程序。
- Caching :缓存是用来确保操作的快速而又高效的。
- Concurrency :Apache Shiro 利用它的并发特性来支持多线程应用程序。
- Testing :测试功能来帮助编写单元测试和集成测试。
- Run As :一个允许用户 以 另一个用户身份(如果允许) 运行的功能,有时候在管理脚本时很有用。
- Remember Me :在会话中记住用户的身份,这样用户只需要在强制登录时登录。
特点
Shiro 框架具有 直观、易用 等特性,同时也能提供了 健壮的安全性 ,在常规的企业级应用中,其实也够用了。
SpringSecurity
Spring Security是一个 功能强大且高度可定制 的,主要负责为 Java 程序提供声明式的 身份验证和访问控制 的安全框架。其 前身是Acegi Security ,后来被收纳为 Spring 的一个子项目,并更名为了Spring Security。
Spring Security的 底层主要是 基于 Spring AOP 和 Servlet 过滤器 来实现安全控制 ,它提供了全面的安全解决方案,同时授权粒度可以在 Web请求级和方法调用级 来处理身份确认和授权。
SpringSecurity是由Spring提供的一个安全框架,依赖于Spring Freamwork的基础功能,也可以将Bean交由Spring管理,充分利用Spring的IOC和AOP,为系统提供安全服务,如果项目使用Spring为基础使用SpringSecurity整合再合适不过。如果你的项目不是用Spring开发的就不要考虑此技术了
功能
Spring Security 的核心功能主要包括如下几个:
- 认证: 解决 “你是谁” 的问题–>解决的是系统中是否有这个“用户”(用户/设备/系统)的问题,也就是我们常说的“登录”。
- 授权: 权限控制/鉴别,解决的是系统中某个用户能够访问哪些资源,即“你能干什么”的问题。Spring Security 支持基于 URL 的请求授权、方法访问授权、对象访问授权。
- 防护攻击: 防止身份伪造等各种攻击手段。
- 加密功能: 对密码进行加密、匹配等。
- 会话功能: 对 Session 进行管理。
- RememberMe功能: 实现“记住我”功能,并可以实现token令牌持久化。
两者区别
- SpringSecurity基于Spring开发,与SpringBoot、SpringCloud更容易集成
- SpringSecurity拥有更多功能,如安全防护,对OAuth授权登录的支持
- SpringSecurity拥有良好的扩展性,更容易自定义实现一些定制需求
- SpringSecurity的社区资源比Shiro更丰富
- Shiro相较于SpringSecurity更轻便,简单,使用流程更清晰,上手容易,反观SpringSecurity属于重量级,学习难度比Shiro高
- Shiro不依赖于其他框架可独立运行,而SpringSecurity需要依赖于Spring容器运行
Sa-Token
是一款国产安全框架,使用简单,轻便。文档清晰详细,内置多种功能,喜欢的同学可以了解一下
SpringSecurity课程使用技术版本
- SpringBoot:3.1.5
- SpringSecurity:6.1.5
- JDK17:长期支持版本
- MySQL:8.0
- Mybatis-Plus:3.5.x
- JWT
- Redis
- …
Spring Security 6.1新特性
- 依赖项和配置的简化:Spring Security 6.1 简化了项目的依赖项和配置,通过使用 Spring Boot 2.3 作为默认的启动器,简化了自动配置和依赖项的处理
- 改进的身份验证和授权:Spring Security 6.1 对身份验证和授权进行了改进,增加了新的策略如默认的UserDetailsService策略,更简单的角色和权限管理,以及更灵活的异常处理
- 密码存储和加密的增强:Spring Security 6.1 提供了更强大的密码存储和加密功能,支持新的密码加密策略,用户可以自定义密码加密方式
- 支持 JDK 16:Spring Security 6.1 支持 JDK 16,可以更好地利用 Java 的新特性和 API 进行开发
- 提高了默认配置的易用性:Spring Security 6.1 增强了默认配置的易用性,例如默认的认证管理器,默认的安全过滤器链等,使得开发者可以更快速地启动项目
- 改进的测试支持:Spring Security 6.1 对测试提供了更好的支持,包括对测试的简化和性能优化
- 对 WebFlux 的支持:Spring Security 6.1 支持 Spring WebFlux,可以更好地适用于非阻塞式的 Web 应用
- 对 Spring Cloud 的集成:Spring Security 6.1 与 Spring Cloud 集成更好,可以更好地支持微服务架构的应用
- 安全性增强:Spring Security 6.1 对安全性进行了增强,包括对 CSRF 和 CSP 等攻击的防御,以及新的安全特性如安全HEADER等的支持
SpringSecurity6.1废弃了很多老的接口,已经不再是过时警告而是直接剔除。在配置上写法也发生变化,后边细说。
创建SpringSecurity项目
SpringSecurity焕发第二春主要是SpringBoot的兴起,让SpringSecurity配置更方便,使用更简单,在使用SpringSecurity时最好与SpringBoot搭配使用,此处以SpringSecurity6.1.5版本为例,首先创建一个基本的Maven工程,此处使用JDK17+SpringBoot3.1.5版本
直接创建SpringBoot项目
在工程中引入springboot和springsecurity依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
引入SpringSecurity之后不同
控制台输出密码,并提示加载了部分过滤器
访问接口首先进入到登录页面,用户名默认是user
,密码是控制台生成的
修改默认用户和密码
可以在项目的application.yml文件中配置自定义用户名和密码
spring:
security:
user:
name: admin
password: 123456
roles: ['admin','user']
配置之后重启项目,控制台就不会输出密码了
默认用户名和密码从哪来
在SpringSecurity源码中的SecurityProperties类中做了关于用户、密码,角色的配置,该类中有一个User内部类。定义了默认用户名密码以及权限
所以当我们配置name和password时就会使用自定的名字和密码了,当然也可以配置角色roles,当然他是一个集合
SpringSecurity配置类
自定义用户名和密码
通过配置类的方式实现基于内存的用户名和密码,角色的定义,后边切换成数据库,目的在于搞明白SpringSecurity认证流程
package com.stt.springsecuritydemo2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.authentication.PasswordEncoderParser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* 使用的是 SpringSecurity6.1.5,配置类有一些变化
* 1、该类不再需要继承其他的Security定义的类
* 2、需要使用 @Configuration 才会被Spring容器加载
* 3、废弃了很多方法,比如and()方法,建议使用Lambda表示实现
*/
@Configuration
// 标记为一个Security类,启用SpringSecurity的自定义配置
@EnableWebSecurity
public class SecurityConfig {
// 自定义用户名和密码
// UserDetailsService:根据用户名加载用户,找到的话返回用户信息【UserDetails类型】
// UserDetails:存储了用户的信息
@Bean
public UserDetailsService userDetailsService() {
// 定义用户信息
// 构建管理员
UserDetails adminUser = User.withUsername("xiaozhang")
.password("$2a$10$csvUZnj/VG6wBkooT/mewO.WbJesVCiHqEoWTyQrOYTKJvk3xpQb6")
.roles("admin", "user")
.build();
// 构建普通用户
UserDetails vipUser = User.withUsername("user")
.password("$2a$10$csvUZnj/VG6wBkooT/mewO.WbJesVCiHqEoWTyQrOYTKJvk3xpQb6")
.roles("user")
.build();
// 将用户存储到SpringSecurity中
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
// 创建两个用户,SpringSecurity在运行时就会知道有两个用户
userDetailsManager.createUser(adminUser);
userDetailsManager.createUser(vipUser);
return userDetailsManager;
}
}
UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails
UserDetails:记录用户信息,如用户名,密码,权限等
密码问题
SpringSecurity提供密码加密工具:PasswordEncoder,具体实现又很多,此处使用BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
// 构建密码编码器
return new BCryptPasswordEncoder();
}
密码加密:
String pass = "123456";
String result = passwordEncoder.encode(pass);
密码匹配:
// 匹配密码
boolean isTrue = passwordEncoder.matches("111111",result);
System.out.println("isTrue===>" + isTrue);
自定义登录页面
不使用前后端分离,涉及跨域问题,这里将会使用Thymeleaf实现。学习更多SpringSecurity配置
定义登录页面
引入Thymeleaf的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
定义登陆页面
SpringBoot要求登录页面写在resources/templates目录下
配置
配置跳转登陆页面的请求不需要拦截
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 配置关闭csrf机制,
http.csrf(csrf -> csrf.disable());
// 配置请求拦截方式
// requestMatchers() : 匹配资源路径
// permitAll() :随意访问
// anyRequest():其他任意请求
// authenticated() : 需要认证之后
http.authorizeHttpRequests(auth -> auth.requestMatchers("/to_login").permitAll().anyRequest().authenticated());
return http.build();
}
登陆实现
需求1:系统中有资源,没有登陆时,访问自动跳转到登录页面,登陆成功则可以正常访问
需求2:登陆成功之后自动跳转到首页
package com.stt.springsecuritydemo3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 配置权限相关的配置
// 安全框架本质都是一堆的过滤器,称之为过滤器链,每一个过滤器功能都不同
// to_login不要拦截
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 配置关闭csrf机制,
http.csrf(csrf -> csrf.disable());
// 配置请求拦截方式
// requestMatchers() : 匹配资源路径
// permitAll() :随意访问
// anyRequest():其他任意请求
// authenticated() : 需要认证之后
http.authorizeHttpRequests(auth -> auth.requestMatchers("/to_login").permitAll().anyRequest().authenticated());
// 表单、Basic等等
http.formLogin(form -> form.loginPage("/to_login") // 跳转到自定的登录页面
.loginProcessingUrl("/doLogin") // 处理前端的请求,与form表单的action一致
.usernameParameter("username") // 用户名
.passwordParameter("password") // 密码
.defaultSuccessUrl("/index") // 请求到index接口上
);
return http.build();
}
}
授权
用户认证之后,会去存储用户对应的权限,并且给资源设置对应的权限,SpringSecurity支持两种粒度的权限
- 基于请求的:在配置文件中配置路径,可以使用**的通配符
- 基于方法的:在方法上使用注解实现
- 动态权限:用户权限被修改之后,不需要用户退出,会自动刷新,也不需要修改代码
角色配置
@Bean
public UserDetailsService userDetailsService() {
// 定义用户信息
// 构建管理员
UserDetails adminUser = User.withUsername("xiaozhang")
.password("$2a$10$csvUZnj/VG6wBkooT/mewO.WbJesVCiHqEoWTyQrOYTKJvk3xpQb6")
.roles("admin") // 角色
.authorities("test1:show") // 权限
.build();
// 构建普通用户
UserDetails vipUser = User.withUsername("user")
.password("$2a$10$csvUZnj/VG6wBkooT/mewO.WbJesVCiHqEoWTyQrOYTKJvk3xpQb6")
.roles("user")
.authorities("user:show","goods:show")
.build();
// 将用户存储到SpringSecurity中
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
// 创建两个用户,SpringSecurity在运行时就会知道有两个用户
userDetailsManager.createUser(adminUser);
userDetailsManager.createUser(vipUser);
return userDetailsManager;
}
基于请求鉴权
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 配置关闭csrf机制,
http.csrf(csrf -> csrf.disable());
// 配置请求拦截方式
// requestMatchers() : 匹配资源路径
// permitAll() :随意访问
// anyRequest():其他任意请求
// authenticated() : 需要认证之后
http.authorizeHttpRequests(auth ->
// auth.requestMatchers("/test1").hasRole("admin")
// auth.requestMatchers("/test1").hasAnyRole("admin","user")
// auth.requestMatchers("/test1").hasAuthority("test1:show")
auth.requestMatchers("/test1").hasAnyAuthority("test1:show","user:show")
.requestMatchers("/to_login").permitAll()
.anyRequest().authenticated());
// 表单、Basic等等
http.formLogin(form -> form.loginPage("/to_login") // 跳转到自定的登录页面
.loginProcessingUrl("/doLogin") // 处理前端的请求,与form表单的action一致
.usernameParameter("username") // 用户名
.passwordParameter("password") // 密码
.defaultSuccessUrl("/index") // 请求到index接口上
);
return http.build();
}
如果没有权限则返回403
基于方法鉴权
在SpringSecurity6版本中@EnableGlobalMethodSecurity被弃用,取而代之的是@EnableMethodSecurity。默认情况下,会激活pre-post注解,并在内部使用AuthorizationManager。
新老API区别
此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改进:
- 使用简化的AuthorizationManager。
- 支持直接基于bean的配置,而不需要扩展GlobalMethodSecurityConfiguration
- 使用Spring AOP构建,删除抽象并允许您使用Spring AOP构建块进行自定义
- 检查是否存在冲突的注释,以确保明确的安全配置
- 符合JSR-250
- 默认情况下启用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter
请求级别和方法级别对比
请求级别 | 方法级别 | |
---|---|---|
授权类型 | 粗粒度 | 细粒度 |
配置位置 | 在配置类中配置 | 在方法上配置 |
配置样式 | DSL | 注解 |
授权定义 | 编程式 | SpEL表达式 |
主要的权衡似乎是您希望您的授权规则位于何处。重要的是要记住,当您使用基于注释的方法安全性时,未注释的方法是不安全的。为了防止这种情况,请在HttpSecurity实例中声明一个兜底授权规则。
如果方法上也定义了权限,则会覆盖类上的权限
注意:使用注解的方式实现,如果接口的权限发生变化,需要修改代码了。后期会学习动态权限,无需修改代码就可以实现接口权限的修改