在前后端分离的架构中,Spring Security 的配置与传统的单体应用有所不同。为了确保安全性和灵活性,我们需要对 Spring Security 进行适当的调整以适应这种架构。下面将详细介绍如何在前后端分离的应用程序中实现 Spring Security。
1. 理解前后端分离的安全需求
在前后端分离的应用程序中,前端通常是一个独立的 Web 应用或移动应用,而后端提供 RESTful API 或 GraphQL 接口。因此,我们需要考虑以下几点:
- 认证:用户登录后,前端应该获得一个令牌(如 JWT),并在后续请求中携带此令牌来证明身份。
- 授权:根据用户的权限级别,控制他们可以访问哪些资源。
- 跨域资源共享 (CORS):由于前端和后端可能部署在不同的域名上,需要处理 CORS 请求。
- 会话管理:避免使用基于 Cookie 的会话机制,转而采用无状态的身份验证方式。
2. 选择认证机制
对于前后端分离的应用,推荐使用 JSON Web Token (JWT) 或 OAuth2 来进行认证。这里我们主要讨论 JWT 的实现。
2.1 JSON Web Token (JWT)
JWT 是一种自包含的令牌格式,它允许我们在客户端存储用户信息,并且可以在每次 HTTP 请求时通过 Authorization Header 发送到服务器。JWT 包含三个部分:Header、Payload 和 Signature。
-
优点:
- 无状态:服务器不需要存储会话信息,减轻了服务器负担。
- 跨域友好:易于在不同域名之间传递。
- 简单易用:前端可以直接保存到 localStorage 或 sessionStorage 中。
-
缺点:
- 安全性依赖于密钥管理:如果私钥泄露,所有签发的 JWT 都可能被伪造。
- 不适合频繁更改权限场景:因为 JWT 是自签名的,一旦生成就难以撤销。
3. 配置 Spring Security
接下来我们将介绍如何配置 Spring Security 来支持 JWT 认证。
3.1 添加依赖
首先,在 pom.xml
文件中添加必要的依赖项:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
3.2 创建 JWT 工具类
创建一个工具类用于生成和解析 JWT:
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JwtUtil {
private static final String SECRET = "your-secret-key"; // 私钥,请确保足够复杂
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token)
.getBody();
}
}
3.3 自定义过滤器
编写一个自定义过滤器来拦截每个请求并验证 JWT:
java
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
try {
String jwt = header.substring(7);
Claims claims = JwtUtil.parseToken(jwt);
// 设置当前线程的安全上下文
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
claims.getSubject(), null, Collections.emptyList()));
} catch (Exception e) {
logger.warn("Failed to set user authentication: {}", e.getMessage());
}
}
filterChain.doFilter(request, response);
}
}
3.4 配置 Spring Security
最后,配置 Spring Security 以集成 JWT 和 CORS 支持:
java
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and() // 启用 CORS 支持
.csrf().disable() // 禁用 CSRF 保护(因为我们使用 JWT)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用会话
.and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll() // 允许未认证用户访问认证接口
.anyRequest().authenticated(); // 所有其他请求都需要认证
// 将自定义过滤器添加到过滤器链中
http.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
4. 前端实现
在前端部分,你需要:
- 在用户登录成功后保存返回的 JWT 到本地存储(如
localStorage
或sessionStorage
)。 - 在每次发送请求时,在
Authorization
头部添加Bearer <token>
。 - 当遇到 401 或 403 错误时,重定向至登录页面或显示适当的消息提示。