【SpringBoot学习】04SpringSecurity

本文介绍了SpringBoot集成SpringSecurity进行权限管理的过程,包括基础配置、用户认证与授权、自定义登录页面、数据库认证、记住我功能以及CSRF防护。重点讲解了SpringSecurity的核心组件和过滤器链,以及如何通过UserDetailsService和PasswordEncoder实现数据库认证。
摘要由CSDN通过智能技术生成

SpringBoot(B站尚硅谷)学习笔记

01基础篇
02-1运维实用篇(打包与运行、多环境配置、日志)
02-2开发实用篇(热部署、配置、NoSQL、整合第三方技术)
03SpringCache
04SpringSecurity



前言

SpringBoot(B站尚硅谷)学习笔记 04SpringSecurity


SpringSecurity

SpringSecurity框架简介

概要

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

  • 用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

同款产品比较

SpringSecurity

官网:https://spring.io/projects/spring-security

SpringSecurity 特点:

  • 和 Spring 无缝整合。
  • 全面的权限控制。
  • 专门为Web 开发而设计。
  • 旧版本不能脱离Web 环境使用。
  • 新版本对整个框架进行了分层抽取,分成了核心模块和Web 模块。单独引入核心模块就可以脱离Web 环境。
  • 重量级。(缺点)

Shiro

Apache 旗下的轻量级权限控制框架。

Shiro特点:

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。

  • 通用性。

    • 好处:不局限于Web 环境,可以脱离Web 环境使用。
    • 缺陷:在Web 环境下一些特定的需求需要手动编写代码定制。

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。

自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。

SpringSecurity入门案例

入门案例

  1. 创建SpringBoot工程

web项目 SpringBoot版本:2.3.7.RELEASE

  1. 引入相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 编写Controller进行相关测试

测试

测试localhost:8080/test/hello回车会自动跳转login页面,说明security已经起作用了。

怎么登录呢,默认用户名叫user,密码是idea控制台打印的一串字符

SpringSecurity基本原理

SpringSecurity 本质是一个过滤器链(Filter):

从启动后控制台是可以获取到过滤器链

[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4d8126f, org.springframework.security.web.context.SecurityContextPersistenceFilter@340b7ef6, org.springframework.se…]

重点看三个过滤器

FilterSecurityInterceptor:是一个方法级的权限过滤器,判断哪些方法可以访问哪些方法不可访问, 基本位于过滤链的最底部。

ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常

UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户名,密码。

两个重要接口

UserDetailsService

当什么也没有配置的时候,账号和密码是由 Spring Security自动生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

UserDetailsService接口:查询数据库用户名和密码过程

创建类实现UserdetailService接口,重写loadUserByUsername,编写查询数据过程

重写的loadUserByUsername方法要返回UserDetails,UserDetails也是一个接口,其内有一些方法,而User实体类实现了UserDetails接口,所以最终返回的是User实体类,这个实体类是SpringSecurity提供的一个实体类,返回的User需要传入的是用户名密码和对应的权限。

UserDetails类

User类

PasswordEncoder

SpringSecurity是不允许密码进行明文显示的,所以需要进行加密,PasswordEncoder是Security提供的一个密码加密接口,用于返回User对象里面密码加密。

BCryptPasswordEncoder实现了这个接口,调用这个对象进行加密,Security只认这个加密,我们可以重写它的encode和matches方法指定加密技术

指定加密技术

加密方法演示

SpringSecurity-web权限方案

用户认证

设置用户名和密码

环境准备:接着用入门案例的工程

在入门案例中用户名和密码是security生成的,我们也可以设置用户名和密码

三种方式:1.通过配置文件 2.通过配置类 3.自定义编写实现类

方式一:通过配置文件

配置文件指定了用户名和密码控制台就不会生成了

测试

方式二:通过配置类

编写配置类继承WebSecurityConfigurerAdapter类,继承的这个类是由Security提供的,所以我们写配置类要继承它,重写里面的configure方法(参数是AuthenticationManagerBuilder auth)。使用这个auth设置登录名和密码

测试运行

输入用户名密码登录没反应,发现 控制台报错了,说没有为id“null”映射PasswordEncoder。

原因是我们的密码要进行加密,加密的时候要用到PasswordEncoder接口,我们需要把这个接口创建出一个对象,要不然默认做加密过程中会报错,因为Spring Security 要求spring容器中必须有 PasswordEncoder实例。所以当自定义登录逻辑时要求必须给容器注入 PaswordEncoder的bean对象。这个bean返回BCryptPasswordEncoder(它实现了PasswordEncoder接口,是security提供的加密技术)。

