如何在前后端分离项目中,使用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
的情况下,可以使用 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;}@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);}
}
新旧版区别总结
-
配置类的结构:
- 旧版:通过继承
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 接口,加载用户信息。
- 编写控制器:创建登录和注册接口,处理用户认证和注册请求。
- 测试与验证:启动应用程序,使用工具测试各个接口,确保功能正常。
相关文章:

如何在前后端分离项目中,使用Spring Security
使用 WebSecurityConfigurationAdapter 在前后端分离的架构中,通常使用 Token 进行认证和授权是一种常见的做法。Token 可以是 JSON Web Token(JWT),用于在客户端和服务器之间传递身份信息和访问控制信息。下面我将详细介绍如何在…...

c#怎么折叠代码快捷
在C#中,你可以使用快捷键来折叠或展开代码,以便更好地管理和浏览代码。以下是一些常用的快捷键: 折叠所有方法:使用Ctrl M O。折叠或展开当前方法:使用Ctrl M M。展开所有方法:使用…...

数据库篇--八股文学习第十七天| 什么是慢查询?原因是什么?可以怎么优化?;undo log、redo log、binlog 有什么用?
1、什么是慢查询?原因是什么?可以怎么优化? 答: 数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。 原因: 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询&…...

插件、cookie存储,json,ajax详解
1.插件 下载地址:http://github.com/carhartl/jquery-cookie/zipball/v1.4.1 使用文档:jquery-cookie(github.com) 2.存储 初学前端用的是localStorage和sessionStorage,后来又引入了cookie进行存储。 localStorage使用如下 sessionStor…...

快速上手Spring Boot
快速上手Spring Boot (qq.com)...

思路超清晰的 LVS-NAT 模式实验部署
目录 一、实验原理 1、实验基础配置图 2、实验原理 二、实验环境准备 1、准备四台红帽9的主机 2、四台主机的基础配置 (1)client 1)配置主机名:client 2)配置ip:172.25.254.200 (2)lv…...

Android实时通信:WebSocket与WebRTC的应用与优化
文章目录 一、WebSocket在Android中的应用1.1 简介1.2 示例 二、WebRTC在Android中的应用2.1 简介2.2 示例 三、Android实时通信的优化策略3.1 网络优化3.2 延迟降低 四、Android实时通信的安全问题五、实时通信协议的比较六、总结 在现代移动应用中,实时通信已经成…...

力扣刷题之3131.找出与数组相加的整数I
题干描述 给你两个长度相等的数组 nums1 和 nums2。 数组 nums1 中的每个元素都与变量 x 所表示的整数相加。如果 x 为负数,则表现为元素值的减少。 在与 x 相加后,nums1 和 nums2 相等 。当两个数组中包含相同的整数,并且这些整数出现的频…...

非线性表之堆的实际应用和二叉树的遍历
目录 前言:前一篇我已经介绍过了二叉树和堆的介绍和相关代码的实现 一、堆的实现 1.1堆向上调整算法 1.2堆向下调整算法 二、堆的应用 2.1堆的排序 2.2TOP-K问题 三、二叉树的遍历 3.1 二叉树的创建 3.2遍历介绍 3.3前序遍历 3.4中序遍历 3.5后序遍历 …...

os.path库学习之splitext函数
os.path库学习之splitext函数 一、简介 os.path.splitext 是 Python 标准库 os.path 模块中的一个函数,用于将文件名分割成两部分:文件名和扩展名。这个函数非常有用,特别是在处理文件路径和文件扩展名时。 二、语法和参数 语法: os.path…...

Python知识点:如何使用Sqlmap进行SQL注入测试
使用 Sqlmap 进行 SQL 注入测试是一个非常有效的方法,它可以帮助你自动化地检测和利用 SQL 注入漏洞。以下是使用 Sqlmap 进行 SQL 注入测试的详细步骤: 1. 安装 Sqlmap 首先,你需要安装 Sqlmap。Sqlmap 是一个 Python 工具,因此…...

Android Gradle开发与应用 (一) : Gradle基础
Gradle基础 Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建工具。它使用一种基于 Groovy 的特定领域语言(DSL)来声明项目设置,而不是传统的 XML。Gradle 提供了灵活的构建脚本和强大的依赖管理功能,使其成为…...

Linux驱动开发—设备树分析:GPIO,中断,时钟信息,CPU信息
书接上回:Linux驱动开发—设备树基本概念,语法详解-CSDN博客 文章目录 使用设备树描述中断使用设备树描述CPU节点CPU 节点缓存节点总结 使用设备树描述时钟总结 使用设备树描述GPIO示例设备树节点逐行解析GPIO 单元 使用设备树描述中断 在NXP 官方中截…...

