Spring Security 6.x 系列(12)—— Form表单认证登录注销自定义配置

news2024/10/3 2:26:02

一、前言

在本系列文章中介绍了 Form 表单认证和注销流程,对部分源码也进行详细分析。

本章主要学习 Spring Security 中表单认证登录注销的相关自定义配置。

二、自定义登录页面

Spring Security 表单认证默认规则中对未认证的请求会重定向到默认登录页面,也支持自定义设置登录页面。

2.1 整合 Thymeleaf 模版引擎

2.1.1 pom 依赖

完整 pom.xml

<?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>

    <parent>
        <artifactId>spring-security</artifactId>
        <groupId>com.gm</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>form-security-custom</artifactId>
    <packaging>jar</packaging>
    <description>form表单登录示例(自定义)</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.1.2 Thymeleaf 配置

src/main/resources/application.yaml

server:
  port: 9000

logging:
  level:
    org.springframework.security: TRACE

spring:
  thymeleaf:
    #配置模板路径,默认是templates
    prefix: classpath:/templates/
    #文件后缀
    suffix: .html
    #编码
    encoding: UTF-8
    #内容类别
    content-type: text/html
    #模板的模式,支持 HTML, XML TEXT JAVASCRIPT
    mode: HTML
    #开发时建议设置为false,否则会有缓存,导致页面没法及时看到更新后的效果。
    cache: false

2.2 创建登录页

src/main/resources/templates 目录下创建一个简单的登录页 login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
    <!-- 引入 Bootstrap 样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f8f9fa;
        }
        .login-container {
            max-width: 400px;
            margin: 0 auto;
            margin-top: 100px;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            background-color: #fff;
        }
        .custom-alert {
            background-color: #FFD2D2;
            padding: 10px;
            border-radius: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            min-height: 50px; /* 设置最小高度 */
        }
    </style>
</head>
<body>
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-6 login-container">
            <h2 class="text-center mb-4">Login</h2>
            <form th:action="@{/login}" method="post">
                <div th:if="${exception}" class="custom-alert"><p th:text="${exception}"></p></div>
                <div th:if="${logout}" class="custom-alert"><p th:text="${logout}"></p></div>
                <div class="mb-3">
                    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
                    <label for="username" class="form-label">用户名</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <div class="mb-3">
                    <label for="password" class="form-label">密码</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <button type="submit" class="btn btn-primary w-100">Login</button>
            </form>
        </div>
    </div>
</div>

<!-- 引入 Bootstrap JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
  • <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> 是处理开启 CSRF 保护所需参数
  • <div th:if="${exception}" class="custom-alert"><p th:text="${exception}"></p></div> 是显示认证相关异常,稍后详细介绍
  • <div th:if="${logout}" class="custom-alert"><p th:text="${logout}"></p></div> 是显示注册相关信息,稍后详细介绍

2.3 创建 Controller 访问控制

新建 LoginController.java

@Controller
@Slf4j
class LoginController {

    /**
     * 登录页面
     *
     * @return
     */
    @GetMapping("/login")
    String login() {
        return "login";
    }
}

2.4 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    /**
     * 构建SecurityFilterChain
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置所有http请求必须经过认证
        http.authorizeHttpRequests(authorizeRequests ->
                authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll()
                        .anyRequest().authenticated());

        // 开启表单认证(默认配置)
        // http.formLogin(Customizer.withDefaults());

        // 表单认证自定义配置
        http.formLogin(form ->
                form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址
                        .loginProcessingUrl("/login") // form表单登录处理请求URL(POST)
                        .usernameParameter("username") // form表单用户名参数名称
                        .passwordParameter("password") // form表单密码参数名称
        );
        // 开启 CSRF 保护
        http.csrf(Customizer.withDefaults());
        // 禁止 CSRF 保护
        // http.csrf(csrf -> csrf.disable());
        // 构造器构建SecurityFilterChain对象
        return http.build();
    }

    /**
     * 配置登录名密码
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();
        return new InMemoryUserDetailsManager(new UserDetails[]{user});
    }
}
  • loginPage("/login").permitAll():配置 URL 生成登录页面,常与 Controller 配合使用,必须是 POST
  • loginProcessingUrl("/login"):配置登录请求处理 URL ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求 URL 地址。
  • usernameParameter("username"):配置用户名参数名称 ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求用户名对应的参数名称。
  • passwordParameter("password"):配置密码参数名称 ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求密码对应的参数名称。

2.5 效果

在这里插入图片描述

三、自定义登录成功请求转发/重定向地址

3.1 创建登录成功页

src/main/resources/templates 目录下创建一个简单的登录成功页面 success.html

3.2 创建 Controller 访问控制

LoginController.java 新增:

/**
 * 登录成功页面
 *
 * @return
 */
