Spring Boot入坑-12-项目实战
-
掌握后端项目整体架构搭建,掌握从0到1构建一个完整项目
-
巩固已学习的后端技术,覆盖Java基础、Spring Boot的主要课程内容,包括但不限:序列化、反射、注解、泛型、Lambda、Stream、REST、Interceptor、数据访问、Swagger等等
-
一些扩展内容的学习,比如登录、密码加密、项目部署等
需求
-
一个测试人员记录开发Bug的Web前端网站
-
核心功能:包含Bug列表、Bug信息维护,所有员工都能访问
-
基础功能:人员信息维护、版本信息维护,供核心功能引用,仅管理员可以访问新增、修改、删除功能,但所有员工能访问查询所有功能
-
登录功能:所有人都能访问
项目需求图
【演示】
-
演示项目《BUG排行》项目,主要演示
-
未登录对接口访问返回401
-
不同角色用户登录,能访问不同接口
-
密码加密
-
开发步骤
一、数据库设计
-
根据业务特点,设计数据表及表之间的关系
-
实际的项目中,会有数据库设计的文档,使用Excel或PowerDesigner等工具进行
-
设计好了数据表,将数据表创建到MySQL数据库中,提供数据服务
-
项目只涉及3个表,分别是user、bug、version,具体见附件的bug_record_backend.sql脚本
-
注意
-
所有表的主键都为id,且为数据库自增
-
所有表都有create_by_id、create_by_name、create_time、update_by_id、update_by_name、update_time
数据库设计-物理模型图 -
【演示】
-
完成数据库的搭建与设计
二、搭建项目
-
创建一个基于Web的Spring Boot项目
-
按需要一次或逐步添加相应的包和类,主要的包,如下图
-
这些包下的类的主要作用分别为
-
controller:控制器层,提供REST服务;很薄的一层,尽量不要有逻辑
-
service:服务层,承载了项目主要的业务功能,主要服务于控制器层
-
mapper:数据访问层,实现与数据库中的数据交互,使用MyBatis-Plus
-
entity:实体类包,里面的类与数据表一一对应,数据访问层中的Mapper都是针对entity进行操作,同时也在服务层被使用
-
dto:数据传输对象,用于控制器层、服务层对外提供请求/响应数据结构规范
-
config:通用配置包,所有的自定义配置类都定义在这个包,像Web配置、Swagger配置、数据库分页配置等
-
interceptor:拦截器包,包括对控制器层的REST服务权限拦截
-
annotation:注解包,包括是否需要进行权限拦截的注解
-
handler:操作包,包含实体类拦截类
-
util:工具包,封装了常用的工具,像密码加密、会话工具
-
【演示】
-
完成包层次定义
三、添加基本功能
-
添加如下基本功能,包括pom.xml依赖、config包下配置类、application.properties中的配置节
-
Swagger
-
Lombok
-
对象映射
-
标准响应封装,见dto.common.ResponseData类
-
通用异常处理,见advice.GlobalExceptionHandler类
-
通用验证框架,见advice.GlobalExceptionHandler类
-
会话工具类,见util.SessionUtil类
-
密码加密工具类,见util.PasswordUtil类
-
-
并添加一个控制器,测试能提供基本的REST服务
【演示】
-
完成基本功能添加、控制器输出、会话工具类定义、密码加密工具类定义
四、集成数据访问
-
数据访问是一个软件项目的核心
-
项目中使用MyBatis-Plus,按顺序通过下列步骤进行集成
-
添加依赖,数据库产品依赖和MyBatis-Plus依赖
-
添加配置,在application.properties中添加数据库配置
-
添加分页组件,见config.MyBatisPlusConfig类
-
添加实体类,用于与数据表一一对应,见entity包下的类
-
添加Mapper,见mapper包下的接口
-
添加Service,并继承MyBatis-Pluse中的服务接口和类,见service包下的接口、service\impl包下的类
-
添加用户信息CRUD,一切的数据都由用户产生,很多独立的系统都由构建用户信息开始,注意使用DTO和ResponseData
-
添加创建、修改信息拦截,见handler.CommonMetaObjectHandler类,里面使用了会话工具类
-
-
注意,部分数据需要进行手动数据类型转换,如对于数据库中使用逗号分隔的内容,在程序中可以处理成List<String>;如用户表中的roles字段中的admin,staff,可转换成List<String>,包含两个元素,具体是通过TypeHandler接口实现,步骤如下
-
实现TypeHandler接口,主要实现其中的Java类型转DB类型、DB类型转Java类型,见entity.ListTypeHandler类
-
应用到实体类,对需要进行此种转换的实体类
-
在类上,@TableName注解中添加属性autoResultMap = true
-
在属性上,@TableFiled注解中进行应用,格式为 @TableField(jdbcType = JdbcType.VARCHAR,typeHandler = ListTypeHandler.class)
-
-
【演示】
-
完成用户各层次CRUD,注意其中的通用性验证、业务性验证、请求信息拦截、类型转换
五、集成登录
-
大部分的软件,都需要登录后,才能访问,后端的REST服务也是如此
-
具体的过程
-
添加登录控制器和功能,见controller.AccountController类、service.impl.AccountService类
-
将登录信息保存到会话(HttpSession),参考util.SessionUtil工具
-
应用到业务,创建、修改信息拦截
-
-
另外,再考虑注册、登出接口
【演示】
-
完成集成登录,使用明文密码
六、密码加密
-
项目中一定不能将明文密码保存到数据库,必须加密后再存储
-
一般会定义一个密码加密工具,对密码进行加密,且密码不能解密,并加盐防止彩虹表破解
-
密码加密主要在代码中下面部分使用
-
新增用户,密码需要加密保存
-
注册
-
修改密码
-
登录,都使用加密后的密码进行比较
-
【演示】
-
完成集成密码加密
七、认证与授权--基于Session
-
如上图一般,除了登录、注册服务,其他服务都需要登录才能使用
-
不同用户登录后,访问的REST服务也不一样,一般根据其角色决定,不同的用户使用不同的服务
-
如何完成按角色过滤REST服务访问权限?具体实现步骤如下
-
设计权限过滤注解,自定义注解@UserRight,里面包含roles属性,用于标识使用该注解的类或方法需要根据角色进行权限过滤;就此项目业务,见annotaion.UserRight注解
-
staff角色,所有人员都可访问,Bug信息所有接口、人员信息的查询所有接口、版本信息的查询所有接口
-
admin角色,仅管理员访问,人员信息除了查询所有之外接口、版本信息除了查询所有之外接口
-
-
设计登录接口,请求参数为用户名和密码,登录成功,将用户信息存入HttpSession,这个前面已经完成,见controller.AccountController类
-
设计拦截器,对自定义注解@UserRight进行拦截,根据注解中需要的角色与登录时保存在HtppSession中用户拥有的角色进行比对,如果当前用户拥有访问接口需要的角色权限,可以访问;否则,拒绝访问,具体如下;见interceptor.AuthenticationInterceptor类
-
无需权限访问,像账户控制器中的访问,主要用于登录
-
登录才能访问,其他的所有控制器的访问
-
登录且具备权限才能访问,基础数据中的CRUD方法(除了queryAll)之外
-
-
绑定拦截器,将拦截器绑定到/**的路由中,拦截所有的Action,见config.WebConfig类
-
标注REST服务,对需要进行权限过滤的REST服务,添加注解@UserRight注解,并配置角色,见controller.UserController类、controller.VersionController类、controller.BugController类
-
查看拦截效果,通过未登录、staff登录用户、admin登录用户查看不同效果
-
【演示】
-
完成认证与授权
八、业务功能开发
-
上述功能完成后,一个基本的软件框架就搭建好了
-
后续根据业务,往各个层次添加代码即可,主要有
-
版本业务
-
BUG业务
-
...
-
【演示】
-
完成业务功能开发
九、与前端集成
-
学完前端后可实现
十、打包与发布
-
项目开发完成后,打包并部署到Linux服务器,具体参考打包和发布
【演示】
-
完成项目打包并部署到CentOS 7服务器,具体参考打包和发布
【练习】
-
完成演示代码编写
实战和作业
-
完成项目实战,具体代码见bug-record-backend
补充-1:认证与授权--基于Token
传统Token
-
传统的Token,是在服务器生成,并保存相应的信息在服务器(如Redis),然后返回给用户保存
JWT(JSON Web Token)
-
数据不再保留在服务器,而是由客户端保存自己登录后获取的Token
-
后续对服务器的请求,都需要携带Token
-
服务器对Token中的信息进行解密后,识别当前用户信息,是否过期等数据,进行认证与授权
-
JWT中的内容分为Header、Payload、Signature三部分
-
Header:存放类型、加密算法
-
Payload:存储超时时间、用户自定义信息等
-
Signature:经过密钥签名的加密信息,安全的保证
-
-
使用Base64进行编码
特点
-
一般无需服务器端通过会话或缓存保存信息
-
无需cookies支持
与Session对比
相同点
-
作用相同,都是在HTTP协议无状态特性下,用于记住多次请求时的用户票据相关信息,实现认证与授权
不同点
-
保存方式
-
Session,使用内存或缓存保存用户票据信息,并通过Cookies形式下发Session ID到客户端浏览器;客户端浏览器每次请求都通过Cookies携带这个Session ID,来告诉服务器“我是谁”
-
Token,通过自包含形式保存用户票据信息,服务器端无需存储相关信息;客户端每次请求一般都通过请求头Header携带这个Token
-
-
过期时间
-
Session,服务器端Session和下发到Cookies的Session ID都可以设置过期时间
-
Token,过期时间也是通过自包含形式保存,一般服务器不可以过期掉Token
-
-
加密方式
-
Session,由于用户票据信息保存在服务器端,无需加密,只下发唯一的Session ID给客户端即可
-
Token,使用密钥配合加密算法加密,只要密钥不泄露,相对安全
-
-
多负载
-
Session,要处理分布式会话共享
-
Token,只要密钥、加密算法一致,单体与多负载处理方法也一致,适合微服务
-
使用方式
-
使用方式与会话模式一致,查看附件项目中有标识【JWT】的内容部分
-
引入依赖
<!--【JWT】1、引入依赖--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.18.3</version> </dependency>
-
设计加密工具类,确定密钥、过期时间等;具体见附件项目中的util.JWTUtil类
-
登录时返回Token,使用工具类,生成token并返回给请求方;见附件项目中的dto.output.account.LoignOutputDto类、service.impl.AccountServiceImpl类;并可在https://jwt.io查看jwt内容
-
拦截器中使用Token进行认证与授权,验证token的有效性,并根据里面的Payload信息进行授权验证;见附件项目中的interceptor.AuthenticationInterceptor类
-
自动填充中表和Token中的用户信息,使用token信息,如果像创建人、更新人自动填充中使用第4步的方式获取token中的Payload信息进行填充
-
【演示】
-
基于Token的认证与授权,见附件中项目bug-record-backend-token
补充-2:认证与授权--基于Spring Security【了解】
概述
-
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案
-
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)
-
用户认证
-
是验证某个用户是否为系统中的合法主体,即用户能否访问该系统
-
一般要求用户提供用户名和密码
-
系统通过校验用户名和密码来完成认证过程
-
-
用户授权
-
是验证某个用户是否有权限执行某个操作
-
在软件中,不同用户所具有的权限是不同的;比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改
-
系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限
-
-
-
一般会对接基于角色的访问控制(RBAC,Role-Based Access Control )
使用方式
-
使用方式与会话模式一致,查看附件项目中有标识【JWT】的内容部分
-
引入依赖
<!--【SpringSecurity】1、引入依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
定义UserDetails子类,
-
将一些验证项返回true
-
包含用户信息(UserEntity类型)的属性
-
使用用户信息中的角色转换成Spring Security中的角色
-
见附件项目中的security.UserDetailsImpl类
-
-
定义UserDetailsService子类
-
覆盖loadUserByUsername方法,在此方法中,会接收到用户名,根据用户名从数据库中获取用户信息(UserEntity对象)
-
并返回上一步的UserDetails子类对象,里面包含从数据库中查询到的用户信息对象
-
见附件项目中的security.UserDetailsServiceImpl类
-
-
注入加密工具BCryptPasswordEncoder
-
定义BCryptPasswordEncoder工具类对象,并注入到Spring容器,采用SHA-256 +随机盐+密钥对密码进行加密
-
见附件项目中的security.WebSecurityConfig类中的passwordEncoder方法
-
-
注入按角色进行授权类SecurityFilterChain
-
使用HttpSecurity类对象,进行URL与角色匹配,完成Action认证与授权逻辑
-
见附件项目中的security.WebSecurityConfig类中的securityFilterChain方法,且类上需要加上@EnableWebSecurity注解
-
-
使用加密工具BCryptPasswordEncoder,分别在下列逻辑中使用
-
注册
-
新增用户
-
重置密码
-
-
获取登录用户信息,使用下列代码获取,一般用于创建信息、更新信息拦截中
//从SpringSecurity获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); //登录票据 UserDetailsImpl userDetails = null; if(authentication.getPrincipal() instanceof UserDetailsImpl) { userDetails = (UserDetailsImpl) authentication.getPrincipal(); }
-
认证与授权就生效了
-
【演示】
-
基于Spring Security的认证与授权,见附件中项目bug-record-backend-spring-security
代码
网盘地址:链接:https://pan.baidu.com/s/1bCIsfG390nh-mTLzkAi7Bw?pwd=8888