摘要
本文介绍了一个简单的 Spring Security 实战示例,涵盖基本的身份验证和授权流程。首先介绍了 Spring Security 是一个强大的安全框架,用于在 Spring 应用中实现身份验证、授权以及保护应用免受常见安全攻击。接着详细阐述了项目结构、添加 Spring Security 依赖、创建数据库表、创建实体类、创建 UserDetailsService 实现、配置 Spring Security、使用@PreAuthorize注解控制方法权限、配置启动类、配置 application.properties 以及运行和测试等步骤。
1. Spring Security简介
Spring Security 是一个强大的安全框架,主要用于在 Spring 应用中实现身份验证、授权以及保护应用免受常见安全攻击。以下是一个简单的 Spring Security 实战示例,涵盖了基本的身份验证和授权流程。
2. 项目结构
src└── main├── java│ └── com│ └── example│ └── security│ ├── SecurityConfig.java│ ├── WebController.java│ ├── Application.java│ ├── CustomUserDetailsService.java│ ├── User.java│ ├── Role.java│ └── UserRepository.java└── resources├── application.properties├── schema.sql└── data.sql
3. 添加 Spring Security 依赖
首先,确保在 pom.xml
或 build.gradle
中添加了 Spring Security 的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId>
</dependency>
4. 创建数据库表
首先,我们需要为用户和角色创建数据库表。下面是一个简单的 SQL 脚本,用于在数据库中创建用户和角色表。
4.1. schema.sql
文件:
CREATE TABLE roles (id BIGINT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) NOT NULL
);CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(255) NOT NULL,enabled BOOLEAN NOT NULL DEFAULT true
);CREATE TABLE user_roles (user_id BIGINT,role_id BIGINT,PRIMARY KEY (user_id, role_id),FOREIGN KEY (user_id) REFERENCES users(id),FOREIGN KEY (role_id) REFERENCES roles(id)
);
4.2. data.sql
文件:
INSERT INTO roles (name) VALUES ('ROLE_ADMIN');
INSERT INTO roles (name) VALUES ('ROLE_USER');INSERT INTO users (username, password, enabled) VALUES ('admin', '{noop}admin', true);
INSERT INTO users (username, password, enabled) VALUES ('user', '{noop}user', true);INSERT INTO user_roles (user_id, role_id)
SELECT u.id, r.id
FROM users u, roles r
WHERE u.username = 'admin' AND r.name = 'ROLE_ADMIN';INSERT INTO user_roles (user_id, role_id)
SELECT u.id, r.id
FROM users u, roles r
WHERE u.username = 'user' AND r.name = 'ROLE_USER';
这里,{noop}
是 Spring Security 5 之前的一个标记,用于指示密码不加密。生产环境中应使用加密密码,如 BCrypt
。
5. 创建实体类
我们需要两个实体类 User
和 Role
,分别表示用户和角色信息。
5.1. User.java
:
package com.example.security;import javax.persistence.*;
import java.util.Set;@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;private boolean enabled;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))private Set<Role> roles;// Getters and Setters
}
5.2. Role.java
:
package com.example.security;import javax.persistence.*;@Entity
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;// Getters and Setters
}
6. 创建 UserDetailsService
实现
我们将通过 UserDetailsService
从数据库加载用户信息,Spring Security 会使用这个服务来进行身份验证和权限控制。
6.1. CustomUserDetailsService.java
:
package com.example.security;import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.stream.Collectors;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate final UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询数据库,加载用户及其角色User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));// 将角色转换为 GrantedAuthorityreturn new User(user.getUsername(),user.getPassword(),user.isEnabled(),true, true, true,user.getRoles().stream().map(role -> new org.springframework.security.core.authority.SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()));}
}
6.2. UserRepository.java
:
package com.example.security;import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);
}
7. 配置 Spring Security
在SecurityConfig
中,配置数据库认证,并启用方法级别的权限控制。
7.1. SecurityConfig.java
:
package com.example.security;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级权限控制
public class SecurityConfig {@Autowiredprivate final CustomUserDetailsService customUserDetailsService;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login", "/public/**").permitAll().anyRequest().authenticated().and().formLogin().permitAll().and().logout().permitAll();return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());}
}
8. 使用@PreAuthorize
注解控制方法权限
在控制器方法上使用 @PreAuthorize
注解进行权限控制。比如,你可以根据角色控制谁能访问哪些页面。
8.1. WebController.java
:
package com.example.security;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class WebController {@GetMapping("/admin")@PreAuthorize("hasRole('ROLE_ADMIN')") // 只有角色为 ROLE_ADMIN 的用户可以访问public String adminPage() {return "admin";}@GetMapping("/user")@PreAuthorize("hasRole('ROLE_USER')") // 只有角色为 ROLE_USER 的用户可以访问public String userPage() {return "user";}@GetMapping("/dashboard")@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')") // 角色为 ROLE_ADMIN 或 ROLE_USER 的用户可以访问public String dashboard() {return "dashboard";}
}
9. 配置启动类
确保你有一个 Application
启动类来启动 Spring Boot 应用。
9.1. Application.java
:
package com.example.security;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
10. 配置 application.properties
确保你的 application.properties
中包含数据库连接配置。
application.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/security_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.security.user.name=admin
spring.security.user.password=admin
11. 运行和测试
- 启动 Spring Boot 应用。
- 使用
admin
和user
用户分别登录,访问/admin
、/user
和/dashboard
页面,检查是否能根据角色权限控制访问。
这个示例展示了如何在 Spring Boot 中使用数据库存储用户和角色信息,并通过 @PreAuthorize
注解进行方法级别的权限控制。我们通过自定义 UserDetailsService
从数据库中加载用户信息并设置相应的角色。这样,Spring Security 会根据用户的角色来控制对不同控制器方法的访问权限。