一. 简介
SpringSecurity是Spring家族中的一个安全管理框架; 相比与另一个安全框架Shiro, 它提供了更丰富的功能, 社区资源也比Shiro丰富;
一般来说中大型的项目都是使用SpringSecurity来做安全框架; 小项目有Shiro比较多,因为Shiro的上手更加的简单
一般Web应用的需要进行认证 和授权 认证: 验证当前访问系统的是不是本系统的用户, 并且要确认具体的哪个用户 授权: 经过认证后判断当前用户是否有权限进行某个操作而认证和授权也是SpringSecurity作为安全框架的核心功能
二. 快速入门
2.1 准备工作
先要搭建一个简单的SpringBoot工程
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.whhh</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建一个简单的测试接口
@RestController
public class HelloController {
@RequestMapping("hello")
public String hello(){
return "hello security!";
}
}
2.2 引入SpringSecurity
# 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
重新启动后访问,就会来到security默认的登录认证页面,必须登录之后才能对接口进行访问,这个后面我们可以自定义
用户: user 密码: 在启动类控制台打印
三. 认证
3.1 登录校验流程
3.2 SpringSecurity完成流程
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器;图中只展示了核心过滤器,其他的非核心过滤器并没有在途中展示
UsernamePasswordAuthenticationFilter: 负责处理我们在登录页面填写了用户名密码后的登录请求; **ExceptionTranslationFilter: ** 处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException;FilterSecurityInterceptor: 负责权限校验的过滤器;
我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及他们的顺序
3.3 认证流程详解
Authentication接口: 他的实现类,表示当前访问系统的用户,封装了用户相关信息;AuthenticationManager接口: 定义了认证Authentication的方法;UserDetailsService接口: 加载用户特定数据的核心接口,里面定义了一个根据用户名查询用户信息的方法;userDetails接口: 提供核心用户信息,通过UserDetailsService根据用户名获取处理的用户信息要封装成userDetails对象返回; 然后将这些信息封装到Authentication对象中;
3.3 准备工作
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.whhh</groupId>
<artifactId>02token</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>02token</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建启动类 创建controller接口 创建用户对象 创建UserDetailsService的实现类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
if(StringUtils.hasText(username) || !username.equals("admin")){
throw new RuntimeException("用户名或密码错误!");
}
// 为了方便就不查询数据库了
AuthUsers authUsers = new AuthUsers();
authUsers.setUsername("admin");
// 如果密码不加密则前面需要添加{noop},否则框架在校验密码进行解密时会报错
authUsers.setPassword("{noop}111111");
// TODO 查询对应的权限信息
// 手动添加权限
String[] authorities = {"test"};
// 把数据库封装成UserDetails
UserDetails userDetails = User.withUsername(authUsers.getUsername()).password(authUsers.getPassword()).authorities(authorities).build();
return userDetails;
}
}
3.4 登录接口实现
# SpringSecurity配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
// 加密(安全性高)
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问(放行)
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
}
登录接口controller
@RestController
public class LoginController {
@Autowired
AuthenticationManager authenticationManager;
@Resource
RedisTemplate redisTemplate;
@PostMapping("/user/login")
public ResponseData login(@RequestBody UserLoginDto userLoginDto){
// 登录
String username = userLoginDto.getUsername();
String password = userLoginDto.getPassword();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username,password);
// AuthenticationManager authenticate进行认证
Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
String jwtToken = null;
if(Objects.isNull(authenticate)){
// 认证没有通过
throw new RuntimeException("登录失败!");
}else{
// 认证通过
User user = (User)authenticate.getPrincipal();
// 生成jwt,保存到redis
jwtToken = JwtUtils.getJwtToken(user.getUsername());
redisTemplate.opsForValue().set("token:" + jwtToken, user, 1800, TimeUnit.SECONDS);
}
// 返回
return ResponseData.success(jwtToken);
}
}
jwt工具类
public class JwtUtils {
/**
* 两个常量: 过期时间;秘钥
*/
// 一天
public static final long EXPIRE = 1000*60*60*24;
public static final String SECRET = "wanhong123";
/**
* 生成token字符串的方法
* @param username
* @return
*/
public static String getJwtToken(String username){
String JwtToken = Jwts.builder()
//JWT头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS2256")
//设置分类;设置过期时间 一个当前时间,一个加上设置的过期时间常量
.setSubject("lin-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token主体信息,存储用户信息
// .claim("id", id)
.claim("username", username)
//.signWith(SignatureAlgorithm.ES256, SECRET)
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @Param jwtToken
*/
public static boolean checkToken(String jwtToken){
if (StringUtils.isEmpty(jwtToken)){
return false;
}
try{
//验证token
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwtToken);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @Param request
*/
public static boolean checkToken(HttpServletRequest request){
try {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return false;
}
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
* @Param request
*/
public static String getMemberIdByJwtToken(HttpServletRequest request){
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
return "";
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return (String) body.get("username");
}
}
3.5 定义jwt认证过滤器
# 1.获取token
# 2.从redis中获取用户信息
# 3.存入SecurityContextHolder
@Component
public class JwtAuthTokenFilter extends OncePerRequestFilter {
@Resource
RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
// 放行
filterChain.doFilter(request, response);
return;
}
// 校验token
boolean tokenFlag = JwtUtils.checkToken(token);
if(!tokenFlag){
throw new RuntimeException("非法的token!");
}
// 从redis中获取信息
User user = (User)redisTemplate.opsForValue().get("token:" + token);
if(Objects.isNull(user) ){
throw new RuntimeException("会话已过期,请重新登录!");
}
// 存入SecurityContextHolder
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
// 放行
filterChain.doFilter(request, response);
}
}
# SecurityConfig配置类
// 自定义的jwt过滤器
@Autowired
JwtAuthTokenFilter jwtAuthTokenFilter;
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问(放行)
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加自定义过滤器, 在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
3.6 退出登录
@PostMapping("/user/logout")
public ResponseData logout(HttpServletRequest request, HttpServletResponse response) {
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
throw new RuntimeException("非法的token!");
}
// 删除redis中的值
redisTemplate.delete("token:" + token);
// 删除SecurityContextHolder
// SecurityContextHolder.getContext().setAuthentication(null);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
return ResponseData.success();
}
四. 授权
# 例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能; 但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能;
# 总结起来就是不同的用户可以使用不同的功能
# 我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮; 因为如果只是这样,如果有人知道了对应功能的接口地址就可以不通过前端,直接去发送请求来实现相关功能操作;
# 所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作
4.1 授权基本流程
# 在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验; 在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication, 然后获取其中的权限信息; 当前用户是否拥有访问当前资源所需的权限;
# 所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication
# 然后设置我们的资源所需要的权限即可
4.2 授权实现
4.2.1 限制访问资源所需权限
SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式; 我们可以使用注解去指定访问对应的资源所需的全西安
# 开启全局权限控制配置
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
// 拥有hello权限方可访问
@PreAuthorize("hasAnyAuthority('hello')")
@RequestMapping("/hello")
public String hello() {
return "访问hello资源";
}
五. 自定义失败处理
# 我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对相应进行统一的处理; 要实现这个功能我们需要知道SpringSecurity的异常处理机制;
# 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到; 在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常;
# 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理;
# 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理;
# 所以如果我们需要自定义异常处理, 我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可
认证过程中出现的异常实现类
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 处理异常
ResponseData result = new ResponseData(false, 401, "认证失败请重新登录!", null);
String json = JSON.toJSONString(result);
WebUtils.renderString(response,json);
}
}
授权过程中出现的异常实现类
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 处理异常
ResponseData result = new ResponseData(false, 403, "权限不足!", null);
String json = JSON.toJSONString(result);
WebUtils.renderString(response,json);
}
}
``
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
AccessDeniedHandler accessDeniedHandler;
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问(放行)
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加自定义过滤器, 在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器。
http.exceptionHandling()
// 认证过程中出现的异常自定义实现类
.authenticationEntryPoint(authenticationEntryPoint)
// 授权过程中出现的异常自定义实现类
.accessDeniedHandler(accessDeniedHandler);
}
六. 跨域
# 浏览器处于安全的考虑,使用XMLHttpRequest对象发起HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的;同源策略要求相同才能正常进行通信,即协议,域名,端口号都完全一致;
# 前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题;
# 所以我们就要处理一下,让前端能进行跨域请求;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域的请求域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许跨域的请求方式
.allowedMethods("GET","POST","DELETE","PUT")
// 设置允许header属性
.allowedHeaders("*")
// 允许跨域的时间
.maxAge(3600);
}
}
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问(放行)
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加自定义过滤器, 在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器。
http.exceptionHandling()
// 认证过程中出现的异常自定义实现类
.authenticationEntryPoint(authenticationEntryPoint)
// 授权过程中出现的异常自定义实现类
.accessDeniedHandler(accessDeniedHandler);
// 允许跨域
http.cors();
}
七. 自定义权限校验方法
7.1 自定义权限校验方法
我们可以自定义自己的权限校验方法,在@PreAuthorize注解中使用我们的方法
@Component("whhhExpre")
public class WhhhExpressionRoot {
public Boolean hasAuthcrity(String authcrity) {
// 获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Object[] authcritys = user.getAuthorities().toArray();
if (authcritys.length <= 0) {
return false;
}
List<String> authcrityList = new ArrayList<>();
for (int i = 0; i < authcritys.length; i++) {
authcrityList.add(authcritys[i].toString());
}
// 判断用户权限集合中是否存在authcrity
return authcrityList.contains(authcrity);
}
}
// 指定自定义类的权限校验认证方法
@PreAuthorize("@whhhExpre.hasAuthcrity('test')")
@RequestMapping("/test")
public String test() {
return "test";
}
7.2 基于配置的权限控制
基于WebSecurityConfigurerAdapter的配置
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login").anonymous()
// 对这个接口配置权限认证
.antMatchers("/test").hasAuthority("test")
.anyRequest().authenticated();
八. CSRF
# CSRF是指跨站请求伪造(Cross-site request forgery), 是web常见的攻击方式之一;
# SpringSecurity去防止CSRF攻击的方式就是通过csrf_token; 后端会生成一个csrf_token,前端发起请求的时候需要携带这个csrf_token,后端会有过滤器进行校验,如果没有携带或者是伪造的就不允许访问;
# 我们可以发现CSRF攻击依靠的是cookie中所携带的认证信息; 但是在前后端分离的项目中我们的认证信息其实是token,而token并不是存储在cookie中,并且需要前端代码去把token设置到请求头中才可以,所以CSRF攻击也就不用担心了
九. 登录自定义处理器
9.1 登录认证成功处理器
# 实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果登录成功了是会调用AuthenticationSuccessHandler的方法进行认证成功后的处理的; AuthenticationSuccessHandler就是登录成功处理器
# 我们也可以自己去自定义成功处理器进行成功后的相应处理
登录成功处理器自定义实现类
@Component
public class WhhhSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("认证成功!");
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().successHandler(authenticationSuccessHandler);
http
// 关闭csrf
.csrf().disable()
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
9.2 登录认证失败处理器
# 实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果认证失败了是会调用AuthenticationFailureHandler的方法进行认证失败后的处理的; AuthenticationFailureHandler就是认证失败处理器
# 我们也可以自己去自定义认证失败处理器进行认证失败后的相应处理
登录失败处理器自定义实现类
@Component
public class WhhhFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("认证失败!");
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置认证成功处理器
http.formLogin().successHandler(authenticationSuccessHandler);
// 配置认证失败处理器
http.formLogin().failureHandler(authenticationFailureHandler);
http
// 关闭csrf
.csrf().disable()
.and()
.authorizeRequests()
.anyRequest().authenticated();
// 允许跨域
http.cors();
}
}
十. 登出成功处理器
登出成功处理器自定义实现类
@Component
public class WhhhLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("注销成功!");
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
// 注销成功处理器
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http
// 关闭csrf
.csrf().disable()
.and()
.authorizeRequests()
.anyRequest().authenticated();
// 允许跨域
http.cors();
}
}