Java全栈解密:从JVM内存管理到Spring框架,揭秘垃圾回收、类加载机制与Web开发精髓的全方位旅程
JVM内存划分 在JVM中,每个线程有自己的虚拟机栈,而整个JVM实例共享一些内存区域。JVM的内存划分主要包括四个部分:程序计数器、虚拟机栈、堆区和方法区(元数据区)。 程序计数器:程序计数器用于存储当前线程…...

【探索Linux】P.46(高级IO —— 五种IO模型简介 | IO重要概念)
阅读导航 引言一、五种IO模型1. 阻塞IO(1)定义(2)特点 2. 非阻塞IO(1)定义(2)特点 3. IO多路复用(1)定义(2)特点 4. 信号驱动IO&#…...

【MongoDB 】MongoDB 介绍及应用,设计到4个案例
MongoDB 介绍概述 基础概念 MongoDB 是非关系型数据库,也就是nosql,存储json数据格式会非常灵活,要比数据库mysql/MariaDB更好,同时也能为mysql/MariaDB分摊一部分的流量压力。 对于经常读写的数据他会存入内存,如此…...

AI浪潮下的程序员生存指南:如何在智能时代锻造不可替代的核心竞争力
人工智能时代,程序员如何保持核心竞争力? 随着AIGC(如chatgpt、midjourney、claude等)大语言模型接二连三的涌现,AI辅助编程工具日益普及,程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…...

Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC]
文章目录 Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现Journyx soap_cgi.pyc接口XML外部实体注入漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章内的相关技术…...

vue 日期控件 100天内的时间禁用不允许选择
vue 日期控件 100天内的时间禁用不允许选择,可以从101天选起 比如,2024年8月9号开始,100天内禁止选择,第101天之后的日期可以选,效果如图所示 // 日期控件代码 加上 :picker-options"pickerOptions" <…...

服务器HTTP响应头安全性优化与漏洞修复方案
在对服务器进行漏洞扫描后,通常会发现一些常见的安全漏洞,特别是涉及HTTP响应头的问题。以下是本次扫描过程中发现的漏洞问题以及对应的修复方案 1.X-Content-Type-Options 响应头缺失 描述: 缺失此响应头可能导致浏览器错误地解析资源类型,存在MIME类型混淆攻击的风险。 …...

4.定时器(TIMER)
理论 预分频寄存器(TIMx_PSC):由于时钟源为:72MHz,T 1/f 1/72MHz,由于不好计算周期时间,则需要分频,若分72则T 1/1MHz 1us(1MHz 一百万秒) 计数方式:向上(递增到某个数触发中断)、向下(递…...

java springboot mqtt控制海康摄像头
GHHKControlService 接口 package org.gh.ghhk.service;public interface GHHKControlService {boolean monitorControl(String payload);}GHHKControlServiceImpl 实现类 package org.gh.ghhk.service.impl;import com.alibaba.fastjson.JSONArray; import com.alibaba.…...

AI大模型02:Prompt Engineering 提示工程
一、什么是提示工程(Prompt Engineering) 1.提示工程,也叫“指令工程” (1)Prompt 就是我们给大模型发送的指令,或者说是在聊天对话框中发送的内容。 Prompt是AGI时代的编程语言。 Prompt是去控制大模型的…...

EasyExcel动态表头导出
1、封装方法 package com.skybird.iot.base.utils;import cn.hutool.core.util.StrUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.w…...

可视化基础的设计四大原则
一个好的数据可视化设计可以帮助观众迅速理解数据背后的意义。然而,如何确保我们的可视化设计既美观又简单易懂呢?本文将介绍四大设计原则——亲密原则、对比原则、对齐原则和重复原则。 1、 亲密原则(Proximity) 定义与应用&am…...

MySQL基础练习题27-上升的温度
目录 题目 准备数据 分析数据 总结 题目 找出与之前(昨天的)日期相比温度更高的所有日期的 id 。 准备数据 ## 创建库 create database db; use db;## 创建表 Create table If Not Exists Weather (id int, recordDate date, temperature int);#…...

只出现一次的数字 II
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。 示例 1: 输入:nums [2,2,3,2]…...

第十一章 数据仓库和商务智能 10分
11.1.0语境关系图 11.1 Q 建立数据仓库,有哪些步骤?如何建设?【6 个步骤非常重要!必须知道】 1. 理解需求(P)(目的明确,ETL) (1) 考虑业务目标和业务战略。 (2) 确定业…...

一篇文章带你解析完整数据结构-----满满干活值得收藏
数据结构是计算机科学中的一个重要分支,它涉及到计算机存储、组织数据的方式。以下是数据结构的主要知识点: 基本概念 数据(Data)。数据元素(Data Element):数据项(Data Item)&…...

11.3 用Python处理常见文件
欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 工💗重💗hao💗:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题.…...