如何在前后端分离项目中,使用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)方法进行配置。 - 新版:通过定义
SecurityFilterChainBean,直接配置HttpSecurity。
- 旧版:通过继承
-
认证管理器:
- 旧版:直接在
WebSecurityConfigurerAdapter中配置。 - 新版:通过
AuthenticationConfiguration获取AuthenticationManager。
- 旧版:直接在
-
注入过滤器:
- 旧版:使用
http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class)。 - 新版:使用
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)。
- 旧版:使用
迁移步骤
-
删除
WebSecurityConfigurerAdapter:- 删除继承自
WebSecurityConfigurerAdapter的类。
- 删除继承自
-
定义
SecurityFilterChainBean:- 新建一个配置类,定义
SecurityFilterChainBean,配置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类型混淆攻击的风险。 …...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
