Spring Security 是一个强大的、安全性框架,用于保护 Spring 应用程序。本文将详细介绍如何在一个 Spring Boot 项目中使用 Spring Security,从基础配置到自定义安全需求。
创建 Spring Boot 项目
首先,我们需要创建一个 Spring Boot 项目。可以通过 Spring Initializr 快速生成项目。
- 访问 Spring Initializr.
- 选择如下选项:
- Project: Gradle Project 或 Maven Project
- Language: Java
- Spring Boot: 最新稳定版本
- 添加依赖:
- Spring Web
- Spring Security
- Spring Data JPA(用于后续数据库操作)
- H2 Database(用于演示)
生成并下载项目,解压后使用你喜欢的 IDE(如 IntelliJ IDEA 或 Eclipse)打开。
引入 Spring Security 依赖
如果你通过 Spring Initializr 创建了项目,Spring Security 依赖应该已经包括在内。如果没有,可以手动添加依赖:
Maven:
代码语言:xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Gradle:
代码语言:groovy复制implementation 'org.springframework.boot:spring-boot-starter-security'
默认配置和基本认证
Spring Security 默认提供了基本的 HTTP Basic 认证。启动项目后,访问任意端点,你会被要求输入用户名和密码。
默认情况下,Spring Boot 会生成一个随机密码并打印在控制台。默认用户名是 user
。
Using generated security password: <generated-password>
自定义用户认证
让我们创建一个自定义的用户认证。首先,创建一个配置类来扩展 WebSecurityConfigurerAdapter
。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());
manager.createUser(User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin();
}
}
基于数据库的用户认证
接下来,配置基于数据库的用户认证。首先,创建用户实体类和数据库表。
User 实体类:
代码语言:java复制import javax.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
// getters and setters
}
UserRepository 接口:
代码语言:java复制import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
数据库初始化脚本:
在 src/main/resources
目录下创建 data.sql
文件,初始化一些数据。
INSERT INTO user (username, password, role) VALUES ('user', 'password', 'ROLE_USER');
INSERT INTO user (username, password, role) VALUES ('admin', 'admin', 'ROLE_ADMIN');
SecurityConfig 配置:
代码语言:java复制import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserRepository userRepository;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected UserDetailsService userDetailsService() {
return username -> {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(passwordEncoder().encode(user.getPassword()))
.roles(user.getRole())
.build();
};
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin();
}
}
自定义登录页面
创建一个自定义的登录页面。首先,在 src/main/resources/templates
目录下创建一个 login.html
文件。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
然后,在 SecurityConfig
中配置自定义登录页面。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
方法级别的安全性
Spring Security 还支持方法级别的安全性。我们可以在服务层使用注解来保护方法。
首先,启用全局方法安全性。在主应用程序类中添加 @EnableGlobalMethodSecurity(prePostEnabled = true)
注解。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
然后,在服务层方法上使用 @PreAuthorize
注解。
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void adminMethod() {
// admin only logic
}
@PreAuthorize("hasRole('ROLE_USER')")
public void userMethod() {
// user only logic
}
}
处理跨站请求伪造(CSRF)
Spring Security 默认启用了 CSRF 保护。在开发和测试过程中,我们可能需要禁用它。在 SecurityConfig
中进行配置。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
日志记录和异常处理
我们可以通过自定义 AuthenticationEntryPoint
和 AccessDeniedHandler
来处理认证和授权异常。
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
@Component
public class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
}
}
在 SecurityConfig
中配置这些组件。
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler);
}
总结
通过这篇详细的教程,我们已经学习了如何在 Spring Boot 项目中集成 Spring Security,包括基本配置、自定义用户认证、基于数据库的用户认证、自定义登录页面、方法级别的安全性、CSRF 保护以及日志记录和异常处理。Spring Security 提供了强大的功能和灵活的配置选项,使我们能够根据需求来保护应用程序的安全。
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!