如何在前后端分离项目中,使用Spring Security
使用 WebSecurityConfigurationAdapter
在前后端分离的架构中,通常使用 Token 进行认证和授权是一种常见的做法。Token 可以是 JSON Web Token(JWT),用于在客户端和服务器之间传递身份信息和访问控制信息。下面我将详细介绍如何在 Spring Boot 后端和 Vue 前端应用中使用 Token(JWT)来实现认证和授权。
后端(Spring Boot + Spring Security + JWT)
1. 添加依赖
首先,确保在你的 Spring Boot 项目中添加相关依赖:
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. 配置Spring Security和JWT
创建一个配置类来配置 Spring Security 和 JWT。
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
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.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.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
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;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll() // 公开访问的API
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用Spring Security的Session管理
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtTokenUtil jwtTokenUtil() {
return new JwtTokenUtil();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
3. JWT Token Util 类
创建一个用于生成和验证 JWT 的工具类 JwtTokenUtil
。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
4. JWT 请求过滤器
创建一个 JWT 请求过滤器来拦截和验证请求中的 JWT。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
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;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
5. 登录和生成JWT
创建一个登录接口,验证用户身份并生成JWT返回给客户端。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JwtAuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping(value = "/api/login")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
前端(Vue)
在前端Vue应用中,你需要处理JWT的存储和使用。
1. 登录请求
在Vue组件中实现用户登录请求,获取JWT并存储到LocalStorage。
// 简化的登录方法示例
login(username, password) {
return axios.post('/api/login', { username, password })
.then(response => {
if (response.data && response.data.token) {
// 将JWT Token保存到LocalStorage中
localStorage.setItem('jwtToken', response.data.token);
}
return response.data;
});
}
2. JWT Token的存储和使用
在Vue应用中,通常将JWT Token存储在LocalStorage中,并在每次请求时将Token添加到请求的Header中,以便后端验证用户的身份和权限。
// 设置全局的axios默认请求头,添加Authorization字段
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('jwtToken');
// 示例:获取用户信息的方法
getUserInfo() {
return axios.get('/api/userinfo')
.then(response => {
return response.data;
});
}
// 示例:注销方法
logout() {
localStorage.removeItem('jwtToken'); // 清除本地存储的JWT Token
// 可以选择发送注销请求到后端,使后端的Token失效
}
3. 路由守卫和权限控制
使用Vue Router的导航守卫(路由守卫),根据用户的登录状态和权限信息控制页面的访问。
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import AdminPanel from './views/AdminPanel.vue'
import Login from './views/Login.vue'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { requiresAuth: true } // 需要登录才能访问的页面
},
{
path: '/admin',
name: 'admin',
component: AdminPanel,
meta: { requiresAuth: true, requiresAdmin: true } // 需要管理员权限才能访问的页面
},
{
path: '/login',
name: 'login',
component: Login
}
]
});
router.beforeEach((to, from, next) => {
const jwtToken = localStorage.getItem('jwtToken');
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!jwtToken) {
next('/login'); // 没有登录,跳转到登录页面
} else {
// 验证Token是否过期等逻辑可以根据实际需求添加
next();
}
} else {
next(); // 不需要登录的页面直接放行
}
});
export default router;
4. 处理Token过期和刷新
在使用JWT时,需要处理Token过期的情况,一般的做法是在前端捕获HTTP请求返回的401状态码(未授权),然后根据情况重新获取新的Token。
// 示例:处理HTTP请求返回的401状态码
axios.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response.status === 401) {
// 清除本地存储的Token
localStorage.removeItem('jwtToken');
// 跳转到登录页面或者重新获取新的Token等操作
// 可以根据实际情况进行处理
}
return Promise.reject(error);
}
);
使用 SecurityFilterChain
在最新版本的 Spring Security 中,WebSecurityConfigurerAdapter
已经被弃用。取而代之的是新的配置方式,直接通过配置类和 SecurityFilterChain
Bean 来配置安全性。下面是如何在不使用 WebSecurityConfigurerAdapter
的情况下配置 Spring Security 和 JWT 认证。
后端(Spring Boot + Spring Security + JWT)
1. 添加依赖
首先,确保在你的 Spring Boot 项目中添加相关依赖:
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. 配置 Spring Security 和 JWT
不使用 WebSecurityConfigurerAdapter
的情况下,可以使用 SecurityConfigurer
和 SecurityFilterChain
进行配置。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
public SecurityConfig(JwtRequestFilter jwtRequestFilter,
CustomAuthenticationEntryPoint customAuthenticationEntryPoint,
CustomAccessDeniedHandler customAccessDeniedHandler) {
this.jwtRequestFilter = jwtRequestFilter;
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
this.customAccessDeniedHandler = customAccessDeniedHandler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(authorizeRequests ->
authorizeRequests.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler))
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
3. JWT Token Util 类
创建一个用于生成和验证 JWT 的工具类 JwtTokenUtil
。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
4. JWT 请求过滤器
创建一个 JWT 请求过滤器来拦截和验证请求中的 JWT。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
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;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
5. 登录和生成 JWT
创建一个登录接口,验证用户身份并生成 JWT 返回给客户端。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JwtAuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping(value = "/api/login")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
前端(Vue)
在前端 Vue 应用中,你需要处理 JWT 的存储和使用。
1. 登录请求
在 Vue 组件中实现用户登录请求,获取 JWT 并存储到 LocalStorage。
// 登录方法
login(username, password) {
return axios.post('/api/login', { username, password })
.then(response => {
if (response.data && response.data.token) {
// 将 JWT Token 保存到 LocalStorage 中
localStorage.setItem('jwtToken', response.data.token);
}
return response.data;
});
}
2. JWT Token 的存储和使用
在 Vue 应用中,通常将 JWT Token 存储在 LocalStorage 中,并在每次请求时将 Token 添加到请求的 Header 中,以便后端验证用户的身份和权限。
// 设置全局的 axios 默认请求头,添加 Authorization 字段
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('jwtToken');
```javascript
// 示例:获取用户信息的方法
getUserInfo() {
return axios.get('/api/userinfo')
.then(response => {
return response.data;
});
}
// 示例:注销方法
logout() {
localStorage.removeItem('jwtToken'); // 清除本地存储的JWT Token
// 可以选择发送注销请求到后端,使后端的Token失效
}
3. 路由守卫和权限控制
使用 Vue Router 的导航守卫(路由守卫),根据用户的登录状态和权限信息控制页面的访问。
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import AdminPanel from './views/AdminPanel.vue'
import Login from './views/Login.vue'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { requiresAuth: true } // 需要登录才能访问的页面
},
{
path: '/admin',
name: 'admin',
component: AdminPanel,
meta: { requiresAuth: true, requiresAdmin: true } // 需要管理员权限才能访问的页面
},
{
path: '/login',
name: 'login',
component: Login
}
]
});
router.beforeEach((to, from, next) => {
const jwtToken = localStorage.getItem('jwtToken');
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!jwtToken) {
next('/login'); // 没有登录,跳转到登录页面
} else {
// 验证Token是否过期等逻辑可以根据实际需求添加
next();
}
} else {
next(); // 不需要登录的页面直接放行
}
});
export default router;
4. 处理Token过期和刷新
在使用JWT时,需要处理Token过期的情况,一般的做法是在前端捕获HTTP请求返回的401状态码(未授权),然后根据情况重新获取新的Token。
// 示例:处理HTTP请求返回的401状态码
axios.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response.status === 401) {
// 清除本地存储的Token
localStorage.removeItem('jwtToken');
// 跳转到登录页面或者重新获取新的Token等操作
// 可以根据实际情况进行处理
}
return Promise.reject(error);
}
);
新的配置方式主要区别在于 Spring Security 中不再使用 WebSecurityConfigurerAdapter
进行配置,而是通过 SecurityFilterChain
和其他配置类来实现同样的功能。下面详细介绍它们的区别和新的配置方法。
新旧版对比
旧版(使用 WebSecurityConfigurerAdapter
)
旧版配置通常使用一个继承 WebSecurityConfigurerAdapter
的类来进行安全配置。
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 添加 JWT 过滤器
http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 定义 JWT 过滤器
@Bean
public JwtRequestFilter jwtRequestFilter() {
return new JwtRequestFilter();
}
}
新版(使用 SecurityFilterChain
)
新版配置不再使用 WebSecurityConfigurerAdapter
,而是通过定义 SecurityFilterChain
Bean 来配置安全性。
新版配置示例
1. SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
public SecurityConfig(JwtRequestFilter jwtRequestFilter,
CustomAuthenticationEntryPoint customAuthenticationEntryPoint,
CustomAccessDeniedHandler customAccessDeniedHandler) {
this.jwtRequestFilter = jwtRequestFilter;
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
this.customAccessDeniedHandler = customAccessDeniedHandler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler))
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
2. 其他相关配置和组件
JwtTokenUtil
:处理 JWT 生成和验证的工具类。JwtRequestFilter
:JWT 过滤器,用于在请求到达时验证 JWT。
JwtTokenUtil.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
JwtRequestFilter.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
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;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
新旧版区别总结
-
配置类的结构:
- 旧版:通过继承
WebSecurityConfigurerAdapter
,重写configure(HttpSecurity http)
方法进行配置。 - 新版:通过定义
SecurityFilterChain
Bean,直接配置HttpSecurity
。
- 旧版:通过继承
-
认证管理器:
- 旧版:直接在
WebSecurityConfigurerAdapter
中配置。 - 新版:通过
AuthenticationConfiguration
获取AuthenticationManager
。
- 旧版:直接在
-
注入过滤器:
- 旧版:使用
http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class)
。 - 新版:使用
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
。
- 旧版:使用
迁移步骤
-
删除
WebSecurityConfigurerAdapter
:- 删除继承自
WebSecurityConfigurerAdapter
的类。
- 删除继承自
-
定义
SecurityFilterChain
Bean:- 新建一个配置类,定义
SecurityFilterChain
Bean,配置HttpSecurity
。
- 新建一个配置类,定义
-
调整其他配置:
- 根据新方式调整
AuthenticationManager
和其他相关配置。
- 根据新方式调整
通过以上步骤,你可以从旧版的 WebSecurityConfigurerAdapter
配置迁移到新版的 SecurityFilterChain
配置,同时保持应用的安全性和功能。
大致流程
- 引入依赖:确保引入 Spring Security 和 JWT 相关的依赖。
- 配置安全性:使用 Java 配置类(如 SecurityConfig)来设置 HTTP 安全性、CSRF、会话管理等。
- 实现 JWT 相关逻辑:创建工具类和过滤器来处理 JWT 的生成、解析和验证。
- 自定义异常处理:编写自定义的认证入口点和访问拒绝处理器。
- 实现用户服务:实现 UserDetailsService 接口,加载用户信息。
- 编写控制器:创建登录和注册接口,处理用户认证和注册请求。
- 测试与验证:启动应用程序,使用工具测试各个接口,确保功能正常。
- 点赞
- 收藏
- 关注作者
评论(0)