SpringSecurity入门
1.创建项目
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
本次演示使用版本为
<spring-security.version>3.1.8</spring-security.version>
添加一个测试的 HelloController
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello World";
}
}
启动项目
此时访问8080端口可以发现
输入用户名user
密码输出在控制台登陆即可访问页面
访问页面http://localhost:8080/logout
即可退出登陆
分析
在登录页面,默认的用户名就是 user,默认的登录密码则是项目启动时控制台打印出来的密码,输入用户名密码之后,就登录成功了,登录成功后,我们就可以访问到 /hello 接口了。
在 Spring Security 中,默认的登录页面和登录接口,都是 /login ,只不过一个是 get 请求(登录页面),另一个是 post 请求(登录接口)。
大家可以看到,非常方便,一个依赖就保护了所有接口。
当我们查看和用户相关的自动化配UserDetailsServiceAutoConfiguration
可以发现getOrDeducePassword
方法
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.warn(String.format(
"%n%nUsing generated security password: %s%n%nThis generated password is for development use only. "
+ "Your security configuration must be updated before running your application in "
+ "production.%n",
user.getPassword()));
}
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
return NOOP_PASSWORD_PREFIX + password;
}
如果用户没有在配置文件中配置密码,那么就使用默认用户名和密码
其中 默认的用户名就是 user,默认的密码则是 UUID,而默认情况下,passwordGenerated 也为 true。
2.用户配置
默认的密码有一个问题就是每次重启项目都会变,这很不方便,下面给出用户、密码配置方案。
2.1配置文件
在application.yaml中配置
在SecurityProperties
我们发现,
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties
也就是说,我们只需要以 spring.security.user 为前缀,在配置文件中定义用户名密码即可
spring:
security:
user:
name: user
password: 1234
当采用配置文件的密码时,在SecurityProperties.User#setPassword
中可以看出
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
将passwordGenerated 属性为 false,这个属性设置为 false 之后,控制台就不会打印默认的密码了。
此时重启项目,就可以使用自己定义的用户名/密码登录了。
2.2 配置类
在配置类中配置,我们就要指定 PasswordEncoder 了,这是一个非常关键的东西。要说 PasswordEncoder ,就得先说密码加密。
2.2.1加密方案
Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。BCryptPasswordEncoder 就是 PasswordEncoder 接口的实现类。
2.2.2PasswordEncoder
其中定义了三个方法:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- encode 方法用来对明文密码进行加密,返回加密之后的密文。
- matches 方法是一个密码校对方法,在用户登录的时候,将用户传来的明文密码和数据库中保存的密文密码作为参数,传入到这个方法中去,根据返回的 Boolean 值判断用户密码是否输入正确。
- upgradeEncoding 是否还要进行再次加密,这个一般来说就不用了。
通过下图我们可以看到 PasswordEncoder 的实现类:
2.2.3配置
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("admin").password("{noop}123").build());
users.createUser(User.withUsername("jdc").password("{noop}123").build());
return users;
}
}
我们只需要提供 UserDetailsService 接口的实现类并注入 Spring 容器即可,{noop}
:这个前缀表明密码是以明文形式存储的,也就是说,密码不会被加密或哈希。
3.自定义表单登录页
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("admin").password("{noop}123").build());
users.createUser(User.withUsername("jdc").password("{noop}123").build());
return users;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests
.requestMatchers("/login").permitAll()
// .requestMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
);
http.formLogin(formLogin ->
formLogin
.loginPage("/login.html")
.loginProcessingUrl("/login")
.permitAll()
);
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
登陆页面resources\static\login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
h1 {
text-align: center;
color: #333333;
margin-bottom: 20px;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #cccccc;
border-radius: 4px;
}
input[type="submit"] {
width: 100%;
padding: 10px;
background-color: #007BFF;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
margin-top: 10px;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="login-container">
<h1>欢迎登录</h1>
<form action="/login" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required><br/>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required><br/>
<input type="submit" value="登录">
</form>
</div>
</body>
</html>
当我们定义了登录页面为 /login.html 的时候,Spring Security 也会帮我们自动注册一个 /login.html 的接口,这个接口是 POST 请求,用来处理登录逻辑。
配置完成后,再去重启项目,此时访问任意页面,就会自动重定向到我们定义的这个页面上来,输入用户名密码就可以重新登录了。
参考:
https://www.javaboy.org/2020/0325/spring-security-form-login.html
https://blog.csdn.net/weixin_40379712/article/details/130056716
https://blog.csdn.net/Maxiao1204/article/details/134825639