测试运行

细节处理

BCryptPasswordEncoder是security提供的加密技术,我们也可以指定加密技术,如图指定MD5的加密方式,此处使用了匿名内部类的写法。

方式三:自定义编写实现类

方式一和方式二都是静态的用户名和密码,在实际开发中都是通过数据库查询认证的。在security认证过程中首先去找配置文件然后是配置类,如果它发现配置文件中或配置类中有用户名和密码,它就会根据这个用户名和密码进行登录认证。如果这两处都没设置它就会去找UserDetailsService接口,在这个接口中找到通过查数据库或者其它方式设置的用户名和密码进行登录认证。所以我们需要编写写这个接口的实现类去实现数据库查询认证。

继承接口重写的loadUserByUsername方法要返回UserDetails,UserDetails也是一个接口,其内有一些方法,而User实体类实现了UserDetails接口,所以最终返回的是User实体类,这个实体类是SpringSecurity提供的一个实体类,返回的User需要传入的是用户名密码和对应的权限。

  1. 在配置类设置使用哪个UserDetailsService实现类
  1. 编写实现类,返回User对象,User对象有用户名密码和操作权限

此处先模拟直接传值进行登录,后面再讲连数据库

测试运行

但发现密码对输入什么用户名都行,原因是loadUserByUserName是利用用户名去查用户,只校验了密码,用户名没有,直接new User,没有做账号判断,PasswordEncoder只做了密码判断,所以用户名随便输入,只要密码校验对就可以了。理论上账号是去数据库查询的,查到存在该账号,然后在匹配密码;

实现数据库认证来完成用户登录

实现数据库认证只需要在方式三:自定义编写实现类上加上查询数据库即可。

  • 整合MyBatisPlus完成数据库操作

环境准备

新建springboot web项目,并引入相关依赖(SpringSecurity、mybatis-plus、mysql驱动、lombok),复制入门案例中config controller service包下的代码过来

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

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

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

    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>

    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

数据库准备

相关数据库创建查看下方附件

  1. 创建实体类
  1. 整合mp,创建接口,继承BaseMapper
  1. 在MyUserDetailService调用mapper里面的方法查询数据进行用户认证

测试运行

成功查询数据库认证

自定义登录页面

前面看到的登录页面是springsecurity自带的登录页面,项目中肯定不会用自带登录页面,我们可以自定义登录页面。并且我们可以规定哪些接口可以允许直接访问不需要认证

  1. 在SecurityConfig配置类中实现相关配置

重写里面的configure方法(参数是HttpSecurity http)

  1. 创建login页面、controller

测试运行

/test/hello可以直接访问

访问/test/index被拦截,跳转到自定义登录页面

登录成功跳转到/test/index接口

细节处理

form表单中用户名和密码框name必须叫username和password,不然security得不到用户名和密码,自定义的话需要在配置中加上自定义的名称

自定义名称

用户授权

在前面的操作中,只要认证成功就能访问内容,但我们可以在访问的过程中做个授权,例如登录只让管理员才能访。

基于权限访问控制

.hasAuthority()

如果当前的主体具有指定的权限,则返回true,否则返回fales

  1. 在配置类设置当前访问地址有哪些权限
  1. MyUserDetailService里在返回User对象处设置权限

测试运行

拥有权限正常访问如:

假如把权限改掉,不叫admins就会被禁止访问如:

.hasAnyAuthority()

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回

true。就是此方法针对多个权限,比如某路径管理员可以访问,普通用户也可以访问。

细节处理

.hasAnyAuthority(“admins”,“root”)和.hasAnyAuthority(“admins,root”)两种写法都可以,其内部用可变参数写法

测试运行

只有root权限或只有admins权限都可以访问

只有root权限

只有admins权限

基于角色访问控制

.hasRole()

如果当前主体具有指定的角色,则返回true允许访问,否则出现403

为什么MyUserDetailService返回的权限要加上ROLE_而.hasRole(manager)不用,因为security把权限和角色都放进同一个List auths,使用","隔开,而使用ROLE_是为了区分哪个是角色哪个是权限,所以我们数据库写角色字段时要加上ROLE_或者在查出来的字段补上ROLE_。那为什么.hasRole(manager)不用写ROLE_,通过查看源码可以发现,他把.hasRole()括号里的参数字段加上了ROLE_,如果再加上就会报异常,这么做的目的是为了统一格式。