@RequestMapping("/login/success")
String success() {
    return "success";
}

3.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    /**
     * 构建SecurityFilterChain
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置所有http请求必须经过认证
        http.authorizeHttpRequests(authorizeRequests ->
                authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll()
                        .anyRequest().authenticated());

        // 开启表单认证(默认配置)
        // http.formLogin(Customizer.withDefaults());

        // 表单认证自定义配置
        http.formLogin(form ->
                form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址
                        .loginProcessingUrl("/login") // form表单登录处理请求URL(POST)
                        .usernameParameter("username") // form表单用户名参数名称
                        .passwordParameter("password") // form表单密码参数名称
                        //.successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变)
                        .defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变)
        );
        // 开启 CSRF 保护
        http.csrf(Customizer.withDefaults());
        // 禁止 CSRF 保护
        // http.csrf(csrf -> csrf.disable());
        // 构造器构建SecurityFilterChain对象
        return http.build();
    }

    /**
     * 配置登录名密码
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();
        return new InMemoryUserDetailsManager(new UserDetails[]{user});
    }
}
  • successForwardUrl("/login/success"):配置登录成功请求转发 URL (请求转发地址栏不变),常与 Controller 配合使用,详情请见:ForwardAuthenticationSuccessHandler

  • defaultSuccessUrl("/login/success"):配置登录成功请求重定向URL(重定向地址栏变),常与 Controller 配合使用,详情请见:SavedRequestAwareAuthenticationSuccessHandler

3.4 效果

3.4.1 请求转发效果

在这里插入图片描述

3.4.2 重定向效果

在这里插入图片描述

四、自定义登录失败请求转发/重定向地址

4.1 创建登录失败页

登录失败页采用原登录页,显示各类登录异常。

4.2 创建 Controller 访问控制

/**
 * 登录失败页面
 *
 * @return
 */
@RequestMapping("/custom/error")
String failure(HttpServletRequest request, Model model) {
    // 以下是配置failureForwardUrl方式获取登录异常
    Object exception = request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    if (exception != null) {
        if (exception instanceof BadCredentialsException) {
            BadCredentialsException badCredentialsException = (BadCredentialsException) exception;
            model.addAttribute("exception", badCredentialsException.getMessage());
            return "login";
        }
    }
    exception = request.getAttribute(WebAttributes.ACCESS_DENIED_403);
    if (exception instanceof AccessDeniedException) {
        AccessDeniedException accessDeniedException = (AccessDeniedException) exception;
        model.addAttribute("exception", accessDeniedException.getMessage());
        return "login";
    }
    // 以下是配置failureUrl方式获取登录异常
    HttpSession session = request.getSession(false);
    if (session != null) {
        exception = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
        model.addAttribute("exception", ((AuthenticationException) exception).getMessage());
        return "login";
    }
    return "login";
}

注意
根据 Spring Security 中失败请求处理的配置不同获取异常的方式也是多样的。

