Spring Boot安全管理

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot安全管理相关的知识,希望对你有一定的参考价值。

Spring Boot安全管理

实际开发中,一些应用通常要考虑到安全性问题。例如,对于一些重要的操作,有些请求需要用户验明身份后才可以执行,还有一些请求需要用户具有特定权限才可以执行。这样做的意义,不仅可以用来保护项目安全,还可以控制项目访问效果。

Spring Security介绍

针对项目的安全管理,Spring家族提供了安全框架Spring Security,它是一个基于Spring生态圈的,用于提供安全访问控制解决方案的框架。为了方便Spring Boot项目的安全管理,Spring Boot对Spring Security安全框架进行了整合支持,并提供了通用的自动化配置,从而实现了Spring Security安全框架进行了整合支持,并提供了通用的自动化配置,从而实现了Spring Security安全框架中包含的多数安全管理功能
1)MVC Security是Spring Boot整合Spring MVC搭建Web应用的安全管理框架,也是开发中使用最多的一款安全功能。
2)WebFlux Security是Spring Boot整合Spring WebFlux搭建Web应用的安全管理。虽然Spring WebFlux框架刚出现不久、文档不够健全,但是它继承了其他安全功能的优点,后续有可能在Web开发中越来越流行。
3)OAuth2是大型项目的安全管理框架,可以实现第三方认证、单点登录等功能,但是目前Spring Boot版本还不支持OAuth2安全管理框架。
4)Actuator Security用于对项目的一些运行环境提供安全监控,例如Health健康信息、Info运行信息等,它主要作为系统指标供运维人员查看管理系统的运行情况

Spring Security快速入门

Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。其中,认证即确认用户是否登录,并对用户登录进行管控;授权即确定用户所拥有的功能权限,并对用户权限进行管控。

基础环境搭建

为了更换地使用Spring Boot整合实现MVC Security安全管理功能,实现Authentication(认证)和Authorization(授权)的功能,后续我们将会结合一个访问电影列表和详情的案例进行演示说明,这里先对案例的基础环境进行搭建。
1)创建Spring Boot项目。使用Spring Initializr方式创建一个名为chapter07的Spring Boot项目,在Dependencies依赖选择中选择Web模块中Web依赖以及Template Engines模块中的Thymeleaf依赖,然后根据提示完成羡慕创建。
2)引入页面html资源文件。在项目的resources下templates目录中,引入案例所需的资源文件。

index.html是项目首页面,common和vip文件夹中分别是普通用户和vip用户可访问的页面。
index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
    <title>影视直播厅</title>
</head>
<body>
<h1 align="center">欢迎进入电影网站首页</h1>
<hr/>
<h3>普通电影</h3>
<ul>
    <li><a th:href="@{/detail/common/1}">飞驰人生</a></li>
    <li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
</ul>
<ul>
    <li><a th:href="@{/detail/vip/1}">速度与激情</a></li>
    <li><a th:href="@{/detail/vip/2}">星球崛起</a></li>
</ul>
</body>
</html>

index.html首页面中通过标签分类展示了一些普通电影和VIP电影,并且这些电影都通过<a>标签连接到了具体的影片详细路径。
在templates文件夹下,common和vip文件夹中引入的HTML文件就是对应电影的简介信息。
common/1.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>影片详情</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>飞驰人生</h1>
</body>
</html>

3)编写Web控制层。在chapter07项目中创建名为com.example.chapter07.controller的包,并在该包下创建一个用于页面请求处理的控制类
FileController.java

package com.example.chapter07.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class FileController {
    @GetMapping("/detail/{type}/{path}")
    public String toDetail(@PathVariable("type")String type,
                           @PathVariable("path")String path){
        return "detail/"+type+"/"+path;
    }
}

开启安全管理效果测试