测试运行

.hasAnyRole()

表示用户具备任何一个角色都可以访问。

测试运行

基于数据库实现权限和角色访问控制

前面我们都把权限和角色都直接写在代码里,一般项目中都是从数据库查询的,下面就来详细介绍从数据库实现权限和角色访问控制。

环境准备

用的还是上方的工程,

数据库准备:相关的数据库创建查看下方附件。

  1. 添加实体类
  1. 整合mybatis,创建接口

注:因为是需要复杂的多表查询,这里选择使用原生Mybatis写法(MP不影响Mybatis使用),自定义sql语句。相关配置详细查看springboot整合Mybatis

  1. 添加xml映射文件编写sql语句
  1. 在MyUserDetailService调用mapper里面的方法查询数据进行权限认证
    为什么查询出的角色要加上ROLE_,通过代码我们可以看到security把权限和角色都放进同一个List auths,而使用ROLE_是为了区分哪个是角色哪个是权限,所以我们数据库写角色字段时要加上ROLE_或者在查出来的字段补上ROLE_,这里我们选择在查出来的字段补上ROLE_。
    至于为什么要把每个单独的权限或角色先放进SimpleGrantedAuthority对象再放进List auths里,还没理解,后续理解了再回来补充

  2. 在SecurityConfig配置类设置当前访问地址允许哪些权限或角色就行访问

为什么.hasRole(“管理员”)不用加上ROLE_,(前面有讲)通过查看源码可以发现,他把.hasRole()括号里的参数字段加上了ROLE_,如果再加上就会报异常,这么做的目的是为了统一格式。

测试运行

zhangsan用户拥有管理员角色成功访问

lisi用户没有管理员角色也没用menu:system权限,被拒绝访问

就算有menu:system也会被拒绝访问,因为他没有管理员角色

自定义403页面

当用户无权限访问时页面会跳转到403页面如下图,此页面我们通过相关配置实现自定义403页面

http.exceptionHandling().accessDeniedPage("/403.html")  // 配置没有权限访问跳转自定义403页面
  1. 创建403页面
  1. 在SecurityConfig设置相关配置

测试运行

注解使用

使用注解可以方便我们进行开发,简化很多配置。springsecurity中也可以使用注解进行角色和权限控制

  • **@Secured:**判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“
  • **@PreAuthorize:**该注解适合进入方法前的权限验证,可以将登录用户的 roles/permissions 参数传到方法中(就是直接用hasRole或hasAnyAuthority跟配置类一样写)
  • @PostAuthorize: 该注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
  • **@PostFilter:**权限验证之后对数据进行过滤(返回数据做过滤)
  • **@PreFilter:**进入控制器之前对数据进行过滤(传入数据做过滤)
  1. @Secured

作用:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“

注:使用注解前要先开启security注解功能!

在启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true)

在controller添加注解

测试运行

  1. @PreAuthorize

该注解适合进入方法前的权限验证,可以将登录用户的 roles/permissions 参数传到方法中(就是直接用hasRole或hasAnyAuthority跟配置类一样写)

注:使用注解前要先开启security注解功能!

在启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

在controller添加注解

测试运行

  1. @PostAuthorize

该注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限

注:使用注解前要先开启security注解功能!

在启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

在controller添加注解

测试运行

还是无权访问,但会执行方法

  1. @PostFilter

权限验证之后对数据进行过滤(返回数据做过滤)

在controller添加注解

留下用户名是 admin1 的数据。表达式中的 filterObject 引用的是方法返回值List 中的某一个元素

测试运行

  1. @PreFilter

进入控制器之前对数据进行过滤(传入数据做过滤)

与@PostFilter类似,这里就不进行演示了

用户注销

  1. 在SecurityConfig配置类中添加退出的配置

跟在前面加.and()后写也行,这里方便阅读。

.logoutUrl(“/logout”)里面的注销接口跟登录接口一样,跟前端匹配就行

测试(这个测试演示麻烦点)

  1. 添加登录成功页面,在成功页面添加超链接,可设置退出路径
  1. 修改配置类,登陆成功后跳转到成功页面
  1. 登录成功之后,在成功页面点击退出再去访问其它controller就不能就行访问了

自动登录(记住我—remember me)

