SpringBoot项目基础模块构建
- 创建SpringBoot项目
- 配置MyBatis
- 整合高级功能
- 自定义异常
- 封装R对象
- 集成Swagger
- 添加后端验证
- 抵御XSS攻击
- Shiro与JWT
- 精简返回给客户端的异常
1 创建SpringBoot项目
1.利用IDEA的Spring Initilizer可以图形化创建项目
2.填写好Maven项目信息
3.勾选若干依赖库
4.配置SpringBoot项目
- 配置Tomcat
- 配置MySQL
- 配置Redis
- 配置MongoDB
- 。。。
2 配置MyBatis
使用MyBatis/MyBatis-Plus
创建数据库连接
选中数据库,生成MyBatis文件
修改yml文件,添加MyBatis配置信息
这里我使用的是MyBatis-plus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#MyBatis-plus配置
mybatis-plus:
# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默认值,可以不设置
global-config:
db-config:
#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: auto
#字段策略 IGNORED:"忽略判断" NOT_NULL:"非 NULL 判断") NOT_EMPTY:"非空判断"
field-strategy: NOT_EMPTY
#数据库类型
db-type: MYSQL
configuration:
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
call-setters-on-nulls: true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3 日志管理
对于SpringBoot项目,日志最佳组合:slf4j+logback(SpringBoot使用)/ slf4j+log4j2
设置日志输出
1 | logging: |
logback.xml
Logback配置与使用:https://liuurick.github.io/2018/10/31/Logback%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BD%BF%E7%94%A8/
AOP+注解
基于数据库Binlog
ELK日志系统
4 整合高级功能
4.1 自定义异常
为什么要继承RuntimeException?
Exception类型的异常必须手动处理
RuntimeException异常既可以自动处理,也可以手动处理
包含的属性
- 状态码
- 异常信息
1 |
|
4.2 封装R对象
JavaWeb项目需要统一数据返回格式
- 业务状态码
- 业务消息
- 业务数据
导入httpcomponents
库
- 定义了很多HTTP状态码
- 免去我们自定义状态码常量
R类继承自HashMap
封装方法
- ok方法
- error方法
1 | public class R extends HashMap<String,Object> { |
4.3 集成Swagger
项目一般会使用Swagger2,这里我使用最近新出的Knife
1.添加依赖库
1 | <dependency> |
2.配置Swagger
(ApiInfoBuilder )定义Swagger页面基本信息
(ApiSelectorBuilder )哪些类中的方法会出现在Swagger上面
开启对JWT的支持
1 | List<ApiKey>:用户需要输入什么参数 |
编写Web接口
TestController
类声明要加上@API注解
Web方法要加上@ApiOperation注解
http://127.0.0.1:8080/emos-wx-api/swagger-ui.html
1 |
|
4.4 添加后端验证
1.使用Validation库
2.添加依赖库
3.创建Form类
- 类声明要添加@ApiModel
- 属性声明要添加@ApiModelProperty
- 属性声明要添加验证注解
4.验证数据要使用@Valid注解
4.5 抵御XSS攻击
原因
XSS攻击通常指的是通过利用网站系统保存数据的漏洞,通过巧妙的方法把恶意指令注入到网页,用户加载网页的时候就会自动执行恶意脚本。
如果黑客能在你的浏览器上执行javaScript,那么就能窃取Cookie或者Token
导入Hutool依赖
1 | <dependency> |
对Http请求中的数据转义
设置过滤器
覆盖Http请求的方法
- HttpServletRequest是接口,各家服务器厂商会实现它
- 如果直接继承各厂商的请求父类,那么我们的程序就跟厂商绑定在一起
- HttpServletRequestWrapper类
- 使用了装饰器模式
- 装饰器封装了厂商的Request实现类
- 只需要覆盖Wrapper类的方法,就能做到覆盖厂商请求对象里方法
- 创建过滤器,把Request对象传入Wrapper对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
public String getParameter(String name) {
String value = super.getParameter(name);
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
return value;
}
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
values[i] = value;
}
}
return values;
}
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameters = super.getParameterMap();
Map<String, String[]> map = new LinkedHashMap<>();
if (parameters != null) {
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
values[i] = value;
}
map.put(key, values);
}
}
return map;
}
public String getHeader(String name) {
String value = super.getHeader(name);
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
return value;
}
public ServletInputStream getInputStream() throws IOException {
InputStream in = super.getInputStream();
StringBuffer body = new StringBuffer();
InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8"));
BufferedReader buffer = new BufferedReader(reader);
String line = buffer.readLine();
while (line != null) {
body.append(line);
line = buffer.readLine();
}
buffer.close();
reader.close();
in.close();
Map<String, Object> map = JSONUtil.parseObj(body.toString());
Map<String, Object> resultMap = new HashMap(map.size());
for (String key : map.keySet()) {
Object val = map.get(key);
if (map.get(key) instanceof String) {
resultMap.put(key, HtmlUtil.filter(val.toString()));
} else {
resultMap.put(key, val);
}
}
String str = JSONUtil.toJsonStr(resultMap);
final ByteArrayInputStream bain = new ByteArrayInputStream(str.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return bain.read();
}
public boolean isFinished() {
return false;
}
public boolean isReady() {
return false;
}
public void setReadListener(ReadListener listener) {
}
};
}
}
4.6 DES加密敏感信息
DES加密与解密
1 | /** |
yml配置
1 | spring: |
配置类:
1 |
|
4.7 Shiro与JWT
SpringBoot中整合Shiro+JWT,实现RBAC权限模型,并且自动为Token令牌续期,解决令牌过期的难题。
Shiro简介
- 什么是认证
- 什么是授权
- Shiro靠什么做认证与授权
JWT简介
- 单点系统中的认证方式
- 集群环境中的JWT认证方式
创建JWT工具类
- 导入依赖库
- 生成令牌
- 密钥
- 过期时间
- 用户ID
- 验证令牌的有效性
- 内容是否有效
- 是否过期
把令牌封装成认证对象
- Shiro框架的认证需要用到认证对象
- 把令牌字符串做简单的封装
实现认证与授权
- 创建AuthorizingRealm类的子类
- 实现认证与授权的方法
如何设计令牌的刷新机制?
- 为什么要刷新令牌?
- 令牌一旦生成就保存在客户端
- 即便用户一直在登陆使用系统,也不会重新生成令牌
- 令牌到期,用户必须重新登录
- 令牌应该自动续期
- 双令牌机制
- 设置长短日期的令牌
- 短日期令牌失效,就用长日期的令牌
- 缓存令牌机制
- 令牌缓存到Redis上面
- 缓存的令牌过期时间是客户端令牌的一倍
- 如果客户端令牌过期,缓存令牌没有过期,则生成新的令牌
- 如果客户端令牌过期,缓存令牌也过期了,则用户必须重新登录
创建ThreadLocalToken类
- 该类是用于在过滤器和AOP之间传递Token
- 因为使用了ThreadLocal,所以是线程安全的
创建过滤器
- 判断哪些请求应该被Shiro处理
- options请求直接放行
- 提交application/json数据
- 请求被分成options和post两次
- 其余所有请求都要被Shiro处理
- options请求直接放行
- 判断Token是真过期还是假过期
- 真过期,返回提示信息,让用户重新登录
- 假过期,就生成新的令牌,返回给客户端
- 存储新令牌
- ThreadLocalToken
- Redis
创建ShiroConfig
- 把Filter和Realm添加到Shiro框架
- 创建四个对象返回给SpringBoot
- SecurityManager–用于封装Realm对象
- ShiroFilterFactoryBean
- 用于封装Filter对象
- 设置Filter拦截路径
- LifecycleBeanPostProcessor
- 管理Shiro对象生命周期
- AuthorizationAttributeSourceAdvisor
- AOP切面类
- Web方法执行前,验证权限
创建AOP切面类
- 拦截所有的Web方法返回值
- 判断是否刷新生成新令牌
- 检查ThreadLocal中是否保存令牌
- 把新令牌绑定到R对象中
4.8 精简返回给客户端的异常
@ControllerAdvice
可以全局捕获SpringMVC异常
判断异常的类型
- 后端数据验证异常—精简异常内容
- 未授权异常—“你不具有相关权限”
- EmosException—精简异常内容
- 普通异常—“后端执行异常”
全局异常处理:
1 | 4j |