1、预置演示环境

这个演示环境继续沿用
SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境。

2、自定义配置类之请求授权详解

http.authorizeRequests()主要是对url进行访问权限控制,通过这个方法来实现url授权操作。
http.authorizeRequests()也支持链式写法,举例:
http.authorizeRequests() .url匹配规则1.权限控制方法1 .url匹配规则2.权限控制方法2...
如图
在这里插入图片描述匹配顺序规则

​在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果。

越是具体的应该放在前面,越是笼统的应该放到后面。

访问控制包含访问控制url匹配访问控制方法角色判断权限判断

2.1 访问控制url匹配

2.1.1 anyRequest()

anyRequest(),表示匹配所有的url请求

配置类代码示例:

http.authorizeRequests()
        // 匹配所有的请求,并且所有请求都需要登录认证
        .anyRequest().authenticated();
2.1.2 antMatcher(String regx)
  • 1)antMatcher(String regx),传递一个ant表达式参数,表示匹配所有满足ant表达式的请求

ant表达式中特殊字符解释

规则解释说明
匹配一个字符
*匹配0个或多个字符
**匹配0个或多个目录

配置类代码示例:

http.authorizeRequests()
        // 允许登录页面匿名访问
        .antMatchers("/showLogin", "/errPage").anonymous()
        // 所有的静态资源允许匿名访问
        .antMatchers(
                "/css/**",
                "/js/**",
                "/images/**",
                "/fonts/**",
                "/favicon.ico"
                ).anonymous()
        // 其他所有的请求都需要登录认证
        .anyRequest().authenticated();
  • 2)antMatcher(HttpMethod.*, String regx),传递一个请求方法类型参数加ant表达式参数,表示匹配所有满足ant表达式的指定请求方式的url

请求方式的枚举类如下:
在这里插入图片描述
配置类代码示例:

http.authorizeRequests()
    // 允许GET请求登录页面匿名访问
    .antMatchers(HttpMethod.GET, "/showLogin", "/errPage").anonymous();
2.1.3 regexMatchers(String regexPattern)
  • 1)regexMatchers(String regexPattern) 传递一个参数
    使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant表达式,而regexMatchers()参数是正则表达式。
    演示案例: 使用正则表达式放行一个名称以demo结尾的js文件,让用户可以匿名访问
http.authorizeRequests()
    // 匹配所有名称以demo结尾的js并允许匿名访问
    .regexMatchers(".+demo[.]js").anonymous();

在这里插入图片描述

  • 2)regexMatchers(HttpMethod.*, String regexPattern) 传递两个参数

演示案例: 使用正则表达式放行一个名称以demo结尾的js文件,让用户可以通过get请求匿名访问

http.authorizeRequests()
    // 匹配所有名称以demo结尾的js并允许匿名GET请求访问
    .regexMatchers(HttpMethod.GET, ".+demo[.]js").anonymous();

2.2 访问控制方法

在2.1中我们把spring security框架中怎么匹配请求url的方法都做了介绍, 现在我们说说spring security框架中提供的访问控制的方法,看看这些方法都有什么用,演示一下

方法名称方法作用
permitAll()表示所匹配的URL任何人都允许访问
anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,只是设置为anonymous()的url会执行filterChain中的filter
denyAll()表示所匹配的URL都不允许被访问。
authenticated()表示所匹配的URL都需要被认证才能访问
rememberMe()允许通过remember-me登录的用户访问
access()SpringEl表达式结果为true时可以访问
fullyAuthenticated()用户完全认证可以访问(非remember-me下自动登录)
hasRole()如果有参数,参数表示角色,则其角色可以访问
hasAnyRole()如果有参数,参数表示角色,则其中任何一个角色可以访问
hasAuthority()如果有参数,参数表示权限,则其权限可以访问
hasAnyAuthority()如果有参数,参数表示权限,则其中任何一个权限可以访问
hasIpAddress()如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问

访问控制方法都很简单, 只要匹配到url后直接在后面追加调用这个方法就行了,链式调用特别简单,不演示了。

2.3 角色、权限判断

说说需要传递参数的几个方法hasRole(), hasAnyRole(),hasAuthority(),hasAnyAuthority()等方法的使用,演示一下
之前我们预置了两个用户,给用户也设置了权限, 我们现在用这两个用户来演示一下角色和权限的访问控制
在这里插入图片描述

