在前后端分离的应用中,通常前端和后端是通过 API 接口进行通信的,前端负责用户界面和用户交互,而后端处理业务逻辑和数据存取。在这种架构下,登录认证流程需要特别设计,以确保安全性并提供良好的用户体验。以下是如何在 Spring Security 中实现前后端分离登录和跳转的详细步骤:
### 1. **配置 Spring Security**
首先,你需要配置 Spring Security,以便它可以处理基于 API 的认证请求而不是传统的表单登录。
**步骤:**
1. **配置 `SecurityConfig`** ```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.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable CSRF for API
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // Allow unauthenticated access to auth endpoints
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public AuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
```
2. **创建自定义认证过滤器**
由于我们使用前后端分离,认证通常通过 REST API 完成,所以我们需要自定义 `UsernamePasswordAuthenticationFilter`。 ```java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
ObjectMapper mapper = new ObjectMapper();
LoginRequest loginRequest = mapper.readValue(request.getInputStream(), LoginRequest.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// Return a success response to the frontend
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("Authentication successful");
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
// Return a failure response to the frontend
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication failed");
}
}
```
3. **实现登录请求和响应**
在自定义过滤器中,你可以控制成功和失败的响应。你可以根据需要修改成功和失败的处理方式,比如返回 JWT token。
### 2. **前端实现**
前端部分负责发送登录请求并处理响应。通常使用 AJAX 或 Fetch API 发送请求。
**步骤:**
1. **发送登录请求**
使用 JavaScript 发送 POST 请求到后端登录接口。 ```javascript
async function login(username, password) {
try {
let response = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (response.ok) {
// Handle success
let result = await response.text();
console.log(result); // Handle result, e.g., redirect to a different page or store token
} else {
// Handle error
let error = await response.text();
console.error('Authentication failed:', error);
}
} catch (error) {
console.error('Error during authentication:', error);
}
}
```
2. **处理成功和失败**
在处理登录结果时,可以根据需要进行页面重定向、存储 JWT token 等操作。
```javascript
if (response.ok) {
// Redirect to the main page or store the token
window.location.href = '/home';
} else {
alert('Login failed');
}
```
### 3. **处理 Token**
如果使用 JWT 或其他 token 机制进行认证,可以在用户登录成功后,将 token 存储在浏览器中(例如在 Local Storage 或 Cookies 中),并在后续的 API 请求中将 token 附加到请求头中。
**步骤:**
1. **存储 Token** ```javascript
async function login(username, password) {
try {
let response = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (response.ok) {
let result = await response.json();
localStorage.setItem('authToken', result.token); // Save token to local storage
window.location.href = '/home';
} else {
alert('Login failed');
}
} catch (error) {
console.error('Error during authentication:', error);
}
}
```
2. **在 API 请求中附加 Token** ```javascript
async function fetchProtectedResource() {
const token = localStorage.getItem('authToken');
try {
let response = await fetch('http://localhost:8080/api/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
let data = await response.json();
console.log('Protected data:', data);
} else {
console.error('Failed to fetch protected resource');
}
} catch (error) {
console.error('Error fetching protected resource:', error);
}
}
```
### 4. **后端实现 Token 验证**
如果使用 JWT,确保后端能够验证 token 并解析用户信息。
**步骤:**
1. **创建 JWT 过滤器** ```java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
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 UsernamePasswordAuthenticationFilter {
private final String secretKey = "your_secret_key";
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// Generate a JWT token
String token = Jwts.builder()
.setSubject(authResult.getName())
.signWith(SignatureAlgorithm.HS512, secretKey.getBytes())
.compact();
response.setHeader("Authorization", "Bearer " + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication failed");
}
}
```
2. **验证 Token**
在配置中添加 JWT 过滤器以验证请求中的 token。 ```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}