当我们把浏览器关闭重新打开后访问每次都要重新登录,很麻烦。而在我们平时浏览的网站中都可以实现自动登录,下面就来详细介绍springsecurity中的记住我功能实现重启浏览器后能够自动登录。

在JavaWeb中我们学过如何使用Cookie来实现记住我功能,但Cookie是把数据存到客户端,这并不安全。我们可以使用SpringSecurity安全框架机制实现自动登录,它已经把过程大部分都进行了封装。

实现原理

浏览器发起登录进行认证请求,认证成功后security会把一串加密串(token)写入浏览器Cookie,然后再把加密串和用户登录信息存入数据库。重新打开浏览器后浏览器会发起服务请求,经过security读取Cookie中的加密串再到数据库查找到对应加密串的登录信息,匹配成功就能实现自动登录功能

写进数据库的过程security也帮我们完成了,从源码可以看到有建表语句,还有增加、删除、查找、修改等sql语句也都是封装好的。

功能实现

  1. 创建数据库表

(其实可以自动生成,这里手动创建是为了方便理解,建表语句是直接用源码的。自己建的话表必须叫persistent_logins)

create table persistent_logins(
    username varchar(64) not null, 
    series varchar(64) primary key, 
    token varchar(64) not null, 
    last_used timestamp not null
)
  1. 编写配置类:注入数据源,配置操作数据库对象
  1. 配置类中配置自动登录

剩余操作数据库都会自动帮我们完成

  1. 在登录页面添加复选框(是否开启自动登录)

注意:复选框中的name属性必须叫remember-me,不然security找不到

测试运行

勾选自动登录

登录后查看控制台

查看数据库,它已经帮我们自动存入了

60s内重新打开浏览器依旧可以正常访问

60s后再打开就要重新登录了

(它还会自动续时!)

CSFR

CSFR理解

在前面的过程中我们做过一个配置 ‘http.csrf().disable(); //关闭从csrf防护’ 这是干什么的呢?

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

举例:比如我在浏览器中打开了淘宝网站(假设它没开csrf防护),打开后进行了登录认证。这时候再打开另一个网站,它两目前都在同一个浏览器中,那另一个网站它能得到当前浏览器中所有的cookie信息,那就也能得到淘宝网站中的所有内容。这么做就很不安全,这就叫跨站请求攻击

从 Spring Security 4.0 开始,默认情况下会启用CSRF 保护,以防止CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和DELETE 方法进行防护。我们之前把csrf防护关掉是为了测试方便,如果上线后没用csrf防护对网站的风险是很大的

Spring Security实现CSRF的原理

security做跨站请求伪造防护时会在认证后生成一个Token(按照一定规则生成的字符串)保存得到Session或Cookie中,而每次请求都带着token值进行请求,拿着每次请求传回来的token值和session中的内容做比较,如果两个都一样就允许访问,反之不允许访问。以此来预防跨站攻击,因为跨站攻击不知道token值而且每次都不一样。

案例

在登录页面添加一个隐藏域:

<input type="hidden" th:if="${_csrf}!=null" th:value="${_csrf.token}" name="_csrf"/>

注释掉配置类中的关闭csrf配置

SpringSecurity-微服务权限方案

暂时跳过,后面再补

SpringSecurity-微服务权限方案

附件:

数据库

create table users(
    id bigint primary key auto_increment,
    username varchar(20) unique not null, 
    password varchar(100)
);
-- 密码 atguigu
insert into users values(1,'zhangsan','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na');
-- 密码 atguigu
insert into users values(2,'lisi','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na');


create table role(
    id bigint primary key auto_increment, 
    name varchar(20)
);

insert into role values(1,'管理员');
insert into role values(2,'普通用户');

create table role_user(
    uid bigint,
    rid bigint
);

insert into role_user values(1,1);
insert into role_user values(2,2);

create table menu(
    id bigint primary key auto_increment,
    name varchar(20),
    url varchar(100),
    parentid bigint,
    permission varchar(20)
);

insert into menu values(1,'系统管理','',0,'menu:system');
insert into menu values(2,'用户管理','',0,'menu:user');

create table role_menu(
    mid bigint,
    rid bigint
);

insert into role_menu values(1,1);
insert into role_menu values(2,1);
insert into role_menu values(2,2);

注:

该内容是根据B站尚硅谷学习时所记,相关资料可在B站查询:尚硅谷SpringSecurity框架教程(spring security源码剖析从入门到精通)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值