当前位置: 首页 > news >正文

如何在前后端分离项目中,使用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 {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtRequestFilter jwtRequestFilter;@Autowiredprivate CustomAuthenticationEntryPoint customAuthenticationEntryPoint;@Autowiredprivate CustomAccessDeniedHandler customAccessDeniedHandler;@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected 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);}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic JwtTokenUtil jwtTokenUtil() {return new JwtTokenUtil();}@Bean@Overridepublic 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 {@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate 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) {// 清除本地存储的TokenlocalStorage.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 的情况下,可以使用 SecurityConfigurerSecurityFilterChain 进行配置。

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;}@Beanpublic 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();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic 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 {@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate 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) {// 清除本地存储的TokenlocalStorage.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 {@Overrideprotected 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);}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 定义 JWT 过滤器@Beanpublic 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;}@Beanpublic 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();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic 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 {@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected 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);}
}

新旧版区别总结

  1. 配置类的结构

    • 旧版:通过继承 WebSecurityConfigurerAdapter,重写 configure(HttpSecurity http) 方法进行配置。
    • 新版:通过定义 SecurityFilterChain Bean,直接配置 HttpSecurity
  2. 认证管理器

    • 旧版:直接在 WebSecurityConfigurerAdapter 中配置。
    • 新版:通过 AuthenticationConfiguration 获取 AuthenticationManager
  3. 注入过滤器

    • 旧版:使用 http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class)
    • 新版:使用 http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)

迁移步骤

  1. 删除 WebSecurityConfigurerAdapter

    • 删除继承自 WebSecurityConfigurerAdapter 的类。
  2. 定义 SecurityFilterChain Bean

    • 新建一个配置类,定义 SecurityFilterChain Bean,配置 HttpSecurity
  3. 调整其他配置

    • 根据新方式调整 AuthenticationManager 和其他相关配置。

通过以上步骤,你可以从旧版的 WebSecurityConfigurerAdapter 配置迁移到新版的 SecurityFilterChain 配置,同时保持应用的安全性和功能。

大致流程

  1. 引入依赖:确保引入 Spring Security 和 JWT 相关的依赖。
  2. 配置安全性:使用 Java 配置类(如 SecurityConfig)来设置 HTTP 安全性、CSRF、会话管理等。
  3. 实现 JWT 相关逻辑:创建工具类和过滤器来处理 JWT 的生成、解析和验证。
  4. 自定义异常处理:编写自定义的认证入口点和访问拒绝处理器。
  5. 实现用户服务:实现 UserDetailsService 接口,加载用户信息。
  6. 编写控制器:创建登录和注册接口,处理用户认证和注册请求。
  7. 测试与验证:启动应用程序,使用工具测试各个接口,确保功能正常。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 英特尔凌动® P5300 和 P5700 处理器使企业能够优化现代网络基础架构、安全加速器和存储设备之间的性能和成本平衡。
  • leetcode:1822. 数组元素积的符号(python3解法)
  • JMeter 性能测试工具入门与实践
  • 高翔【自动驾驶与机器人中的SLAM技术】学习笔记(七)卡尔曼滤波器三:卡尔曼滤波器公式推导【转载】
  • Zookeeper的监听机制
  • *算法训练(leetcode)第四十七天 | 并查集理论基础、107. 寻找存在的路径
  • 从理论到实践网络编程模型:(BIO、NIO、AIO)同步与异步模型的原理与应用 (五)
  • shell脚本编程实践(五)
  • 刷完Armbian的盒子后根目录空间太小解决方案
  • 高阶数据结构——LRU Cache
  • pod详解 list-watch机制 预选优选策略 如何指定节点调度pod
  • 10.1 使用ansible部署 redis-exporter
  • Python 机器学习求解 PDE 学习项目 基础知识(3)matplotlib 画函数热图
  • 十六、【Python】基础教程 - 【Flask】网络编程开发
  • SpringBoot可以同时处理多少请求?
  • 2017年终总结、随想
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • axios 和 cookie 的那些事
  • bootstrap创建登录注册页面
  • ES6之路之模块详解
  • Flex布局到底解决了什么问题
  • mysql常用命令汇总
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 聊聊sentinel的DegradeSlot
  • 你不可错过的前端面试题(一)
  • 前端攻城师
  • 源码安装memcached和php memcache扩展
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • 阿里云API、SDK和CLI应用实践方案
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • #14vue3生成表单并跳转到外部地址的方式
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • (8)STL算法之替换
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (HAL库版)freeRTOS移植STMF103
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (NSDate) 时间 (time )比较
  • (八)c52学习之旅-中断实验
  • (二)c52学习之旅-简单了解单片机
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (南京观海微电子)——COF介绍
  • (转载)CentOS查看系统信息|CentOS查看命令
  • **PHP二维数组遍历时同时赋值
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • *上位机的定义
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .net core Redis 使用有序集合实现延迟队列
  • .NET Core 通过 Ef Core 操作 Mysql
  • .net dataexcel winform控件 更新 日志
  • .NET Framework 4.6.2改进了WPF和安全性
  • .Net(C#)自定义WinForm控件之小结篇
  • .net6 webapi log4net完整配置使用流程
  • @Slf4j idea标红Cannot resolve symbol ‘log‘
  • @WebServiceClient注解,wsdlLocation 可配置