彻底弄清SpringSecurity登录原理及开发步骤

news2024/12/25 9:03:52

SpringBoot+Vue之SpringSecurity登录与授权(一)

工具:idea2018,springboot 2.1.4,springsecurity 5.1.5

简介

SpringSecurity是Spring下的一个安全框架,与shiro 类似,一般用于用户认证(Authentication)和用户授权(Authorization)两个部分,常与与SpringBoot相整合。

开发步骤

便于理解,下一节再使用前后端分离,并引入数据库用户和角色信息

测试登录

1 导入依赖

(pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
复制代码

2 编写测试方法

(controller.UserController)

@Controller
public class UserController {

    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello controller";
    }
}    
复制代码

3 测试

启动项目,浏览器访问:localhost:8080/hello,地址栏自动跳转到http://localhost:8080/login,进入默认登陆页面,验证登录

Username默认为user,Password随机生成(实际就是UUID),查看控制台。

Spring Security默认进行URL访问进行拦截,并提供了验证的登录页面

输入密码,我这里目前是c1068cdb-18f3-48f4-b838-7698218d14c4。登录成功

这里的用户名和密可以修改,直接在配置文件中修改登录名和密码,如

(application.properties)

spring.security.user.name=admin
spring.security.user.password=123
复制代码

切入源码

1> 用户参数

参照源码,查看静态内部类。可以看出,默认用户的密码实际就是一个UUID。

(SpringSecurity -- SecurityProperties.java)

@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
    ...
    // 默认用户
    private User user = new User();
    ...
	
    public static class User {
       // 默认用户名
        private String name = "user";

        // 默认用户名的默认密码,随机生成
        private String password = UUID.randomUUID().toString();

        // 默认用户名的角色
        private List<String> roles = new ArrayList<>();

        // 是否生成密码
        private boolean passwordGenerated = true;

       ...     
    }    
}
复制代码

2> 用户名密码验证

  • 导入security依赖后,默认访问的路径将经过该过滤器,并访问其无参构造,创建一个新的post方式的登录请求,路径为/login

  • 进入默认登录页

  • 通过HttpServletRequest对象获取到登录表单中的用户名和密码

  • 创建一个用户名和密码的令牌对象

  • 处理登陆表单的信息

(SpringSecurity -- UsernamePasswordAuthenticationFilter.java)

// @since spring security 3.0
public class UsernamePasswordAuthenticationFilter extends
      AbstractAuthenticationProcessingFilter {

   public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
   public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

   private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
   private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
   private boolean postOnly = true;

    // 构造器,以不区分大小写的方式post方式和HTTP方法创建匹配器。
   public UsernamePasswordAuthenticationFilter() {
      super(new AntPathRequestMatcher("/login", "POST"));
   }

   public Authentication attemptAuthentication(HttpServletRequest request,
         HttpServletResponse response) throws AuthenticationException {
      if (postOnly && !request.getMethod().equals("POST")) {
         throw new AuthenticationServiceException(
               "Authentication method not supported: " + request.getMethod());
      }
		
      // 从请求路径获取用户名和密码 
      String username = obtainUsername(request);
      String password = obtainPassword(request);

       // 空值判断
      if (username == null) {
         username = "";
      }

      if (password == null) {
         password = "";
      }

       // 去除用户名首尾空格
      username = username.trim();

       // 生成一个用户名密码身份验证的令牌
      UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

      // 设置身份认证请求的信息
      setDetails(request, authRequest);

       // 返回一个完全经过身份验证的对象,包括凭据
      return this.getAuthenticationManager().authenticate(authRequest);
   }
    ....
    
    protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}
}    
复制代码

自定义登录接口

(为便于解释,不引入数据库信息验证)

1 实现接口

实现UserDetailsService接口,重写方法。

(service.MyUserDetailsSerice)

/**
 * 自定义登录接口(核心接口,加载用户特定的数据。)
 */