在Spring Boot项目中开启Spring Security的方式非常简单,只需要引入spring-boot-statrter-security启动器即可。
添加spring-boot-starter-security启动器
在项目的pom.xml中引入Spring Security安全框架的依赖启动器spring-boot-starter-security

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

上述引入的依赖spring-boot-starter-security就是Spring Boot整合Spring Security安全框架而提供的依赖启动器,其版本号由Spring Boot进行统一管理。当在当前Spring Boot版本下,对应的Spring Security框架版本号为5.1.4。
需要说明的是,一旦项目引入spring-boot-start-security启动器,MVC Security和WebFlux Security负责的安全功能都会立即生效;对应OAuth2安全管理功能来说,则还需要额外引入一些其他安全依赖。
项目启动测试

项目启动时会在控制台自动生成一个安全密码(这个密码在每次启动项目时都是随机生成的)。
通过浏览器查看项目首页,

自动跳转到了一个新的登录链接页面"http:/localhost:8080/login",这说明在项目中添加spring-boot-starter-security依赖启动器后,项目实现了Spring Security的自动化配置,并且具有了一些默认的安全管理功能。另外,项目中没有手动创建用户登录页面,而添加了Security依赖后,SPring Boot会自带一个默认的登录页面。

当在Spring Security提供的默认登录页面"/login"中输入错误登录信息后,会重定向到"/login?error"页面并显示出错误信息。
需要说明的是,在Spring Boot项目中加入安全依赖启动器后,Security会默认提供一个可登录的用户信息,其中用户名为user,密码随机生成,这个密码会随着项目的每次启动随机生成并打印在控制台上。

可以发现这种默认安全管理方式存在诸多问题,例如,只有唯一的默认登录用户user、密码随机生成且过于暴露、登录页面及错误提示页面不是我们想要的等。

MVC Security安全配置介绍

使用Spring Boot与Spring MVC进行Web开发时,如果项目引入安全依赖启动器,MVC Security安全管理功能就会自动生效,其默认的安全配置是在SecurityAutoConfigration和UserDetailsServiceAutoConfiguration中实现的。其中,Security AutoConfiguration会导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全安全管理,UserDetailsServiceAutoConfigration则用于配置用户身份信息。
通过自定义WebSecurityConfigurerAdapter类型的Bean组件,可以完全关闭Security提供的Web应用默认安全配置,但是不会关闭UserDetailsService用户信息自动配置类。如果要关闭UserDetailsService默认用户信息配置,可以之定义UserDetailsService、AuthenticationProvider或AuthenticationManager类型的Bean组件。另外,可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件覆盖默认访问规则。Spring Boot提供了非常多方便的方法,可用于覆盖请求映射和静态资源的访问规则。
WebSecurityConfigurerAdapter类的主要方法与说明

方法描述
configure(AuthenticationManagerBuilder auth)定制用户认证管理器来实现用户认证
configure(HttpSecurity http)定制基于Http请求的用户访问控制

自定义用户认证

通过自定义WebSecurityConfigurerAdapter类型的Bean组件,并重写configure(AuthenticationManagerBuilder auth)方法,可以自定义用户认证。针对自定义用户认证,Spring Security提供了多种自定义认证方法,包括有:In-Memory Authentication(内存身份认证)、Authentication Provider(身份认证提供商)和UserDetailsService(身份详情服务)。

内存身份认证

In-Memory Authentication(内存身份认证)是最简单的身份认证方式,主要用于Security安全认证体验和测试。自定义内存身份认证时,只需要在重写的configure(Authentication ManagerBuilder auth)方法中定义测试用户即可。下面通过Spring Boot整合Spring Security实现内存身份认证。
自定义WebSecurityConfigurerAdapter配置类
在chapter07项目中创建名为com.example.chapter07.config的包,并在该包下创建一个配置类SecurityConfig
SecurityConfig.java

package com.example.chapter07.config;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