4.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    /**
     * 构建SecurityFilterChain
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置所有http请求必须经过认证
        http.authorizeHttpRequests(authorizeRequests ->
                authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll()
                        .anyRequest().authenticated());

        // 开启表单认证(默认配置)
        // http.formLogin(Customizer.withDefaults());

        // 表单认证自定义配置
        http.formLogin(form ->
                form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址
                        .loginProcessingUrl("/login") // form表单登录处理请求URL(POST)
                        .usernameParameter("username") // form表单用户名参数名称
                        .passwordParameter("password") // form表单密码参数名称
                        //.successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变)
                        .defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变)
                        .failureForwardUrl("/custom/error") // 登录失败请求转发URL(请求转发地址栏不变)
                        //.failureUrl("/custom/error") // 登录失败请求重定向URL(重定向地址栏变)(POST)
        );
        
       // 配置AccessDeniedException异常处理请求URL(POST),主要是是处理401 BadCredentialsException 和 403 AccessDeniedException 异常
        http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error"));
        // 开启 CSRF 保护
        http.csrf(Customizer.withDefaults());
        // 禁止 CSRF 保护
        // http.csrf(csrf -> csrf.disable());
        // 构造器构建SecurityFilterChain对象
        return http.build();
    }

    /**
     * 配置登录名密码
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();
        return new InMemoryUserDetailsManager(new UserDetails[]{user});
    }
}
  • failureForwardUrl("/custom/error"):配置登录失败跳转请求 URL ,常与 Controller 配合使用,详情请见:ForwardAuthenticationFailureHandler

  • failureUrl("/custom/error"):配置登录失败请求重定向URL(重定向地址栏变),常与 Controller 配合使用,详情请见:SimpleUrlAuthenticationFailureHandler

  • http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error")):配置 AccessDeniedException 异常处理调整URL,主要是是处理401 BadCredentialsException403 AccessDeniedException 异常,详情请见:AccessDeniedHandlerImpl

4.4 效果

在这里插入图片描述
在这里插入图片描述

五、自定义登录成功/失败处理器

目前基本都是前后端分离,基于 JSON 实现交与,后端中并没有任何页面,也不需要跳转地址,只需要告知前端登录成功返回用户信息即可,然后由前端进行页面跳转。

5.1 自定义登录成功处理器

在上篇录流程分析过,登录成功后会调用登录成功处理器(默认SavedRequestAwareAuthenticationSuccessHandler)进行页面跳转,那么只需要自定义登录成功处理器,就可以直接实现 JSON 返回,AuthenticationsuccessHandler 接口用于处理用户身份验证成功后的处理策略,实现可以随心所欲:

public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    /**
     * 登录成功后直接返回 JSON
     *
     * @param request        请求
     * @param response       响应
     * @param authentication 成功认证的用户信息
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8"); // 返回JSON
        response.setStatus(HttpStatus.OK.value());  // 状态码 200
        Map<String, Object> result = new HashMap<>(); // 返回结果
        result.put("msg", "登录成功");
        result.put("code", 200);
        result.put("data", authentication);
        response.getWriter().write(JSONUtil.toJsonStr(result));
    }
}

5.2 自定义登录失败处理器

在上篇录流程分析过,登录失败后会调用登录失败处理器(默认SimpleUrlAuthenticationFailureHandler)进行页面跳转,那么只需要自定义登录失败处理器,就可以直接实现 JSON 返回,AuthenticationFailureHandler 接口用于处理用户身份验证成功后的处理策略,实现可以随心所欲:

public class JsonAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8"); // 返回JSON
        response.setStatus(HttpStatus.BAD_REQUEST.value());  // 状态码 400
        Map<String, Object> result = new HashMap<>(); // 返回结果
        result.put("msg", "登录失败");
        result.put("code", 400);
        result.put("data", exception.getMessage());
        response.getWriter().write(JSONUtil.toJsonStr(result));
    }
}

六、自定义注销配置

6.1 创建注销确认页

src/main/resources/templates 目录下创建一个简单的登录成功页面 logout_confirm.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logout Confirmation</title>
    <!-- 引入 Bootstrap 样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card">
                <div class="card-header bg-danger text-white">
                    <h4 class="mb-0">确认注销</h4>
                </div>
                <div class="card-body" sec:authorize="${isAuthenticated()}">
                    <p>您确定要注销吗?</p>
                    <p>用户名: <span th:text="${#authentication.name}"></span></p>
                    <div sec:authorize="${isAuthenticated()}">
                        <form th:action="@{/logout}" method="post">
                            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
                            <a href="/login/success" class="btn btn-success me-2">取消</a>
                            <input type="submit" class="btn btn-danger" value="确认注销" />
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 引入 Bootstrap JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

6.2 创建 Controller 访问控制

/**
 * 注销成功页面
 *
 * @return
 */