@Component
public class MyUserDetailsSerice implements UserDetailsService {
    // 日志 返回与作为参数传递的类对应的日志程序
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);


    /**
     * 校验,根据用户名定位用户
     * @param username 标识需要其数据的用户的用户名。
     * @return 核心用户信息,一个完全填充的用户记录
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("登录,用户名:{}", username);
        return new User(username, "123", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
复制代码

2 配置登录拦截

继承WebSecurityConfigurerAdapter配置类,重写里面的配置方法

配置方法可查看官网springboot或查看EnableWebSecurity接口的注释信息

(config.MySecurityConfig)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);
        // 基础配置
        http.httpBasic()
                .and()
                // 身份认证
                .authorizeRequests()
                // 所有请求
                .anyRequest()
                // 身份认证
                .authenticated();
    }        
复制代码

返回的User实现了UserDetail接口,详情见切入源码

3 测试

启动项目,清除浏览器缓存,访问hello,跳转到默认登录页面,校验密码。登录时,用户名任意,密码必须为123(MyUserDetailsSerice中已配置)。

登录失败,控制台打印,没有针对id“null”PasswordEncoder(映射的密码编码器)

4 加入密码编码器组件

继承PassawordEncoder接口

/**
 * 用于编码密码的服务接口的实现类。
 */
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    /**
     *  编码原始密码。通常,良好的编码算法应用SHA-1或更大的哈希与8字节或更大的随机生成的盐相结合。
     * @param rawPassword 密码,一个可读的字符值序列
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * 验证从存储中获得的编码密码是否与提交的原始密码匹配。如果密码匹配,返回true;如果不匹配,返回false。存储的密码本身永远不会被解码。
     * @param rawPassword 预设的验证密码。要编码和匹配的原始密码
     * @param encodedPassword 表单输入的密码。来自存储的编码密码与之比较
     * @return
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(rawPassword.toString());
    }
}
复制代码

4 测试

重启项目,清除浏览器缓存,访问hello。

切入源码

1 关于WebSecurityConfigurerAdapter可参考接口EnableWebSecurity

(SpringSecurity -- EnableWebSecurity)

/**
 * Add this annotation to an {@code @Configuration} class to have the Spring Security
 * .............
 * 	&#064;Override
 * 	protected void configure(HttpSecurity http) throws Exception {
 * 		http.authorizeRequests().antMatchers(&quot;/public/**&quot;).permitAll().anyRequest()
 * 				.hasRole(&quot;USER&quot;).and()
 * 				// 更多配置 ...
 * 				.formLogin() // 确保基础表单登录
 * 				// 为所有与表单登录相关联的URL设置许可证
 * 				.permitAll();
 * 	}
 *
 * ...................
 * @since 3.2
 */
...
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	// 默认关闭debug模式
	boolean debug() default false;
}

复制代码

2 security中封装的默认用户User的信息 (SpringSecurity -- User.java)

// 
public class User implements UserDetails, CredentialsContainer{
   ...
    private String password;
	private final String username;
    // 用户权限集合
	private final Set<GrantedAuthority> authorities;
    // 账户未过期
	private final boolean accountNonExpired;
    // 账户未锁定
	private final boolean accountNonLocked;
    // 凭据未过期
	private final boolean credentialsNonExpired;
    // 用户可用
	private final boolean enabled;
    ...
}
复制代码

密码加密

1 注入密码编码器对象

继承WebSecurityConfigurerAdapter配置类

在MySecurity中直接注入一个BCryptPasswordEncoder对象。它实现了PasswordEncoder接口,并重写了encodematches方法

(config.MySecurityConfig.java)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 实现使用BCrypt强哈希函数的密码编码器。客户机可以选择性地提供“强度”(即BCrypt中的日志轮数)和SecureRandom 实例。
     * 强度参数越大,需要做的工作就越多(指数级)来散列密码。默认值是10。
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    ...
}
复制代码

2 完善服务层

完善MyUserDetailsSerice

(service.MyUserDetailsSerice.java)