自定义了一个继承自WebSecurityConfigurerAdapter的SecurityConfig配置类,用于进行MVC Security自定义配置,该类上方的@EnableWebSecurity注解是一个组合注解,其效果等同于@Configuration、@Import、@EnableGlobalAuthentication的组合用法,关于这些注解的介绍具体如下:
1)@Configuration注解的作用是将当前自定义的SecurityConfig类作为Spring Boot的配置类。
2)@Import注解的作用是根据pom.xml中导入的Web模块和Security模块进行自动化配置
3)@EnableGlobalAuthentication注解则用于开启自定义的全局认证。
需要说明的是,如果是针对Spring WebFlux框架的安全支持,需要在项目中导入Reactive Web模块和Security模块,并使用@EnableWebFluxSecurity注解开启基于WebFlux Security的安全支持。
使用内存进行身份认证
在自定义的SecurityConfig类中重写configure(AuthenticationManagerBuilder auth)方法,并在该方法中使用内存身份认证的方式进行自定义用户认证
SecurityConfig.java

package com.example.chapter07.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication().passwordEncoder(encoder)
                .withUser("shitou").password(encoder.encode("123456")).roles("common")
                .and()
                .withUser("李四").password(encoder.encode("123456")).roles("vip");
    }
}

重写了WebSecurityConfigurerAdapter类的configure(AuthenticationManagerBuilder auth)方法,并在该方法中使用内存身份认证的方式自定义了认证用户信息。定义用户认证信息时,设置了两个用户,包括用户名、密码和角色。
1)从Spring Security 5开始,自定义用户认证必须设置密码编码器用于保护密码,否则控制台会出现异常错误。
2)Spring Security提供了多种密码编码器,包括BcryptPasswordEncoder、Pbkdf2PasswordEncoder、ScryptPasswordEncoder等,密码设置不限于本例中的BcryptPasswordEncoder密码编码器。
3)自定义用户认证时,可以定义用户角色roles,也可以定义用户权限authorities。在进行赋值时,权限通常是在角色值得基础上添加"ROLE_"前缀。例如,authorities(“ROLE_common”)和roles(“common”)是等效的。
4)自定义用户认证时,可以为某个用户一次指定多个角色或权限,例如roles(“common”,“vip”)或者authorities(“ROLE_common”,“ROLE_vip”)。
效果测试
重启chapter07项目进行效果测试,项目启动成功后,仔细查看控制台打印信息,发现没有默认安全管理时随机生成的密码了。通过浏览器访问"http://localhost:8080"查看首页
实际开发中,用户都是在页面注册和登录时进行认证管理的,而非在程序内部使用内存管理的方式手动控制注册用户,所以上述使用内存身份认证的方式无法用于实际生产,只可以作为初学者的测试使用。

JDBC身份认证

JDBC Authentication(JDBC 身份认证)是通过JDBC连接数据库对已有用户身份进行认证。
数据准备
JDBC身份认证的本质是使用数据库中已有的用户信息在项目中实现用户认证服务,所以需要提前准备好相关数据。这里我们使用之前创建的名为springbootdata的数据库,在该数据库中创建3个表t_customer、t_authority和t_customer_authority,并预先插入几条测试数据。
security.sql

use springbootdata;
drop table if exists `t_customer`;
create table `t_customer`(
	`id` int(20) not null auto_increment,
	`username` varchar(200) default null,
	`password` varchar(200) default null,
	`valid` tinyint(1) not null default '1',
	primary key (`id`)
) engine=InnoDB auto_increment=4 default charset=utf8;
insert into `t_customer` values('1','shitou','$2a$10$BxzPg9I0VAKfDy6F5SRYhepktsvpfhbdz/iecePudLmMCcOdlK0n6','1');
insert into `t_customer` values('2','李四','$2a$10$BxzPg9I0VAKfDy6F5SRYhepktsvpfhbdz/iecePudLmMCcOdlK0n6','1');