2.3.1 hasRole()

演示案列: 具有admin权限的用户才可以访问role.html

添加一个role.html
在这里插入图片描述
Controller类如下:

package com.kkarma.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @GetMapping("showLogin")
    public String showLogin(){
        return "login";
    }

    @RequestMapping("home")
    public String home(){
        return "home";
    }

    @RequestMapping("errPage")
    public String errPage(){
        return "error_page";
    }

    @RequestMapping("role")
    public String role(){
        return "role";
    }
}

配置类如下

http.authorizeRequests()
   // admin角色才可以访问/role
   .antMatchers(HttpMethod.GET, "/role").hasRole("admin")

admin用户登录后可以正常访问/role
在这里插入图片描述
common用户登录后可以无法访问/role, 无权限
在这里插入图片描述

2.3.2 hasAnyRole()

配置类如下

http.authorizeRequests()
   // admin或superAdmin角色才可以访问/role
   .antMatchers(HttpMethod.GET, "/role").hasAnyRole("admin","superAdmin")
2.3.3 hasAuthority()

修改了配置类中预置用户的代码, 给用户预置了权限

    /**
     * 在内存中预置两个演示用户admin和common
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("admin", "superAdmin")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("system,system:user,system:user:list"))
                .and()
                .withUser("common")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("common")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("good,good:cate,good:cate:list"));

配置类如下

http.authorizeRequests()
    // 用户具有system:user权限时允许访问/role
    .antMatchers(HttpMethod.GET, "/role").hasAuthority("system:user")
2.3.4 hasAnyAuthority()

配置类如下

http.authorizeRequests()
    // 用户具有system:user权限时允许访问/role
    .antMatchers(HttpMethod.GET, "/role").hasAnyAuthority("system", "system:user")
2.3.5 access()

上面实现的访问控制的方法都可以使用access()来代替,因为他们本质上都是调用了access(),见下图
在这里插入图片描述

配置类如下

http.authorizeRequests()
      // 用户具有system:user权限时允许访问/role
    .antMatchers(HttpMethod.GET, "/role").access("hasAuthority('system'"))
    // 用户具有system:user或system权限时允许访问/role
    .antMatchers(HttpMethod.GET, "/role").access("hasAnyAuthority('system', 'system:user'"))

2.4 使用注解进行角色权限控制

首先如果要启动spring security提供的角色权限注解的话,需要在配置类上添加
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)注解,这样才能开启
@Secured和@PreAuthorize注解
在这里插入图片描述

2.4.1 @Secured注解的使用
package com.kkarma.web.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @GetMapping("showLogin")
    public String showLogin(){
        return "login";
    }

    @Secured("admin")
    @RequestMapping("home")
    public String home(){
        return "home";
    }

    @RequestMapping("errPage")
    public String errPage(){
        return "error_page";
    }

    @RequestMapping("role")
    public String role(){
        return "role";
    }

    @PreAuthorize("hasRole('admin')")
    @RequestMapping("index")
    public String index(){
        return "index";
    }
}

这里出现了一个bug, 控制器的方法上添加了@Secured(“ROLE_admin”)注解之后,访问仍提示403,这是为什么呢?后面再说这个问题怎么解决。
在这里插入图片描述

2.4.2 @PreAuthorize注解的使用
package com.kkarma.web.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @GetMapping("showLogin")
    public String showLogin(){
        return "login";
    }

    @RequestMapping("home")
    public String home(){
        return "home";
    }

    @RequestMapping("errPage")
    public String errPage(){
        return "error_page";
    }

    @RequestMapping("role")
    public String role(){
        return "role";
    }
    
    // 直接在类或者方法上添加@PreAuthorize()并传递权限判断方法的字符串和参数
    @PreAuthorize("hasRole('admin')")
    @RequestMapping("index")
    public String index(){
        return "index";
    }
}
2.4.3 @postAuthorize注解的使用

这个注解的主要作用就是将权限判断后置, 什么意思呢, @Secured和@PreAuthorize这两个注解都是对于request来进行判断, 而@postAuthorize这个注解是基于response的响应来进行授权操作的。
比如用户发来一个请求,服务器响应了一个数据, 我就针对这个响应数据跟我的预期结果是不是相符, 如果相符, 说明有权限访问, 如果不相符,说明权限不足,这里简单演示一下
演示代码很简单,一个字符串数据包含inout两个元素,随机获取一个元素,当取出的元素是in时允许用户访问,其他则无权限

package com.kkarma.web.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Random;


@Controller
public class LoginController {

    @GetMapping("showLogin")
    public String showLogin(){
        return "login";
    }

//    @Secured("admin")
    @RequestMapping("home")
    public String home(){
        return "home";
    }

    @RequestMapping("errPage")
    public String errPage(){
        return "error_page";
    }

    @RequestMapping("role")
    public String role(){
        return "role";
    }

    @PreAuthorize("hasRole('admin')")
    @RequestMapping("index")
    public String index(){
        return "index";
    }

    @PostAuthorize("returnObject=='in'")
    @RequestMapping("getStr")
    @ResponseBody
    public String getString() {
        String[] arr = {"in", "out"};
        return arr[new Random().nextInt(2)];
    }
}

响应结果获取到"in",匹配,说明you权限访问
在这里插入图片描述
响应结果获取到"out",不匹配,说明权限不足, 抛出403异常
在这里插入图片描述

2.5 自定义403异常返回处理

首先, 自定义403异常处理器

package com.kkarma.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setHeader("Content-Type","application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("{\"code\":\"403\",\"msg\":\"权限不足,无法访问!\"}");
        out.flush();
        out.close();
    }
}

其次, 在spring security自定义配置类中注入该处理器对象,并通过http设置
在这里插入图片描述

package com.kkarma.config;

import com.kkarma.handler.CustomAccessDeniedHandler;
import com.kkarma.web.service.impl.MyUserDetailsServiceImpl;
import org.apache.coyote.Adapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.WebSecurityConfigurer;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /**
         * 配置csrf开启关闭,默认是开启的
         */
         // http.csrf().disable();

        /**
         * 表单登录相关的配置
         */
        http.formLogin()
                // 前端登录表单用户名别名, 从参数user中获取username参数取值
                .usernameParameter("user")
                // 前端登录表单密码别名, 从参数passwd中获取password参数取值
                .passwordParameter("passwd")
                // 当http请求的url是/login时,进行我们自定义的登录逻辑
                .loginProcessingUrl("/login")
                // 自定义登录的前端控制器
                .loginPage("/showLogin")
                // 设置登录成功的跳转链接
                // .successForwardUrl("/home");
                // 通过successHandler处理器进行登录成功之后的逻辑处理
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        System.out.println("登录成功,页面即将跳转...");
                        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                        authorities.forEach(System.out::println);
                        response.sendRedirect("/home");
                    }
                })
                // 设置登录失败的跳转链接
                // .failureForwardUrl("/errPage");
                // 通过failureHandler处理器进行登录失败之后的逻辑处理
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        e.printStackTrace();
                        System.out.println("登录失败,页面即将跳转到默认失败页...");
                        response.sendRedirect("/errPage");
                    }
                })
                .and()
                // 403权限不足异常使用自定义403处理器处理
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);

        /**
         * http请求是否要登录认证配置
         */
        http.authorizeRequests()
                // 允许GET请求登录页面匿名访问
                .antMatchers(HttpMethod.GET, "/showLogin", "/errPage").anonymous()
                // 匹配所有名称以demo结尾的js并允许匿名访问
                .regexMatchers(HttpMethod.GET, ".+demo[.]js").anonymous()
                // 用户具有admin角色时允许访问/role
                .antMatchers(HttpMethod.GET, "/role").hasRole("admin")
                // 用户具有system:user权限时允许访问/role
                .antMatchers(HttpMethod.GET, "/role").hasAuthority("system:user")
                // 所有的静态资源允许匿名访问
                .antMatchers(
                        "/css/**",
                        "/js/**",
                        "/images/**",
                        "/fonts/**",
                        "/favicon.ico"
                        ).anonymous()
                // 其他所有的请求都需要登录认证
                .anyRequest().authenticated();
    }



    /**
     * 在内存中预置两个演示用户admin和common
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("admin", "superAdmin")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("system,sytem:user,system:user:list"))
                .and()
                .withUser("common")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("common")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("good,good:cate,good:cate:list"));
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
}

重启项目, 再次访问/getStr, 当无权限访问时就是使用自定义的403进行处理响应。
在这里插入图片描述

2.6 用户退出

通过spring security框架我们可以自己自定义用户退出逻辑
在这里插入图片描述
具体的配置类信息

package com.kkarma.config;

import com.kkarma.handler.CustomAccessDeniedHandler;
import com.kkarma.web.service.impl.MyUserDetailsServiceImpl;
import org.apache.coyote.Adapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.WebSecurityConfigurer;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /**
         * 配置csrf开启关闭,默认是开启的
         */
         http.csrf().disable();

        /**
         * 表单登录相关的配置
         */
        http.formLogin()
                // 前端登录表单用户名别名, 从参数user中获取username参数取值
                .usernameParameter("user")
                // 前端登录表单密码别名, 从参数passwd中获取password参数取值
                .passwordParameter("passwd")
                // 当http请求的url是/login时,进行我们自定义的登录逻辑
                .loginProcessingUrl("/login")
                // 自定义登录的前端控制器
                .loginPage("/showLogin")
                // 设置登录成功的跳转链接
                // .successForwardUrl("/home");
                // 通过successHandler处理器进行登录成功之后的逻辑处理
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        System.out.println("登录成功,页面即将跳转...");
                        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                        authorities.forEach(System.out::println);
                        response.sendRedirect("/home");
                    }
                })
                // 设置登录失败的跳转链接
                // .failureForwardUrl("/errPage");
                // 通过failureHandler处理器进行登录失败之后的逻辑处理
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        e.printStackTrace();
                        System.out.println("登录失败,页面即将跳转到默认失败页...");
                        response.sendRedirect("/errPage");
                    }
                })
                .and()
                // 403权限不足异常使用自定义403处理器处理
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);

        // 用户退出登录处理配置
        http.logout().
                // 设置用户退出的url
                logoutUrl("/logout")
                // 用户退出成功之后的跳转链接
                .logoutSuccessUrl("/showLogin")
                // 自定义用户的退出逻辑处理器, 可以处理认证信息、session、cookies等信息
                .addLogoutHandler(new LogoutHandler() {
                    @Override
                    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
                        // 这里可以自定义用户退出的处理逻辑
                        System.out.println("退出成功...");
                    }
                })
                // 是否清除认证状态,默认为true
                .clearAuthentication(true)
                // 是否销毁HttpSession对象,默认为true
                .invalidateHttpSession(true);


        /**
         * http请求是否要登录认证配置
         */
        http.authorizeRequests()
                // 允许GET请求登录页面匿名访问
                .antMatchers(HttpMethod.GET, "/showLogin", "/errPage").anonymous()
                // 匹配所有名称以demo结尾的js并允许匿名访问
                .regexMatchers(HttpMethod.GET, ".+demo[.]js").anonymous()
                // 用户具有admin角色时允许访问/role
                .antMatchers(HttpMethod.GET, "/role").hasRole("admin")
                // 用户具有system:user权限时允许访问/role
                .antMatchers(HttpMethod.GET, "/role").hasAuthority("system:user")
                // 所有的静态资源允许匿名访问
                .antMatchers(
                        "/css/**",
                        "/js/**",
                        "/images/**",
                        "/fonts/**",
                        "/favicon.ico"
                        ).anonymous()
                // 其他所有的请求都需要登录认证
                .anyRequest().authenticated();
    }



    /**
     * 在内存中预置两个演示用户admin和common
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("admin", "superAdmin")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("system,sytem:user,system:user:list"))
                .and()
                .withUser("common")
                .password(bCryptPasswordEncoder().encode("123456"))
                .roles("common")
                .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("good,good:cate,good:cate:list"));
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
}

在首页home.html添加一个退出链接, 演示一下效果
在这里插入图片描述
重启项目, 测试一下
登录成功
在这里插入图片描述
点击用户退出,成功跳转到登录页面
在这里插入图片描述
这中间可能会出现退出登录抛出异常404,这是因为默认开启了csrf, 在配置类中关闭csrf重启即可解决
在这里插入图片描述

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