@Component
public class MyUserDetailsSerice implements UserDetailsService {
	...
        
    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123");
        logger.info("登录,用户名:{},密码:{}", username,password);
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
复制代码

3 测试

注释掉MyPasswordEncoder的@component注解,使其失去容器组件身份

使用debug模式,启动项目,访问hello。

debug可看到密码的转化,原始密码123加密为为$2a10YGYb9i0ZjnTHPlOk/NQb/efrPNOaJq8hJYtdXf8VcdQUi8T8S3Iim

控制台打印日志

切入源码

可以看到,这里自动注入的其实是BCryptPasswordEncoder对象,并调用了encode方法

(SpringSecurity -- BCryptPasswordEncoder)

// 构造器
public BCryptPasswordEncoder() {
	this(-1);
}
public BCryptPasswordEncoder(int strength) {
    ...
}
public BCryptPasswordEncoder(int strength, SecureRandom random) {
    ...
}
...
    
public String encode(CharSequence rawPassword) {
    // 盐值
   String salt;
    // 判断构造器是否有相应参数
   if (strength > 0) {
      if (random != null) {
          // 通过random和strength生成的salt
         salt = BCrypt.gensalt(strength, random);
      }
      else {
           // 通过strength生成的salt
         salt = BCrypt.gensalt(strength);
      }
   }
    // 无参构造
   else {
       // 调用gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);随机生成salt
       // GENSALT_DEFAULT_LOG2_ROUNDS = 10
      salt = BCrypt.gensalt();
   }
    // 使用OpenBSD bcrypt方案散列密码,参数分别为原始密码和盐值
   return BCrypt.hashpw(rawPassword.toString(), salt);
}
复制代码
  • 这里BCryptPasswordEncoder使用的无参,使用默认的盐值,循环10次,生成了散列的密码。

  • 这里虽然是123,但每次加密后都不相同,Spring Security在进行密码加密的时候,生成了一份随机salt,最终加密的密码=密码+随机salt。

  • 注意这里的AuthorityUtils的方法,参数包含角色信息。实际业务中,一般以“ROLE_**”规定用户的角色字段,并在登录后授予相应权限

/**
 *从逗号分隔的字符串表示创建一个GrantedAuthority对象数组(例如“ROLE_A,ROLE_B,ROLE_C”)
 *@param authorityString 逗号分隔的字符串
 *@return 通过标记字符串创建的权限
/
AuthorityUtils.commaSeparatedStringToAuthorityList("admin")
复制代码

自定义登录请求

不使用springsecurity提供的默认登陆界面

1 自定义前端登录页

(template.login.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>欢迎登录</h2>
<form action="/auth/login" method="post">
    <input name="username" type="text" placeholder="请输入用户名.."><br/>
    <input name="password" type="password" placeholder="请输入密码.."><br/>
    <input type="submit" value="登录">
</form>
</body>
</html>
复制代码

2 自定义首页

(template.index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MAIN首页</title>
</head>
<body>
<h1>欢迎来到首页</h1>
</body>
</html>
复制代码

3 在控制器类中添加跳转路径

@Controller
public class UserController {

    // 登录测试
	...

    // 登录页,跳转到/templates/login.html页面
    @GetMapping("/login")
    public String login() {
        return "login";
    }

    // 首页,跳转到/templates/index.html页面
    @GetMapping("/index")
    public String index() {
        return "index";
    }
}    
复制代码

4 修改拦截配置

修改MySecurityConfig中configure方法

(config.MySecurityConfig.java)

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
	...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                // 表单认证
                .formLogin()
                // 登录页
                .loginPage("/login")
                // 登录表单提交地址
                .loginProcessingUrl("/auth/login")
                .and()
                // 身份认证请求
                .authorizeRequests()
                // URL路径匹配
                .antMatchers("/login").permitAll()
                // 任意请求
                .anyRequest()
                // 身份认证
                .authenticated();

    }
}
复制代码

loginProcessingUrl("/auth/login")中定义了表单提交地址,但在控制器UserController中并没有对应的请求路径,SpringSecutity默认拦截所有请求,并将URL 302重定向到/login默认登录页,使用默认的用户名密码即可登录。

自定义登录请求状态

方式一:继承接口实现

1 自定义登录成功类

(handler.MyAuthenticationSuccessHandler.java)

/**
 * 继承接口,用于处理成功的用户身份验证的策略
 */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);

    // 提供了读取和写入JSON的功能,可以与基本pojo类进行交互,也可以与通用JSON树模型进行交互,还提供了执行转换的相关功能。
    @Autowired
    private ObjectMapper objectMapper;

    // 当用户已成功通过身份验证时调用。
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        response.setContentType("application/json;charset=utf-8");
        // writeValueAsString:将java对象序列化为字符串
        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

复制代码

2 自定义登录失败类

(handler.MyAuthenticationFailureHandler.java)

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        logger.info("登录失败");
        // http状态,200,成功
        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception));
    }
}
复制代码

方式二:修改MySecurityConfig的配置方法

1 添加登录成功和失败的处理方法