@RequestMapping("/logout/success")
String logoutSuccess(HttpServletRequest request, Model model) {
    model.addAttribute("logout", "您已登出");
    return "login";
}

/**
 * 注销确认页面
 *
 * @return
 */
@RequestMapping("/custom/logout")
String logoutConfirm(HttpServletRequest request, Model model) {
    return "logout_confirm";
}

6.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    /**
     * 构建SecurityFilterChain
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置所有http请求必须经过认证
        http.authorizeHttpRequests(authorizeRequests ->
                authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll()
                        .anyRequest().authenticated());


        // 开启表单认证(默认配置)
        // http.formLogin(Customizer.withDefaults());

        // 表单认证自定义配置
        http.formLogin(form ->
                form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址
                        .loginProcessingUrl("/login") // form表单登录处理请求URL(POST)
                        .usernameParameter("username") // form表单用户名参数名称
                        .passwordParameter("password") // form表单密码参数名称
                        .successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变)
                        //.defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变)
                        .failureForwardUrl("/custom/error") // 登录失败请求转发URL(请求转发地址栏不变)
                        //.failureUrl("/custom/error") // 登录失败请求重定向URL(重定向地址栏变)
        );

        // 配置AccessDeniedException异常处理调整URL,主要是是处理401 BadCredentialsException 和 403 AccessDeniedException 异常
        http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error"));

        // 登出自定义配置
        http.logout(logout -> logout.logoutSuccessUrl("/logout/success") // 自定义注销成功后跳转请求URL(POST)
                //.logoutUrl("/logout") // 自定义单个注销处理请求URL(开启CSRF保护时POST,关闭开启CSRF保护时POST GET PUT DELETE)
                .logoutRequestMatcher(new OrRequestMatcher(
                        new AntPathRequestMatcher("/logout","POST"), // 未配置 LogoutSuccessHandler 时,执行默认的 LogoutSuccessHandler
                        new AntPathRequestMatcher("/logout2","GET"),
                        new AntPathRequestMatcher("/logout2","GET")
                )) // 自定义注销处理请求拦截组合
                .defaultLogoutSuccessHandlerFor(new LogoutSuccessHandler() { // 通过添加多个来实现从不同注销处理请求URL退出执行不同的逻辑
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");
                        Map<String, Object> result = new HashMap<String, Object>();
                        result.put("status", 200);
                        result.put("msg", "使用logout1注销成功!");
                        ObjectMapper om = new ObjectMapper();
                        String s = om.writeValueAsString(result);
                        response.getWriter().write(s);
                    }
                }, new AntPathRequestMatcher("/logout1", "GET"))
                .defaultLogoutSuccessHandlerFor(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");
                        Map<String, Object> result = new HashMap<String, Object>();
                        result.put("status", 200);
                        result.put("msg", "使用logout2注销成功!");
                        ObjectMapper om = new ObjectMapper();
                        String s = om.writeValueAsString(result);
                        response.getWriter().write(s);
                    }
                }, new AntPathRequestMatcher("/logout2", "GET")).addLogoutHandler(new LogoutHandler() {
                    @Override
                    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
                        System.out.println("-----自定义注销处理器------");
                    }
                })
        );

        // 开启 CSRF 保护
        http.csrf(Customizer.withDefaults());
        // 禁止 CSRF 保护
        // http.csrf(csrf -> csrf.disable());
        // 构造器构建SecurityFilterChain对象
        return http.build();
    }

    /**
     * 配置登录名密码
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();
        return new InMemoryUserDetailsManager(new UserDetails[]{user});
    }
}
  • logoutUrl("/logout"):配置单个登录请求处理 URL ,用于修改LogoutFilter中拦截登录处理请求 URL 地址。

    在上文介绍过:开启CSRF保护时匹配POST请求类型,关闭开启CSRF保护时匹配POSTGETPUTDELETE请求类型。

  • logoutRequestMatcher(...) :配置组合式请求处理匹配规则,用于不同的登录请求处理匹配不同的处理规则。
  • defaultLogoutSuccessHandlerFor(...) :配置基于不同的登录请求处理匹配不同的注销成功处理器
  • addLogoutHandler(...):配置添加自定义注销处理器

    自定义注销处理器会添加到执行首位,并不会删除默认添加的处理器:。

  • 自定义清理项:
    • clearAuthentication(true):清理Authentication ,默认true

    • deleteCookies("",""):删除某些指定 cookie

    • invalidateHttpSession(true):设置当前登录用户Session(保存登录后的用户信息)无效,默认true

6.4 效果

6.4.1 登录确认效果

在这里插入图片描述
在这里插入图片描述

6.4.2 自定义注销成功处理器效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1332276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

固定效应模型-以stata为工具

固定效应模型-以stata为工具 文章目录 1.固定效应模型2. 模型原理3. `stata`代码实现1.固定效应模型 固定效应模型(Fixed Effects Model)是一种面板数据分析方法,通过引入个体固定效应来控制个体间的异质性,并更准确地估计解释变量对因变量的影响。它在许多经济、社会科学…

快速剪辑视频软件,视频图像翻转软件

在这个信息爆炸的时代&#xff0c;视频已经成为了人们获取信息、娱乐、学习的主要方式之一。一个好的视频&#xff0c;不仅可以吸引观众的眼球&#xff0c;更可以传达出深层次的意义。那该什么快速的编辑视频&#xff0c;有没有好用的工具推荐呢&#xff1f;今天小编就给大家介…

Pytorch项目(模型训练与优化),肺癌检测项目之六

数据优化方案 数据优化方案1&#xff1a;重复抽样 &#xff08;1&#xff09;对多数类的样本实施欠采样&#xff0c;减少多数类数量 &#xff08;2&#xff09;对少数类的样本实施过采样&#xff0c;增加少数类数量 数据优化方案2&#xff1a;数据增强 数据增强&#xff08…

如何提升亚马逊、速卖通店铺自然排名?测评自养号的关键要素

一、自然排名的重要性 一条链接是否推广成功或者赚到钱&#xff0c;就看这条链接的自然排名有没有打上来! 无论是搜索流量的自然排名&#xff0c;还是关联流量的自然排名&#xff0c;或BSR排行榜&#xff0c;这些自然排名的入口就是我们要时刻盯紧的位置。 二、自然排名的位…

【数据库系统概论】第2章-关系数据库

复习记录 2.1 关系数据结构及形式化定义2.1.1 关系2.1.2 关系模式2.1.3 关系数据库 2.2 关系操作2.3 关系的完整性2.4 关系代数2.5 题目 2.1 关系数据结构及形式化定义 2.1.1 关系 一些概念 关系 R ( D 1 , D 2 , . . . , D n ) R(D_1,D_2,...,D_n) R(D1​,D2​,...,Dn​) …

Python----静态Web服务器-返回指定页面数据

1. 静态Web服务器的问题 目前的Web服务器&#xff0c;不管用户访问什么页面&#xff0c;返回的都是固定页面的数据&#xff0c;接下来需要根据用户的请求返回指定页面的数据 返回指定页面数据的实现步骤: 获取用户请求资源的路径根据请求资源的路径&#xff0c;读取指定文件…

Vue3数据交互axios

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

【C++】function包装器全解(代码演示,例题演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…

【LearnOpenGL基础入门——6】纹理

目录 一.前言 二.纹理环绕方式 三.纹理过滤 四.多级渐远纹理 五.加载与创建纹理 六.生成纹理 七.应用纹理 八.纹理单元 一.前言 我们可以为每个顶点添加颜色来增加图形的细节&#xff0c;从而创建出有趣的图像。但是&#xff0c;如果想让图形看起来更真实&#xff0c;我…

vue3(六)-基础入门之自定义组件

一、全局组件 html: <div id"app"><mytemplace></mytemplace> </div>javascript: <script>const { createApp } Vueconst app createApp({})app.component(mytemplace, {template: <div><button>返回</button>…

智慧楼宇整体解决方案:PPT全文50页,附下载

关键词&#xff1a;智慧楼宇建设方案&#xff0c;智慧建筑建设方案&#xff0c;智慧楼宇控制系统&#xff0c;智慧楼宇解决方案&#xff0c;智慧建筑与建造&#xff0c;智慧建筑的定义 一、智慧楼宇背景分析 1、城市化进程加速&#xff1a;随着全球城市化率的提高&#xff0c…

敏捷开发 - 知识普及

敏捷开发- Scrum 前言 知乎有一篇文章描写Scrum,我觉得比较好:https://zhuanlan.zhihu.com/p/631459977 简单科普下PM和PMO 原文来源:https://zhuanlan.zhihu.com/p/546820914 PM - 项目经理(Project Manager) ​ 需要具备以下能力 ​ 1.号召力 2.影响力 3.交流能力 4.应…

三菱人机交互GT Designer的安装

今天&#xff0c;与小编一起来学习三菱的GT Designer软件&#xff0c;下面就是小编记录的软件查找&#xff0c;安装的全过程&#xff0c;希望对你学习三菱有帮助。 目录 安装 选择官网下载安装包 解压安装包进行安装 创建一个工程 安装 选择官网下载安装包 三菱&#xff08;中…

像素级调整,高效转换——轻松提升你的图片处理体验!

探索更高级的图片处理体验&#xff0c;我们为你带来像素级调整与高效转换的完美结合&#xff01;借助我们的专业工具&#xff0c;轻松调整图片像素&#xff0c;让你在细节处展现无限创意&#xff0c;提升作品质感。 第一步&#xff0c;进入首助编辑高手主页面&#xff0c;可以看…

电子学会C/C++编程等级考试2023年03月(六级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:波兰表达式 波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的波兰表示法为+ 2 3。波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的波兰表示法为* + 2 3 4。本…

互联网上门洗衣洗鞋小程序优势有哪些?

互联网洗鞋店小程序相较于传统洗鞋方式&#xff0c;具有以下优势&#xff1b; 1. 便捷性&#xff1a;用户只需通过手机即可随时随地下单并查询&#xff0c;省去了许多不必要的时间和精力。学生们无需走出宿舍或校园&#xff0c;就能轻松预约洗鞋并取件。 2. 精准定位&#xff1…

1859_续流二极管是什么以及其作用

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1859_续流二极管是什么以及其…

飞天使-k8s知识点5-kubernetes基础名词扫盲

文章目录 deploymentspodNodeserviceskubectl 实现应用伸缩kubectl 实现滚动更新kubernetes架构 deployments 中文文档 http://docs.kubernetes.org.cn/251.htmldeployment是用来创建和更新应用的&#xff0c;master 会负责将创建好的应用实例调度到集群中的各个节点 应用实例…

SSTI模板注入基础(Flask+Jinja2)

文章目录 一、前置知识1.1 模板引擎1.2 渲染 二、SSTI模板注入2.1 原理2.2 沙箱逃逸沙箱逃逸payload讲解其他重要payload 2.3 过滤绕过点.被过滤下划线_被过滤单双引号 "被过滤中括号[]被过滤关键字被过滤 三、PasecaCTF-2019-Web-Flask SSTI参考文献 一、前置知识 1.1 模…

合并两个有序链表算法(leetcode第21题)

题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a;输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a;输入&#xff1a;l1 [], l2 [] 输…