Spring Security 是 Spring 生态中用于身份验证和授权的强大框架。
常用的身份校验方案
| 特性 | Basic Authentication | Token Authentication | OAuth Authentication |
|---|---|---|---|
| 原理 | 客户端在每个请求的 Authorization 头中发送 Base64 编码的用户名和密码。 | 客户端在登录后获取一个令牌(Token),后续请求在 Authorization 头中携带该令牌。 | 客户端通过授权服务器获取访问令牌(Access Token),并使用该令牌访问资源服务器。 |
| 安全性 | 较低。密码以 Base64 编码传输,容易被拦截和解码。建议与 HTTPS 结合使用。 | 较高。令牌通常是加密的,且可以设置过期时间。支持 HTTPS 进一步增强安全性。 | 高。支持多种授权流程(如授权码模式),令牌有较短的生命周期,支持刷新令牌。 |
| 复杂度 | 简单。易于实现,适合小型应用或内部系统。 | 中等。需要实现令牌的生成、验证和管理。 | 高。需要实现授权服务器、资源服务器和客户端之间的复杂交互。 |
| 性能 | 较高。每个请求都需要验证用户名和密码,可能增加服务器负载。 | 较高。令牌验证通常比密码验证更快,但需要额外的令牌管理开销。 | 较低。涉及多次网络请求(如获取令牌、刷新令牌),可能增加延迟。 |
| 适用场景 | 适合简单的内部系统或测试环境。 | 适合需要较高安全性的 Web 应用和 API。 | 适合需要第三方授权的分布式系统或开放平台。 |
| 扩展性 | 差。难以扩展,不支持复杂的授权需求。 | 较好。支持自定义令牌和权限管理。 | 优秀。支持多种授权流程和第三方集成。 |
| 令牌管理 | 无。每次请求都需要发送用户名和密码。 | 需要管理令牌的生成、存储、验证和过期。 | 需要管理访问令牌、刷新令牌和授权码的生命周期。 |
| 跨域支持 | 有限。需要额外的配置支持跨域请求。 | 较好。令牌可以跨域使用,适合分布式系统。 | 优秀。支持跨域和第三方授权。 |
| 示例协议 | HTTP Basic Auth | JWT (JSON Web Token) | OAuth 2.0 |
| 支持刷新 | 无 | JWT (JSON Web Token)可通过refresh token实现 | OAuth 2.0中有 refrsh token机制 |
用户(User)-角色(Role)-权限(Permission) 的权限管理方案
-- 用户表
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE roles (
role_id INT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) NOT NULL UNIQUE,
description TEXT
);
-- 权限表
CREATE TABLE permissions (
permission_id INT AUTO_INCREMENT PRIMARY KEY,
permission_name VARCHAR(50) NOT NULL UNIQUE,
description TEXT
);
-- 用户角色关联表
CREATE TABLE user_roles (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE
);
-- 角色权限关联表
CREATE TABLE role_permissions (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) ON DELETE CASCADE
);
JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。它通常用于身份验证和授权,特别是在分布式系统和微服务架构中。
1. JWT 的结构
JWT 由三部分组成,用 . 分隔:
- Header: 包含令牌类型(如 JWT)和签名算法(如 HMAC SHA256)。
- Payload: 包含声明(claims),如用户信息、角色、权限等。
- Signature: 用于验证令牌的完整性和真实性。
示例 JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2. JWT 的工作流程
- 用户登录: 客户端发送用户名和密码到服务器。
- 生成 JWT: 服务器验证用户信息后,生成 JWT 并返回给客户端。
- 客户端存储 JWT: 客户端将 JWT 存储在本地(如 localStorage 或 cookie)。
- 发送 JWT: 客户端在后续请求的
Authorization头中携带 JWT。 - 验证 JWT: 服务器验证 JWT 的签名和有效期,并提取用户信息。
例如用postman 在 head中的Authorization中添加 jwt
SpringBoot 集成 Spring Security
以下是在 Spring Boot 项目中集成 Spring Security 的最佳实践,涵盖基本配置、身份验证、授权、密码加密、CSRF 防护等内容。
1. 添加依赖
在 pom.xml 中添加 Spring Security 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 基本配置
创建一个 Spring Security 配置类,由于版本之间差异较大,很多方法已经Deprecated了,这里的springSecurity 是 6.3.3
Spring Security 6.3.3 及之后
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final Logger log = LoggerFactory. getLogger(SecurityConfig.class);
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("!-- securityFilterChain--");
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
// 不拦截特定的 URL,这里以 /public/** 为例
.requestMatchers("/public/**").permitAll()
.requestMatchers("/login").permitAll()
// 其他请求需要认证
.requestMatchers("/user/**").authenticated()
.requestMatchers("/admin/**").hasRole("admin")
.anyRequest().authenticated()
)
.formLogin(formLogin ->
formLogin.loginPage("/login").permitAll()
)
.logout(logout ->
logout.permitAll()
)
.exceptionHandling(exceptionHandling ->
exceptionHandling
.accessDeniedHandler(accessDeniedHandler())
);
return http.build();
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> {
log.info("!--- access deny ");
response.sendRedirect("/access-denied");
};
}
}
3. 用户详情服务
实现 UserDetailsService 接口,用于加载用户信息。
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
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;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库或其他存储中加载用户信息
if ("admin".equals(username)) {
return User.withUsername("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build();
} else if ("user".equals(username)) {
return User.withUsername("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build();
} else {
throw new UsernameNotFoundException("User not found");
}
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. 密码加密
使用 BCryptPasswordEncoder 对密码进行加密。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
在注册或更新用户时,确保密码经过加密后存储:
String rawPassword = "user123";
String encodedPassword = passwordEncoder.encode(rawPassword);
5. CSRF 防护
Spring Security 默认启用 CSRF 防护。对于 REST API,可以禁用 CSRF 防护:
@Configuration
@EnableWebSecurity
public class CsrfSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf((csrf) -> csrf. disable());
return http. build();
}
}
6. JWT 集成(可选)
对于基于令牌的身份验证,可以集成 JWT(JSON Web Token)。
添加 JWT 依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.6</version>
</dependency>
JWT 工具类
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
private String SECRET_KEY = "jakeSecret";
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().
claims(claims).
subject(subject).
issuedAt(new Date(System.currentTimeMillis())).
expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)).signWith(this.getSigningKey())
.compact();
}
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(this.SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
...
...
// 验证token..
// 验证 有效期,..
// 是否刷新,生成新token等..
}
** 如果需要全局验证登陆,可以添加 JWT 过滤器 **
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 从请求头中获取 JWT
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // 去掉 "Bearer " 前缀
// 验证 JWT
if (jwtUtil.validateToken(token)) {
// 如果 JWT 有效,将用户信息放入请求中
Claims claims = jwtUtil.parseToken(token);
request.setAttribute("username", claims.getSubject());
} else {
// 如果 JWT 无效,返回 401 未授权
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} else {
// 如果没有 JWT,返回 401 未授权
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 放行请求
filterChain.doFilter(request, response);
}
}
7. 测试
启动应用后,访问受保护的端点(如 /admin),系统会要求登录。登录后,可以访问授权资源。
8. 总结
以上是 Spring Boot 集成 Spring Security 的基础应用、其他可根据实际需求,可以进一步扩展和优化配置。