• SpringSecurity - 定制登录和注销登录


    1. SecurityConfig 配置登录表单

    定义一个登录页面:login.html

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面title>
    head>
    <body>
    <h2>自定义登录页面h2>
    <form action="/user/login" method="post">
        <table>
            <tr>
                <td>用户名:td>
                <td><input type="text" name="username">td>
            tr>
            <tr>
                <td>密码:td>
                <td><input type="password" name="password">td>
            tr>
            <tr>
                <td colspan="2"><button type="submit">登录button>td>
            tr>
        table>
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    定义2个测试接口,作为受保护的资源,当用户登录成功后,就可以访问受保护的资源 :

    @RestController
    public class UserResource {
    
        @RequestMapping("/hello")
        public String test(){
            return "hello";
        }
    
        @RequestMapping("/index")
        public String test1(){
            return "index";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    提供 SpringSecurity 的配置类 :

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问 
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                	// 开启表单登录配置
                	// loginProcessingUrl ,usernameParameter ,passwordParameter 
                	// 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                		// 配置登录页面地址
                        .loginPage("/login.html")
                		// 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                		// 配置登录成功的跳转地址
                        .defaultSuccessUrl("/index")
                		// 配置登录失败的跳转地址
                        .failureUrl("/login.html")
                		// 登录用户名的参数名称
                        .usernameParameter("username")
                		// 登录密码的参数名称
                        .passwordParameter("password")
                		// 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                	// 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                	// 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    配置完成后,启动 SpringBoot 项目,浏览器地址栏中输入 http://localhost:8080/index,会自动跳转到http://localhost:8080/login.html页面:

    在这里插入图片描述

    输入用户名和密码进行登录,登录成功后,会自动跳转到登录前访问的 http://localhost:8080/index 页面,经过上面的配置,已经成功的自定义了一个登录页面出来,用户在登录成功之后,就可以访问受保护的资源了。

    2. 登录成功后的处理:AuthenticationSuccessHandler

    defaultSuccessUrl 表示用户登录成功之后,会自动重定向到登录之前的地址上,如果用户本身就是直接访问的登录页面,则登录成功后就会重定向到defaultSuccessUrl 指定的页面中。例如,用户在未认证的情况下,访问了/hello 页面;而用户如果一开始就访问登录页面,则登录成功后会自动重定向到defaultSuccessUrl 所指定的页面中。

    第一种情况:用户登录成功之后,会自动重定向到登录之前的地址上

    浏览器地址栏中输入 http://localhost:8080/hello,会自动跳转到 http://localhost:8080/login.html 页面:

    在这里插入图片描述
    输入用户名和密码 登录成功后,会自动跳转到登录之前访问的 http://localhost:8080/hello,而不是defaultSuccessUrl 所指定的 http://localhost:8080/index 页面。

    第二种情况:一开始就访问登录页面,则登录成功后会自动重定向到defaultSuccessUrl 所指定的页面中

    浏览器直接访问 http://localhost:8080/login.html 页面 :
    在这里插入图片描述
    输入用户名和密码登录成功后,自动跳转到了defaultSuccessUrl 所指定的 http://localhost:8080/index 页面;

    那么如果想登录前不访问登录页面,而登录后又想跳转到指定的页面呢 ?

    当用户登录成功后,除了defaultSuccessUrl() 方法可试下登录成功后的跳转之外,successForwardUrl() 方法也可以实现登录成功后的跳转:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login.html")
                        .loginProcessingUrl("/user/login")
                        .successForwardUrl("/index")
                        .failureUrl("/login.html")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    successForwardUrl 不会来了用户之前的访问地址,只要用户登录陈宫,就会通过服务器跳转到 successForwardUrl 所指定的页面。

    defaultSuccessUrl 有一个重载的方法,如果重载方法的第2个参数传true ,则defaultSuccessUrl 的效果与successForwardUrl 类似,即不用考虑用户之前的访问地址,只要登录成功,就重定向到defaultSuccessUrl 所指定的页面。不同之处在于defaultSuccessUrl 是通过重定向实现的跳转,而 successForwardUrl 是通过服务器端跳转实现的。

    无论是 defaultSuccessUrl 还是 successForwardUrl 最终配置的都是 AuthenticationSuccessHandler 接口的实例。SpringSecurity 中专门提供了AuthenticationSuccessHandler 接口用来处理登录成功的事项:

    public interface AuthenticationSuccessHandler {
       
        default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
            this.onAuthenticationSuccess(request, response, authentication);
            chain.doFilter(request, response);
        }
    
        // 处理登录成功的具体事项,其中authentication参数保存了登录成功的用户信息
        void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    AuthenticationSuccessHandler 接口有三个实现类:

    (1) SimpleUrlAuthenticationSuccessHandler 通过handler() 方法实现请求的重定向;

    (2) ForwardAuthenticationSuccessHandler 实现服务端的跳转;

    (3) SavedRequestAwareAuthenticationSuccessHandler 在 SimpleUrlAuthenticationSuccessHandler 的基础上增加了请求缓存的功能,可以记录之前请求的地址,进而登录成功后重定向到一开始访问的地址;

    SavedRequestAwareAuthenticationSuccessHandler 在 SimpleUrlAuthenticationSuccessHandler 的基础上增加了请求缓存的功能,可以记录之前请求的地址,进而登录成功后重定向到一开始访问的地址;

    AuthenticationSuccessHandler 默认的三个实现类,无论哪一个都是用来出来页面跳转的。但是在前后端分离的开发中,用户登录成功后,不再需要页面跳转了,只需要给前端返回一个 JSON 数据即可,告诉前端登录成功还是失败,前端收到消息后自行处理,像这样的请求我们可以通过自定义AuthenticationSuccessHandler 的实现类来完成:

    public class MyAthenticationSuccessHandler implements AuthenticationSuccessHandler {
        // 重写onAuthenticationSuccess()方法
        @Override
        public void onAuthenticationSuccess(
                HttpServletRequest request,
                HttpServletResponse response,
                Authentication authentication) throws IOException, ServletException {
            
            response.setContentType("application/json;charset=utf-8");
            Map<String, String> respMap = Map.of(
                    "code", "200",
                    "message", "登录成功"
            );
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(respMap);
            // 返回一段登录成功的Json字符串给前端
            response.getWriter().write(json);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在 SecurityConfig 中配置自定义的 MyAthenticationSuccessHandler

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(new MyAthenticationSuccessHandler())
                        .failureUrl("/login.html")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    重启项目,访问: http://localhost:8080/index
    在这里插入图片描述

    输入用户名和 密码,登录成功后,返回一段字符串:
    在这里插入图片描述
    上面 MyAthenticationSuccessHandler 可以简写在 SecurityConfig 类中:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(jsonAthenticationSuccessHandler())
                        .failureHandler("/login.html"
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    3. 登录失败后的处理:AuthenticationFailureHandler

    为了在前端页面展示登录失败的异常信息,首先在项目的pom.xml文件中引入thymeleaf依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    然后再resources/template目录下创建 mylogin.html 文件:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面title>
    head>
    <body>
    <h2>自定义登录页面h2>
    <form action="/user/login" method="post">
        <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}">div>
        <table>
            <tr>
                <td>用户名:td>
                <td><input type="text" name="username">td>
            tr>
            <tr>
                <td>密码:td>
                <td><input type="password" name="password">td>
            tr>
            <tr>
                <td colspan="2"><button type="submit">登录button>td>
            tr>
        table>
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    mylogin.html 和前面的 login.html 基本类似,前面的login.html是静态页面,这里是模板页面,mylogin.html 页面中加了一个div,用来展示登录失败的异常信息,登录失败的异常信息会放在request中返回给前端。

    动态页面不能直接访问,需要一个访问控制器:

    @Controller
    public class LoginController {
        @RequestMapping("/mylogin.html")
        public String login(){
            return "mylogin";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在SecurityConfig中配置登录页面和登录失败时的跳转页面:

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/mylogin.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(new MyAthenticationSuccessHandler())
                        .failureUrl("/mylogin.html")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    failureUrl 表示登录失败后重定向到 login.html 页面。重定向是一种客户端跳转,重定向不方便携带请求失败的异常信息(只能放在URL中)。如果希望能够在前端页面展示请求失败的异常信息,可以使用下面这种方式:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(new MyAthenticationSuccessHandler())
                        .failureForwardUrl("/login.html")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    failureForwardUrl 从名字上就可以看出,这种跳转是一种服务器跳转,服务器跳转的好处是可以携带登录异常信息。如果登录失败,自动跳转到登录页面后,就可以将错误信息展示出来,如图:输入错误的用户名或密码

    在这里插入图片描述
    无论是 failureUrl 还是 failureForwardUrl ,最终所配置的都是 AuthenticationFailureHandler 接口的实现:

    public interface AuthenticationFailureHandler {
        void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException;
    }
    
    • 1
    • 2
    • 3

    AuthenticationFailureHandler 接口只有一个方法,用来处理登录失败的请求,exception表示登录失败的异常信息。

    AuthenticationFailureHandler 接口共有5个实现类:

    (1) SimpleUrlAuthenticationFailureHandler 默认的处理逻辑就是通过充电功能项跳转到登录页面,当然也可以通过配置 forwardToDestination 属性将重定向改为服务器跳转,failureUrl 方法的底层实现逻辑就是SimpleUrlAuthenticationFailureHandler ;

    (2) ExceptionMappingAuthenticationFailureHandler 可以实现根据不同的异常类型,映射不同的路径;

    (3) ForwardAuthenticationFailureHandler 表示通过服务器端跳转来重新回到登录页面,failureForwardUrl 方法的底层实现逻辑就是 ForwardAuthenticationFailureHandler ;

    (4) AuthenticationEntryPointFailureHandler 可以通过AuthenticationEntryPoint来处理登录异常;

    (5) DelegatingAuthenticationFailureHandler 可以实现为不同的异常类型配置不同的登录失败处理回调;

    看一下 SimpleUrlAuthenticationFailureHandler 类的源码:

    public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
        protected final Log logger = LogFactory.getLog(this.getClass());
        private String defaultFailureUrl;
        private boolean forwardToDestination = false;
        private boolean allowSessionCreation = true;
        private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    
        public SimpleUrlAuthenticationFailureHandler() {
        }
    
        // 构造对象时传入登录失败时要跳转的地址 defaultFailureUrl
        public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
            this.setDefaultFailureUrl(defaultFailureUrl);
        }
    
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            // 如果登录失败时要跳转的地址defaultFailureUrl==null,直接通过response返回异常信息
            if (this.defaultFailureUrl == null) {
                this.logger.debug("No failure URL set, sending 401 Unauthorized error");
                response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
            } else {
                // 调用 saveException() 方法
                this.saveException(request, exception);
                // 如果 forwardToDestination 为true,就通过服务器端跳转到登录页面
                if (this.forwardToDestination) {
                    this.logger.debug("Forwarding to " + this.defaultFailureUrl);
                    request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
                } else {
                    // 否则,重定向回到登录页面
                    this.logger.debug("Redirecting to " + this.defaultFailureUrl);
                    this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
                }
            }
        }
    
        protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
            // 如果forwardToDestination 属性为true,表示通过服务器端跳转回到登录页面
            // 把异常信息放在request中
            if (this.forwardToDestination) {
                request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
            } else {
                HttpSession session = request.getSession(false);
                if (session != null || this.allowSessionCreation) {
                    request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
                }
            }
        }
        
        public void setUseForward(boolean forwardToDestination) {
            this.forwardToDestination = forwardToDestination;
        }
        // ....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    因此,假如不使用 failureForwardUrl 方法,同时又想在登录失败后通过服务端跳转到登录页面,那么可以自定义 SimpleUrlAuthenticationFailureHandler 配置,并将 forwardToDestination 属性置为 true:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/mylogin.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(jsonAthenticationSuccessHandler())
                        .failureHandler(failureHandler())
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private SimpleUrlAuthenticationFailureHandler failureHandler() {
            SimpleUrlAuthenticationFailureHandler handler
                    = new SimpleUrlAuthenticationFailureHandler("/mylogin.html");
            // 将 forwardToDestination 属性置为 true
            handler.setUseForward(true);
            return handler;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    这样配置之后,如果用户再次登录失败,就会通过服务端跳转重新回到登录页面,登录页面也会展示相应的错误信息,效果和 failureForwardUrl 一样。

    如果是前后端分离开发,登录失败就不需要页面跳转了,只需要返回Json字符串给前端即可,此时可以通过自定义AuthenticationFailureHandler 的实现类来完成:

    public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(
                HttpServletRequest request, 
                HttpServletResponse response, 
                AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            Map<String, String> respMap = Map.of(
                    "code", "500",
                    "message", "登录失败" + exception.getMessage()
            );
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(respMap);
            response.getWriter().write(json);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在 SecurityConfig 中进行配置:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/mylogin.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(new MyAthenticationSuccessHandler())
                        .failureHandler(new MyAuthenticationFailureHandler())
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    配置完成后,当用户再次登录失败,就不会进行页面跳转了,而是直接返回 json 字符串:
    在这里插入图片描述
    上面的 MyAuthenticationFailureHandler 简写在SecurityConfig中:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/mylogin.html")
                        .loginProcessingUrl("/user/login")
                        .successHandler(jsonAthenticationSuccessHandler())
                        .failureHandler(jsonAuthenticationFailureHandler())
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .and()
                    .csrf().disable();
        }
    
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    4. 注销登录

    SpringSecurity中提供了默认的注销登录页面,我们也可以根据自己的需求对注销登录进行定制。

    在 SecurityConfig 中配置注销登录:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问 
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                    // 开启表单登录配置
                    // loginProcessingUrl ,usernameParameter ,passwordParameter 
                    // 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                        // 配置登录页面地址
                        .loginPage("/mylogin.html")
                        // 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                        // 配置登录成功的跳转地址
                        .successHandler(jsonAthenticationSuccessHandler())
                        // 配置登录失败的跳转地址
                        .failureHandler(jsonAuthenticationFailureHandler())
                        // 登录用户名的参数名称
                        .usernameParameter("username")
                        // 登录密码的参数名称
                        .passwordParameter("password")
                        // 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                    // 开启注销登录配置
                    .logout()
                        // 指定注销登录请求地址,默认是GET请求,路径为/logout
                        .logoutUrl("/logout")
                        // 表示是否使session失效,默认为true
                        .invalidateHttpSession(true)    
                        // 表示是否清除认证信息,默认为true
                        .clearAuthentication(true)
                        // 表示注销登录后的跳转地址
                        .logoutSuccessUrl("/mylogin.html")
                    .and()
                    // 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                	// 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .csrf().disable();
        }
    
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    启动项目访问 http://localhost:8080/logout,自动跳转到登录页面:
    在这里插入图片描述
    登录成功后在浏览器输入 http://localhost:8080/logout 就可以发起注销登录请求了,注销登录成功后,会自动跳转到 mylogin.html 页面。

    如果项目有需要还可以配置多个注销登录的请求,同时还可以指定请求的方法:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                    // 开启表单登录配置
                    // loginProcessingUrl ,usernameParameter ,passwordParameter
                    // 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                        // 配置登录页面地址
                        .loginPage("/mylogin.html")
                        // 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                        // 配置登录成功的跳转地址
                        .successHandler(jsonAthenticationSuccessHandler())
                        // 配置登录失败的跳转地址
                        .failureHandler(jsonAuthenticationFailureHandler())
                        // 登录用户名的参数名称
                        .usernameParameter("username")
                        // 登录密码的参数名称
                        .passwordParameter("password")
                        // 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                    // 开启注销登录配置
                    .logout()
                        // 指定注销登录请求地址和请求方式
                        // 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
                        .logoutRequestMatcher(new OrRequestMatcher(
                                new AntPathRequestMatcher("/logout","GET"),
                                new AntPathRequestMatcher("/logout2","POST")
                                )
                        )
                        // 表示是否使session失效,默认为true
                        .invalidateHttpSession(true)
                        // 表示是否清除认证信息,默认为true
                        .clearAuthentication(true)
                        // 表示注销登录后的跳转地址
                        .logoutSuccessUrl("/mylogin.html")
                    // 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                    // 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .and()
                    .csrf().disable();
        }
    
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    5. 注销登录成功后的处理:LogoutSuccessHandler

    如果项目是前后端分离的项目,注销成功后就不需要页面跳转了,只需要将注销成功的信息返回给前端即可,此时我们可以自定义返回内容,实现成功退出登录的接口LogoutSuccessHandler,自定义一个注销登录类

    public interface LogoutSuccessHandler {
        void onLogouauthenticationtSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
    }
    
    • 1
    • 2
    • 3
    public class MyLogoutSuccessfulHandler implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(
                HttpServletRequest request, 
                HttpServletResponse response, 
                Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            Map<String, String> respMap = Map.of(
                    "code", "200",
                    "message", "注销登录成功"
            );
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(respMap);
            response.getWriter().write(json);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在 SecurityConfig 中配置:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                    // 开启表单登录配置
                    // loginProcessingUrl ,usernameParameter ,passwordParameter
                    // 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                        // 配置登录页面地址
                        .loginPage("/mylogin.html")
                        // 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                        // 配置登录成功的跳转地址
                        .successHandler(jsonAthenticationSuccessHandler())
                        // 配置登录失败的跳转地址
                        .failureHandler(jsonAuthenticationFailureHandler())
                        // 登录用户名的参数名称
                        .usernameParameter("username")
                        // 登录密码的参数名称
                        .passwordParameter("password")
                        // 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                    // 开启注销登录配置
                    .logout()
                        // 指定注销登录请求地址和请求方式
                        .logoutSuccessHandler(new MyLogoutSuccessfulHandler())
                        // 表示是否使session失效,默认为true
                        .invalidateHttpSession(true)
                        // 表示是否清除认证信息,默认为true
                        .clearAuthentication(true)
                        // 表示注销登录后的跳转地址
                        .logoutSuccessUrl("/mylogin.html")
                    // 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                    // 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .and()
                    .csrf().disable();
        }
    
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    启动项目,登录成功后,注销登录访问 localhost:8080/logout,会将注销登录的成功信息返回给前端:
    在这里插入图片描述
    将上面的配置方式简写为:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                    // 开启表单登录配置
                    // loginProcessingUrl ,usernameParameter ,passwordParameter
                    // 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                        // 配置登录页面地址
                        .loginPage("/mylogin.html")
                        // 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                        // 配置登录成功的跳转地址
                        .successHandler(jsonAthenticationSuccessHandler())
                        // 配置登录失败的跳转地址
                        .failureHandler(jsonAuthenticationFailureHandler())
                        // 登录用户名的参数名称
                        .usernameParameter("username")
                        // 登录密码的参数名称
                        .passwordParameter("password")
                        // 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                    // 开启注销登录配置
                    .logout()
                        // 指定注销登录请求地址和请求方式
                        // 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
                        .logoutRequestMatcher(new OrRequestMatcher(
                            new AntPathRequestMatcher("/logout","GET"),
                            new AntPathRequestMatcher("/logout2","POST")
                            )
                        )
                        // 表示是否使session失效,默认为true
                        .invalidateHttpSession(true)
                        // 表示是否清除认证信息,默认为true
                        .clearAuthentication(true)
                        // 不再页面跳转,将注销成功的信息返回给前端
                        .logoutSuccessHandler(jsonLogoutSuccessfulHandler())
                    // 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                    // 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .and()
                    .csrf().disable();
        }
    
        private LogoutSuccessHandler jsonLogoutSuccessfulHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "注销登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    如果希望为不同的注销地址返回不同的结果,也是可以的,配置如下:

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // authorizeRequests() 方法表示开启权限配置
            http.authorizeRequests()
                    // 表示所有的请求都要认证后才能访问
                    .anyRequest().authenticated()
                    // and()方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。
                    .and()
                    // 开启表单登录配置
                    // loginProcessingUrl ,usernameParameter ,passwordParameter
                    // 和login.html中登录表单的配置一致,即action,用户名name属性值,密码name属性值;
                    .formLogin()
                        // 配置登录页面地址
                        .loginPage("/mylogin.html")
                        // 配置登录接口地址
                        .loginProcessingUrl("/user/login")
                        // 配置登录成功的跳转地址
                        .successHandler(jsonAthenticationSuccessHandler())
                        // 配置登录失败的跳转地址
                        .failureHandler(jsonAuthenticationFailureHandler())
                        // 登录用户名的参数名称
                        .usernameParameter("username")
                        // 登录密码的参数名称
                        .passwordParameter("password")
                        // 跟登录相关的页面和接口不做拦截,直接通过
                        .permitAll()
                    .and()
                    // 开启注销登录配置
                    .logout()
                        // 指定注销登录请求地址和请求方式
                        // 配置两个注销登录的请求,使用任意一个请求都可以完成登录注销
                        .logoutRequestMatcher(new OrRequestMatcher(
                            new AntPathRequestMatcher("/logout","GET"),
                            new AntPathRequestMatcher("/logout2","POST")
                            )
                        )
                        // 表示是否使session失效,默认为true
                        .invalidateHttpSession(true)
                        // 表示是否清除认证信息,默认为true
                        .clearAuthentication(true)
                        // 不再页面跳转,将注销成功的信息返回给前端
                        .defaultLogoutSuccessHandlerFor(
                                (request, response, authentication) -> {
                                    response.setContentType("application/json;charset=utf-8");
                                    Map<String, String> respMap = Map.of(
                                            "code", "200",
                                            "message", "logout 注销登录成功"
                                    );
                                    ObjectMapper objectMapper = new ObjectMapper();
                                    String json = objectMapper.writeValueAsString(respMap);
                                    response.getWriter().write(json);
                                },new AntPathRequestMatcher("/logout","GET")
                        )
                        .defaultLogoutSuccessHandlerFor(
                                (request, response, authentication) -> {
                                    response.setContentType("application/json;charset=utf-8");
                                    Map<String, String> respMap = Map.of(
                                            "code", "200",
                                            "message", "logout2 注销登录成功"
                                    );
                                    ObjectMapper objectMapper = new ObjectMapper();
                                    String json = objectMapper.writeValueAsString(respMap);
                                    response.getWriter().write(json);
                                },new AntPathRequestMatcher("/logout2","POST")
                        )
                    // 禁用CSRF防御功能功能,SpringSecurity 自带了 CSRF 防御机制,
                    // 但是我们这里为了测试方便,先将CSRF防御机制关闭
                    .and()
                    .csrf().disable();
        }
        
        private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
            return (request, response, exception) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "500",
                        "message", "登录失败" + exception.getMessage()
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    
        private AuthenticationSuccessHandler jsonAthenticationSuccessHandler() {
            return (request, response, authentication) -> {
                response.setContentType("application/json;charset=utf-8");
                Map<String, String> respMap = Map.of(
                        "code", "200",
                        "message", "登录成功"
                );
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(respMap);
                response.getWriter().write(json);
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    defaultLogoutSuccessHandlerFor 方法可以注册过个不同的注销成功回调函数,该方法第一个参数时注销成功回调,第二个承诺书是具体的注销请求。当用户注销成功后,使用了哪个注销请求,就给出对应的响应信息。

  • 相关阅读:
    爆刷leetcode——二叉树(二)——路径问题(一)
    24. Kernel 4.19环境下,Cilium网络仍然需要使用iptables
    Nacos
    docker入门加实战—Docker镜像和Dockerfile语法
    spider-node-初识
    virtualbox配置_20211127
    “2024杭州智慧城市及安防展会”将于4月在杭州博览中心盛大召开
    概率论与数理统计_第1章_几何概型
    开源欧拉 openEuler 23.09 创新版本发布
    C语言 & 图形化界面方式连接MySQL【C/C++】【图形化界面组件分享】
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/126449650