(config.MySecurityConfig.java)

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()        
        .formLogin()
        .loginPage("/login")
        .loginProcessingUrl("/auth/login")
        // 登陆成功处理器
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                ObjectMapper om = new ObjectMapper();
                String successMsg = om.writeValueAsString(om.writeValueAsString(authentication));
                writer.write(successMsg);
                writer.flush();
                writer.close();
            }
        })
        // 登陆失败处理器
        .failureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                resp.setContentType("application/json;charset=utf-8");
                PrintWriter writer = resp.getWriter();
                writer.write(new ObjectMapper().writeValueAsString(e));
                writer.flush();
                writer.close();
            }
        })
        .and()
        .authorizeRequests()
        .antMatchers("/login").permitAll()
        .anyRequest()        
        .authenticated();

}
复制代码

获取当前用户信息

(controller.UserController.java)

@Controller
public class UserController {
	...

    // 当前用户信息
    @GetMapping("/info")
    @ResponseBody
    public Object getCurrentUser(Authentication authentication) {
        return authentication;
    }
}
复制代码

测试

启动项目,访问/info,登录成功,检查F12

来源:https://juejin.cn/post/6844903834402193415

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

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

相关文章

【案例教程】MAXENT模型的生物多样性生境模拟与保护优先区甄选、自然保护区布局优化评估及论文写作技巧

【查看原文】基于MAXENT模型的生物多样性生境模拟与保护优先区甄选、自然保护区布局优化评估及论文写作技巧 随着生物多样性全球大会的举办&#xff0c;不论是管理机构及科研单位、高校都在积极准备&#xff0c;根据国家林草局最新工作指示&#xff0c;我国将积极整合、优化自…

ruoyi导入数据,第二次导入相同文件时,覆盖原数据;

考勤记录表——在导入时——应该做判断——避免重复导入成功报错 1.现在是&#xff0c;可以多次成功导入同一个文档&#xff0c;导致数据展示出错 期望&#xff1a; 导入文件时&#xff0c;如果这个文件名内数据select值不为0&#xff0c;那么覆盖表数据&#xff08;update&…

vue组件库搭建报错问题(vue-loader报错、gulp打包css报错,包含组件库打包文件webpack及gulp)

1、vue-loader报错 报错详情 vue-loader 17.0.0运行webpack打包命令时会报错&#xff1a; 解决方法&#xff1a; 需适配版本&#xff1a;将package.json&#xff0c;vue-loader版本修改为 “vue-loader”: “^15.10.0”&#xff0c;删除本地node_modules文件夹&#xff0c;重…

这让人眼前一亮的MySQL14个小玩意

前言 我最近几年用MYSQL数据库挺多的&#xff0c;发现了一些非常有用的小玩意&#xff0c;今天拿出来分享到大家&#xff0c;希望对你会有所帮助。 1.group_concat 在我们平常的工作中&#xff0c;使用group by进行分组的场景&#xff0c;是非常多的。 比如想统计出用户表中…

整个网页设置为全灰

前段时间&#xff0c;不是全网变灰色了嘛&#xff0c;整个网页的按钮、文本框、图片、文字等等都变成了灰蒙蒙的了&#xff0c;这是怎么做到的呢&#xff1f; 案例如下&#xff1a; 我就随便写个普通的html网页吧&#xff0c;在网页里随便写点文字和随便放几张图片(大概意思下…

TensorFlow安装与配置教程(2022.12)

1. TensorFlow的安装 首先需要安装 Anaconda 环境&#xff0c;可以转至&#xff1a;Anaconda3安装与配置教程&#xff08;2022.11&#xff09;。 然后我们打开 Anaconda&#xff0c;创建一个 TensorFlow 环境&#xff1a; conda create -n TensorFlow python3.9进入 TensorF…

API接口相关设计及基础知识

接口开发 API接口是什么 接口&#xff1a;预先定义的函数逻辑&#xff0c;供其他系统请求&#xff0c;然后返回结果 为什么需要API接口 核心需求&#xff1a;利用现有接口降低开发成本&#xff0c;缩短开发时间 API接口的核心是什么 接口地址请求参数&#xff08;报文&am…

OPEN ALLIANCE TC2和TC9线束以太网测试标准?何种设备可以满足测试?除了矢量网分之外

