??🎉🎉欢迎来到我的CSDN主页!🎉🎉
🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚
🌟推荐给大家我的博客专栏《SpringBoot开发之Security系列》。🎯🎯
🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁
? ? ? ? 上一期的博客中我们一起了解了什么是Security,以及在SpringBoot中集成Security使用,以及一些的基础用法和一些案例演示。今天的这期博客基于上期博客进一步完善使用,请仔细阅读。
? ? ? ? 在我们的数据库中国创建好我们所需要的表,有用户信息表、角色信息表、模块信息表(权限信息表)、用户角色表、角色模块表这五张表。用于后续的权限设置
表名 | 说明 |
---|---|
sys_user | 用户信息表 |
sys_role | 角色信息表 |
sys_module | 模块信息表(权限信息表) |
sys_user_role | 用户角色表 |
sys_role_module | 角色模块表 |
? ? ? ? ?我们可以对用户信息表中的字段进行修改,只保留关键字段,也可以不修改。(我这里只保留了四个字段)
?????????当然我们要连接数据库需要我们进行导入Mybatis-Plus的依赖以及数据库的依赖。
? ? ? ? 当然还需要我们的生成器等等一些配置类?
package com.yx.security.config;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Slf4j
public class MySQLGenerator {
private final static String URL = "jdbc:mysql://localhost:3306/bookshop";
private final static String USERNAME = "root";
private final static String PASSWORD = "root123";
private final static DataSourceConfig.Builder DATA_SOURCE_CONFIG =
new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);
public static void main(String[] args) {
FastAutoGenerator.create(DATA_SOURCE_CONFIG)
.globalConfig(
(scanner, builder) ->
builder.author(scanner.apply("请输入作者名称?"))
.outputDir(System.getProperty("user.dir") + "\\src\\main\\java")
.commentDate("yyyy-MM-dd")
.dateType(DateType.TIME_PACK)
.enableSwagger()
)
.packageConfig((builder) ->
builder.parent("com.zking.security")
.entity("pojo")
.service("service")
.serviceImpl("service.impl")
.mapper("mapper")
.xml("mapper.xml")
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
)
.injectionConfig((builder) ->
builder.beforeOutputFile(
(a, b) -> log.warn("tableInfo: " + a.getEntityName())
)
)
.strategyConfig((scanner, builder) ->
builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.addTablePrefix("tb_", "t_", "lay_", "meeting_", "sys_", "t_medical_")
.entityBuilder()
.enableChainModel()
.enableLombok()
.enableTableFieldAnnotation()
.controllerBuilder()
.enableRestStyle()
.enableHyphenStyle()
.build()
)
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
? ? ? ? 别忘记了根据项目信息进行修改?
server:
port: 8080
spring:
freemarker:
# 设置freemarker模板后缀
suffix: .ftl
# 设置freemarker模板前缀
template-loader-path: classpath:/templates/
enabled: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/bookshop
? ? ? ? 我们这就只生成用户信息表。
?
? ? ? ? mapper记得打对应的注解,以及在项目启动类中加上扫描mapper的注解
package com.yx.security.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/")
public String toLogin(){
return "login";
}
@RequestMapping("/index")
public String toIndex(){
return "index";
}
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>用户登录</h1>
<form action="/userLogin" method="post">
<p>
<label>用户:<input type="text" name="username"/></label>
</p>
<p>
<label>密码:<input type="password" name="password"/></label>
</p>
<input type="submit" value="登录"/>
<p>${msg!""}</p>
</form>
</body>
</html>
? ? ? ? 首先在实体类中实现一个接口——UserDetails类,实现之后会进行一个重新编写四个方法。?
? ? ? ? ?但是重写方法是返回的值是死的,应该是重数据库中获取的返回值,因此改为定义四个属性及添加指定字段到表中。
? ? ? ? ?数据库表中的字段注意数驼峰命名用下滑线隔开,默认值都是1.,authorities不是数据库中的字段是代表权限的,待会在类上会打上注释说明,字段会打上注释说明是数据库上的那些字段。
? ? ? ? 接下来就是实现功能的代码编写了,首先在实体接口实现类实现UserDetailsService,并且重新方法。
? ? ? ? ?对其配置类进行修改调整
? ? ? ? ?直接运行项目进行登陆
? ? ? ? ?如果存在多个密码加密方法,并都注释了@Bean注解,则可以用@Primary注解标记使用那个方法。
????????创建自定义
MD5
加密类并实现PasswordEncoder
PasswordEncoder.java
public class CustomMd5PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
//对密码进行 md5 加密
String md5Password = DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
System.out.println(md5Password);
return md5Password;
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 通过md5校验
System.out.println(rawPassword);
System.out.println(encodedPassword);
return encode(rawPassword).equals(encodedPassword);
}
}
?????????修改
SecurityConfig
配置类,更换密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
// 自定义MD5加密方式:
return new CustomMd5PasswordEncoder();
}
????????
BCryptPasswordEncoder
是Spring Security
中一种基于bcrypt
算法的密码加密方式。bcrypt
算法是一种密码哈希函数,具有防止彩虹表攻击的优点,因此安全性较高。????????使用
BCryptPasswordEncoder
进行密码加密时,可以指定一个随机生成的salt
值,将其与原始密码一起进行哈希计算。salt值可以增加密码的安全性,因为即使两个用户使用相同的密码,由于使用不同的salt
值进行哈希计算,得到的哈希值也是不同的。????????在
Spring Security
中,可以通过在SecurityConfig
配置类中添加以下代码来使用BCryptPasswordEncoder
进行密码加密:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
????????在实际开发中,为了用户登录方便常常会启用记住我(
Remember-Me
)功能。如果用户登录时勾选了“记住我”选项,那么在一段有效时间内,会默认自动登录,免去再次输入用户名、密码等登录操作。该功能的实现机理是根据用户登录信息生成Token
并保存在用户浏览器的Cookie
中,当用户需要再次登录时,自动实现校验并建立登录态的一种机制。
Spring Security
提供了两种Remember-Me
的实现方式:
简单加密
Token
:用散列算法加密用户必要的登录系信息并生成Token
令牌。持久化
Token
:数据库等持久性数据存储机制用的持久化Token
令牌。
创建数据库表 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
);
SecurityConfig
配置类????????
Remember-Me
功能的开启需要在configure(HttpSecurity http)
方法中通过http.rememberMe()
配置,该配置主要会在过滤器链中添加RememberMeAuthenticationFilter
过滤器,通过该过滤器实现自动登录。? ? ? ? 在配置类中添加一个方法
/**
* 配置持久化Token方式,注意tokenRepository.setCreateTableOnStartup()配置
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 设置为true要保障数据库该表不存在,不然会报异常哦
// 所以第二次打开服务器应用程序的时候得把它设为false
tokenRepository.setCreateTableOnStartup(false);
return tokenRepository;
}
? ? ? ? ?没创建表的可以将setCreateTableOmStartup的属性值该为true运行时会自动创建。
<input type="checkbox" name="remember-me"/>记住我<br/>
.rememberMe()
// 指定 rememberMe 的参数名,用于在表单中携带 rememberMe 的值。
//.rememberMeParameter("remember-me")
// 指定 rememberMe 的有效期,单位为秒,默认2周。
.tokenValiditySeconds(30)
// 指定 rememberMe 的 cookie 名称。
.rememberMeCookieName("remember-me-cookie")
// 指定 rememberMe 的 token 存储方式,可以使用默认的 PersistentTokenRepository 或自定义的实现。
.tokenRepository(persistentTokenRepository())
// 指定 rememberMe 的认证方式,需要实现 UserDetailsService 接口,并在其中查询用户信息。
.userDetailsService(userDetailsService)
? ? ? ? 当我们删除网页重新访问主页时会直接进入不会需要登陆?,数据库中也会生成对应数据,以及网页会生成Cookie
? ? ? ? 当我们删除数据库表中的登陆信息数据时,重新刷新主页则访问不了?
????????
CSRF
(Cross-Site Request Forgery
,跨站请求伪造)是一种利用用户已登录的身份在用户不知情的情况下发送恶意请求的攻击方式。攻击者可以通过构造恶意链接或者伪造表单提交等方式,让用户在不知情的情况下执行某些操作,例如修改密码、转账、发表评论等。为了防范
CSRF
攻击,常见的做法是在请求中添加一个CSRF Token
(也叫做同步令牌、防伪标志),并在服务器端进行验证。CSRF Token
是一个随机生成的字符串,每次请求都会随着请求一起发送到服务器端,服务器端会对这个Token
进行验证,如果Token
不正确,则拒绝执行请求。
_csrf
隐藏域 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
? ? ? ?不添加默认开启了防止跨域攻击的功能,任何
POST
提交到后台的表单都要验证是否带有_csrf
参数,一旦传来的_csrf
参数不正确,服务器便返回 403 错误。
如果针对一些特定的请求接口,不需要进行
CSRF
防御,可以通过以下配置忽略:
http.csrf().ignoringAntMatchers("/upload"); // 禁用/upload接口的CSRF防御
????????在
Spring Security
中,防范CSRF
攻击可以通过启用CSRF
保护来实现。启用CSRF
保护后,Spring Security
会自动在每个表单中添加一个隐藏的CSRF Token
字段,并在服务器端进行验证。如果Token
验证失败,则会抛出异常,从而拒绝执行请求。启用CSRF
保护的方式是在Spring Security
配置文件中添加.csrf()
方法,例如:
? http.csrf().disable();是禁用CSRF
? ? ? ? ?我们复制一份登陆表单不携带csrf隐藏域进行测试
? ? ? ? 由上图所知,第一个登陆添加了csrf隐藏域能够成功访问主页,第二个登陆没有添加则无法访问。
.csrf()
主要方法介绍:方法 | 说明 |
---|---|
disable() | 关闭CSRF 防御 |
csrfTokenRepository() | 设置CookieCsrfTokenRepository 实例,用于存储和检索CSRF 令牌。与HttpSessionCsrfTokenRepository 不同,CookieCsrfTokenRepository 将CSRF 令牌存储在cookie 中,而不是在会话中。 |
ignoringAntMatchers() | 设置一组Ant模式,用于忽略某些请求的CSRF 保护。例如,如果您想要忽略所有以/api/ 开头的请求,可以使用.ignoringAntMatchers("/api/**") 。 |
csrfTokenManager() | 设置CsrfTokenManager 实例,用于管理CSRF 令牌的生成和验证。默认情况下,Spring Security 使用DefaultCsrfTokenManager 实例来生成和验证CSRF 令牌。 |
requireCsrfProtectionMatcher() | 设置RequestMatcher 实例,用于确定哪些请求需要进行CSRF 保护。默认情况下,Spring Security 将对所有非GET、HEAD、OPTIONS和TRACE 请求进行CSRF 保护。 |
🎉🎉本期的博客分享到此结束🎉🎉
📚📚各位老铁慢慢消化📚📚
🎯🎯下期博客博主会带来新货🎯🎯
🎁三连加关注,阅读不迷路?!🎁