drop table if exists t_authority;
create table `t_authority`(
	`id` int(20) not null auto_increment,
	`authority` varchar(20) default null,
	primary key (`id`)
) engine=InnoDB auto_increment=3 default charset=utf8;
insert into `t_authority` values ('1','ROLE_common');
insert into `t_authority` values ('2','ROLE_vip');

drop table if exists `t_customer_authority`;
create table `t_customer_authority`(
	`id` int(20) not null auto_increment,
	`customer_id` varchar(20) default null,
	`authority_id` varchar(20) default null,
	primary key (`id`)
) engine=InnoDB auto_increment=5 default charset=utf8;
insert into `t_customer_authority` values ('1','1','1');
insert into `t_customer_authority` values ('2','2','2');

1)创建用户表t_customer时,用户名username必须唯一,因为Security在进行用户查询时是通过username定位是否存在唯一用户的。
2)创建用户表t_customer时,必须额外定义一个tinyinit类型的字段(对应boolean类型的属性,例如示例中的valid),用于校验用户身份是否合法(默认都是合法的)。
3)初始化用户表t_customer数据时,插入的用户密码password必须是对应编码器编码后的密码,例如示例中的密码就是加密后的形式(对应的原始密码为123456)。因此,在自定义配置类中进行用户密码查询时,必须使用与数据库密码统一的密码编码器进行编码。
4)初始化权限表t_authority数据时,权限authority值必须带有"ROLE_"前缀,而默认的用户角色值则是对应权限值去掉"ROLE_"前缀。
添加JDBC连接数据库的依赖驱动启动器
pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

进行数据库连接配置
在项目的全局配置文件application.properties中编写对应的数据库连接配置。
application.properties

spring.datasource.url=jdbc:mysq://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456

使用JDBC进行身份认证
SecurityConfig.java

package com.example.chapter07.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.sql.DataSource;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String userSQL="select username,password,valid from t_customer" +
                "where username = ?";
        String authoritySQL="select c.username,a.authority from t_customer c," +
                "t_authority a t_customer_authority ca where" +
                "ca.customer_id=c.id and cd.authority_id=a.id and c.username = ?";
        auth.jdbcAuthentication().passwordEncoder(encoder)
                .dataSource(dataSource)
                .usersByUsernameQuery(userSQL)
                .authoritiesByUsernameQuery(authoritySQL);
    }
}

重写的configure方法中使用JDBC身份认证的方式进行身份认证。使用JDBC身份认证时,首先需要对密码进行编码设置(必须与数据库中用户密码加密方式一致);然后需要加载JDBC进行认证连接的数据源DataSource;最后,执行SQL语句,实现通过用户名username查询用户信息和用户权限。
需要注意的是,定义用户查询的SQL语句时,必须返回用户名username、密码password是否为有效用户valid3个字段信息;定义权限查询的SQL语句时,必须返回用户名username权限authority两个字段信息。否则,登录时输入正确的用户信息会出现PreparedStatement Callback的SQL异常错误信息。
效果测试
重启chapter07项目进行效果测试,项目启动成功后,通过浏览器访问。

UserDetailsService身份认证

对于用户流量较大的项目来说,频繁地使用JDBC进行数据库查询不仅麻烦,而且会降低网站响应速度。对于一个完善地项目来说,如果某些业务已经实现了用户信息查询地服务,就没必要使用JDBC进行身份认证了。
创建数据库实体类
新建包com.example.chapter07.domain
Customer.java

package com.example.chapter07.domain;

import javax.persistence.*;

@Entity(name = "t_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String password;
    private Integer valid;
	//省略setter,getter,tostring
}
package com.example.chapter07.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name = "t_authority")
public class Authority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private 以上是关于Spring Boot安全管理的主要内容,如果未能解决你的问题,请参考以下文章

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

玩转 Spring Boot 集成篇(任务动态管理代码篇)

Spring Boot:禁用状态异常代码的安全性