OPEN ALLIANCE TC2和TC9线束以太网测试标准&#xff1f; 联盟中TC2汽车线束以太网线束定义标准&#xff1a; 100BASE-T1 Ethernet Channel & Components 100BASE-T1 offers a way to introduce modern signal processing in automotive, which allows optimal usage of th…

Prometheus Operator实战—— Prometheus、Alertmanager、Grafana 监控Springboot服务

1. Spring Boot 工程集成 Micrometer 1.1引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency><groupId>io.micrometer&l…

JVM简单介绍

JVMJVM内存区域划分JVM类加载机制JVM垃圾回收机制【哪些内存需要被JVM中垃圾回收机制回收】【JVM中垃圾回收机制的基本单位】【JVM中垃圾回收机制是如何判断对象是否是垃圾】【如何回收垃圾】JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java虚拟机。虚拟机是指通过软…

电巢:半导体ATE国产化产业探究(附国内外厂家汇总)

前言 2022年10月7日&#xff0c;美国BIS发布近年来范围最大半导体管制举措&#xff0c;管控范围包括芯片、设备、零部件、人员等。 12月6日下午&#xff0c;全球最大晶圆代工厂台积电&#xff0c;在美国亚利桑那州凤凰城高调举行了首批机台设备迁机仪式。 据联合早报12月8日报道…

分蛋糕

题目描述 SYCSYC在每个阶段结束(语言阶段、基础算法、提高算法)后,都会切蛋糕 现在MasMas有一个蛋糕,他希望将蛋糕分成nn份 规定一刀合法的蛋糕切法为符合以下两种条件之一 一刀的切痕迹是一条线段,线段两个端点在圆上,且线段经过圆心(切痕为圆的直径) 一刀的切痕迹是一条线…

[附源码]计算机毕业设计Python电商小程序(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

2023年数字信号处理前沿国际会议(CFDSP 2023)

2023年数字信号处理前沿国际会议&#xff08;CFDSP 2023&#xff09; 重要信息 会议网址&#xff1a;www.cfdsp.org 会议时间&#xff1a;2023年2月24-26日 召开地点&#xff1a;新加坡 截稿时间&#xff1a;2022年12月31日 录用通知&#xff1a;投稿后2周内 收录检索&am…

【OpenCV】SURF图像拼接和Stitcher拼接

介绍两种图像拼接的方法&#xff0c;一种是SURF算法的图像拼接&#xff0c;另一种是Stitcher硬拼接 首先先从简单的讲起 一、Stitcher直接拼接 可以实现多张图片一起拼接&#xff0c;只要两行代码就可以实现拼接&#xff1b; 1.首先准备多张图片&#xff0c;放入向量容器中…

PDF如何插入空白页?3 次点击在PDF中插入空白页!

由于工作和学习的需要&#xff0c;您可能需要在现有的PDF文件中插入一张空白页或几页PDF&#xff0c;使之成为一个完整的PDF文件。但是&#xff0c;PDF文件实际上是最终作品&#xff0c;似乎不可能向其添加额外的页面。事实上&#xff0c;将空白页或 PDF 文件插入现有 PDF 文件…

Conv2Former ~2

还是来说Conv2Former~~ 上次发了一次~~ 一种卷积调制模块&#xff0c;利用卷积来建立关系&#xff0c;这比注意力机制在处理高分辨率图像时更高效&#xff0c;称为 Conv2Former。作者在 ImageNet 分类、目标检测和语义分割方面的实验也表明&#xff0c;Conv2Former 比以前基于…

【2022.12.19】备战春招Day14——每日一题 + 234. 回文链表 + 860. 柠檬水找零

【每日一题】1971. 寻找图中是否存在路径 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n -1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi之间的双向边。…

昆仑万维的AI作画简直就是业界天花板

AI作画的业界天花板被我找到了&#xff0c;AIGC模型揭秘 | 昆仑万维。 一、前景 1、AI和AIGC的关系 人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的…

SpringMVC入门到实战------ 十一 拦截器的使用

1、拦截器的配置 拦截器用于拦截控制器方法的执行 拦截器需要实现HandlerInterceptor 拦截器必须在SpringMVC的配置文件中进行配置 1.1 和1.2 是对所有的请求进行拦截&#xff1b;1.3 对排出的请求不在拦截 1.1 不使用注解的情况 创建拦截器类 /*** 拦截器* author zyz* ve…