下面是关于 "SpringBoot Security密码加盐实例" 的详细攻略。
介绍
Spring Security 是一个强大的身份认证和授权框架,Spring Boot 的集成让我们可以非常方便地搭建安全的应用。但是,如果我们对密码进行单纯的 hash 加密,容易被暴力破解,因此需要加盐(salt)使其更加安全。
盐是在密码加密的时候添加到原始密码中的随机字符串,这个字符串可以是任意的,比如用户的 email 地址、用户名、或者是一个随机生成的字符串。在对密码进行 hash 加密之前,需要将盐与原始密码组合,再进行 hash 加密。这样可以增加攻击者破解密码的难度。
下面我们来学习一个 Spring Security 加盐实例。
步骤
第一步:添加依赖
在 pom.xml
文件中,添加 Spring Security 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
第二步:定义用户和角色
定义一个用户和角色的实体类,以及一个 UserRepository 接口:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String salt;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles = new HashSet<>();
// getters and setters
}
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
// getters and setters
}
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
在 User
实体类中,我们添加了一个 salt
字段,用来存储盐值。
第三步:重写密码加密策略
在 Spring Security 中,默认的加密方式是 bcrypt,我们需要将其替换为使用盐值的加密方式。首先创建一个 PasswordEncoder
的实现类:
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@Component
public class SaltPasswordEncoder implements PasswordEncoder {
private static final String SALT = "mySalt";
@Override
public String encode(CharSequence rawPassword) {
try {
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
final byte[] hash = messageDigest.digest((SALT + rawPassword.toString()).getBytes(StandardCharsets.UTF_8));
return base64Encode(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error while hashing password", e);
}
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(encode(rawPassword));
}
private static String base64Encode(byte[] bytes) {
return java.util.Base64.getEncoder().encodeToString(bytes);
}
}
我们在这里使用的是 SHA-256 算法,可以根据实际情况选择其他的加密方式。在这个实现类中,我们使用了一个固定的盐值,你也可以根据实际情况自动生成一个随机的盐值。
第四步:配置 Spring Security
创建一个 SecurityConfig
配置类,用来替代 Spring Security 的默认配置。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserRepository userRepository;
@Autowired
private SaltPasswordEncoder saltPasswordEncoder;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(saltPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll();
}
@Override
public UserDetailsService userDetailsService() {
return username -> {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("User %s not found", username));
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), true, true, true, true, getAuthorities(user.getRoles()));
};
}
private Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
}
在 configureGlobal
方法中,我们使用了自定义的 SaltPasswordEncoder
来加密密码,而不是使用默认的 BCryptPasswordEncoder
。同时我们也自定义了用户认证逻辑,从 UserRepository
中获取用户信息。
在 configure
方法中,我们定义了不同路径需要的权限,以及登录和退出的处理方式。
最后,我们重写了 userDetailsService
方法,用来在用户登录时根据用户名获取用户信息。
第五步:完成登录页面和用户角色
创建一个登录页面和一个控制器来处理登录请求和主页面请求。
<!-- /src/main/resources/templates/login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Login Page</h1>
<form action="/login" method="post">
<label>Username:</label><br>
<input type="text" name="username"><br>
<label>Password:</label><br>
<input type="password" name="password"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
@RestController
public class UserController {
@GetMapping("/")
public String index(Principal principal) {
return "Hello " + principal.getName() + "!";
}
@GetMapping("/admin")
public String admin() {
return "Admin Page";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
第六步:测试
最后,我们启动应用程序并测试它。可以尝试使用以下两种方式:
- 测试登录页面和登出功能。输入正确的用户名和密码,应该可以登录成功。登出功能应该也可以正常使用。
- 测试受限制的页面访问。只有拥有 ADMIN 权限的用户才能访问
/admin
页面,其他用户将被重定向到登录页面。
这里提供了两个测试账号:
username: admin
password: admin
username: user
password: user
这些账号只用做演示用途,实际使用时需要进行修改。
总结
本文展示了一个简单的 Spring Security 加盐实现。使用盐值加密可以大大提高密码的安全性。在实际使用中,你可以根据需要选择不同的加密算法,比如 bcrypt 或者 SHA-512 等。同时,你也可以根据实际需要定义不同的盐值,比如采用固定的、随机生成的或者根据用户名/email 自动生成的。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot Security密码加盐实例 - Python技术站