Spring Security入门学习

news2024/11/19 23:23:08

认识Spring Security

Spring Security 是为基于 Spring 的应用程序提供声明式安全保护的安全性框架。Spring Security 提供了完整的安全性解决方案,它能够在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入(dependency injection, DI)和面向切面的技术。

核心功能

对于一个权限管理框架而言,无论是 Shiro 还是 Spring Security,最最核心的功能,无非就是两方面:

  • 认证
  • 授权

通俗点说,认证就是我们常说的登录,授权就是权限鉴别,看看请求是否具备相应的权限。

认证(Authentication)

Spring Security 支持多种不同的认证方式,这些认证方式有的是 Spring Security 自己提供的认证功能,有的是第三方标准组织制订的,主要有如下一些:

一些比较常见的认证方式:

  • HTTP BASIC authentication headers:基于IETF RFC 标准。
  • HTTP Digest authentication headers:基于IETF RFC 标准。
  • HTTP X.509 client certificate exchange:基于IETF RFC 标准。
  • LDAP:跨平台身份验证。
  • Form-based authentication:基于表单的身份验证。
  • Run-as authentication:用户用户临时以某一个身份登录。
  • OpenID authentication:去中心化认证。

除了这些常见的认证方式之外,一些比较冷门的认证方式,Spring Security 也提供了支持。

  • Jasig Central Authentication Service:单点登录。
  • Automatic “remember-me” authentication:记住我登录(允许一些非敏感操作)。
  • Anonymous authentication:匿名登录。

作为一个开放的平台,Spring Security 提供的认证机制不仅仅是上面这些。如果上面这些认证机制依然无法满足你的需求,我们也可以自己定制认证逻辑。当我们需要和一些“老破旧”的系统进行集成时,自定义认证逻辑就显得非常重要了。

授权(Authorization)

无论采用了上面哪种认证方式,都不影响在 Spring Security 中使用授权功能。Spring Security 支持基于 URL 的请求授权、支持方法访问授权、支持 SpEL 访问控制、支持域对象安全(ACL),同时也支持动态权限配置、支持 RBAC 权限模型等,总之,我们常见的权限管理需求,Spring Security 基本上都是支持的。

项目实践

创建 maven 工程

项目依赖如下:

<dependencies>
  <!-- 以下是>spring boot依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- 以下是>spring security依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
</dependencies>

提供一个简单的测试接口,如下:

@RestController
public class HelloController {

  @GetMapping("/hello")
  public String hello() {
    return "hello,hresh";
  }

  @GetMapping("/hresh")
  public String sayHello() {
    return "hello,world";
  }
}

再创建一个启动类,如下:

@SpringBootApplication
public class SecurityInMemoryApplication {

  public static void main(String[] args) {
    SpringApplication.run(SecurityInMemoryApplication.class, args);
  }
}

在 Spring Security 中,默认情况下,只要添加了依赖,我们项目的所有接口就已经被统统保护起来了,现在启动项目,访问 /hello 接口,就需要登录之后才可以访问,登录的用户名是 user,密码则是随机生成的,在项目的启动日志中,如下所示:

Using generated security password: 21596f81-e185-4b6a-a8ff-1b21e2a60c6f

我们尝试访问 /hello 接口,因为该接口被 Spring Security 保护起来了,重定向到 /login 接口,如下图所示:

Spring Security登录验证

输入账号和密码后,即可访问 /hello 接口。

那么如何自定义登录用户信息呢?以及 Spring Security 如何知道我们想要支持基于表单的身份验证?

认证

启用web安全性功能

Spring Security 提供了用户名密码登录、退出、会话管理等认证功能,只需要配置即可使用。

在 Spring Security 5.7版本之前,或者 SpringBoot2.7 之前,我们都是继承 WebSecurityConfigurerAdapter 来配置。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  //定义用户信息服务(查询用户信息)
  @Bean
  public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
    return manager;
  }

  //密码编码器,不加密,字符串直接比较
  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/hello");
  }

  //安全拦截机制(最重要)
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .and()
      .httpBasic();
  }
}

Spring Security 提供了这种链式的方法调用。上面配置指定了认证方式为 HTTP Basic 登录,并且所有请求都需要进行认证。

这里有一点需要注意,我没并没有在 Spring Security 配置类上使用@EnableWebSecurity 注解。这是因为在非 Spring Boot 的 Spring Web MVC 应用中,注解@EnableWebSecurity 需要开发人员自己引入以启用 Web 安全。而在基于 Spring Boot 的 Spring Web MVC 应用中,开发人员没有必要再次引用该注解,Spring Boot 的自动配置机制 WebSecurityEnablerConfiguration 已经引入了该注解,如下所示:

package org.springframework.boot.autoconfigure.security.servlet;

// 省略 imports 行

@Configuration(
  proxyBeanMethods = false
)
@ConditionalOnMissingBean(
  name = {"springSecurityFilterChain"}
)
@ConditionalOnClass({EnableWebSecurity.class})
@ConditionalOnWebApplication(
  type = Type.SERVLET
)
@EnableWebSecurity
class WebSecurityEnablerConfiguration {
  WebSecurityEnablerConfiguration() {
  }
}

实际上,一个 Spring Web 应用中,WebSecurityConfigurerAdapter 可能有多个 , @EnableWebSecurity 可以不用在任何一个WebSecurityConfigurerAdapter 上,可以用在每个 WebSecurityConfigurerAdapter 上,也可以只用在某一个WebSecurityConfigurerAdapter 上。多处使用@EnableWebSecurity 注解并不会导致问题,其最终运行时效果跟使用@EnableWebSecurity 一次效果是一样的。

在 userDetailsService()方法中,我们返回了一个 UserDetailsService 给 Spring 容器,Spring Security 会使用它来获取用户信息。我们暂时使用 InMemoryUserDetailsManager 实现类,并在其中分别创建了zhangsan、lisi两个用户,并设置密码和权限。

configure(HttpSecurity http)方法中进入如下配置:

  • 确保对我们的应用程序的任何请求都要求用户进行身份验证
  • 允许用户使用基于表单的登录进行身份验证
  • 允许用户使用HTTP基本身份验证进行身份验证

注意上述还有一个 passwordEncoder()方法,在 IDEA 中会提示 NoOpPasswordEncoder 已过期。这是因为 Spring Security 5对 PasswordEncoder 做了相关的重构,原先默认配置的 PlainTextPasswordEncoder(明文密码)被移除了,想要做到明文存储密码,只能使用一个过期的类来过渡。

//加入
//已过期
@Bean
PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
}

Spring Security 提供了多种类来进行密码编码,并作为了相关配置的默认配置,只不过没有暴露为全局的 Bean。在实际应用中使用明文校验密码肯定是存在风险的,NoOpPasswordEncoder 只能存在于 demo 中。

//实际应用
@Bean
PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

//加密方式与对应的类
bcrypt - BCryptPasswordEncoder (Also used for encoding)
ldap - LdapShaPasswordEncoder
MD4 - Md4PasswordEncoder
MD5 - new MessageDigestPasswordEncoder("MD5")
noop - NoOpPasswordEncoder
pbkdf2 - Pbkdf2PasswordEncoder
scrypt - SCryptPasswordEncoder
SHA-1 - new MessageDigestPasswordEncoder("SHA-1")
SHA-256 - new MessageDigestPasswordEncoder("SHA-256")
sha256 - StandardPasswordEncoder

但是在 Spring Security 5.7版本之后(包括5.7版本),或者 SpringBoot2.7 之后,WebSecurityConfigurerAdapter 就过期了,虽然可以继续使用,但看着比较别扭。

看 5.7版本官方文档是如何解释的:

WebSecurityConfigurerAdapter介绍

以前我们自定义类继承自 WebSecurityConfigurerAdapter 来配置我们的 Spring Security,我们主要是配置两个东西:

  • configure(HttpSecurity)
  • configure(WebSecurity)

前者主要是配置 Spring Security 中的过滤器链,后者则主要是配置一些路径放行规则。

现在在 WebSecurityConfigurerAdapter 的注释中,人家已经把意思说的很明白了:

  • 以后如果想要配置过滤器链,可以通过自定义 SecurityFilterChain Bean 来实现。
  • 以后如果想要配置 WebSecurity,可以通过 WebSecurityCustomizer Bean 来实现。

我们对上文中的 SecurityConfig 文件做一下改动,试试新版中该如何配置。

@Configuration
public class SecurityConfig {

  @Bean
  public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
    return manager;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

  @Bean
  WebSecurityCustomizer webSecurityCustomizer() {
    return web -> web.ignoring().antMatchers("/hello");
  }

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .permitAll()
        .and()
        .csrf().disable();
    return http.build();
  }
}

此时重启项目,你会发现 /hello 也是可以直接访问的,就是因为这个路径不经过任何过滤器。

个人觉得新写法更加直观,可以清楚的看到 SecurityFilterChain 是关于过滤器链配置的,与我们理论知识提到的过滤器知识是一致的。

测试

访问 http://localhost:8086/hello,可以直接看到页面内容,不需要输入账号密码。

访问 http://localhost:8086/hresh,则需要账号密码,即我们配置的 zhangsan 和 lisi 用户。

在测试过程中,你可能会发现这样几个问题:

1、直接访问 http://localhost:8086,默认会跳转到 /login 页面,该配置位于 UsernamePasswordAuthenticationFilter 类文件中,如果你想自定义登录页面,可以这样修改:

    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
//        .loginPage("/login.html")	
        .loginProcessingUrl("/login")

2、表单登录时,账号密码默认字段为 username 和 password。

3、按理来说,登录成功之后是跳到/页面,失败跳转到登录页,但因为我们这是 SpringBoot 项目,我们可以让它登录成功时返回json数据,而不是重定向到某个页面。默认情况下,账号密码输入错误会自动返回登录页面,所以此处我们就不处理失败的情况。

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

  private static ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException {
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write(objectMapper.writeValueAsString("登录成功"));
  }
}

接着修改 securityFilterChain()方法

http.authorizeRequests()
  .anyRequest().authenticated()
  .and()
  .formLogin()
  .successHandler(myAuthenticationSuccessHandler)
  .and()
  .csrf().disable();

再次重启项目,在登录页面输入账号密码后,返回结果如下所示:

SpringSecurity表单登录成功

4、自定义登录页面,在 resource 目录下新建 static 目录,里面添加 login.html 文件,暂时未添加样式

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>登录</title>
</head>
<body>
<form action="/doLogin" method="post">
  <div class="input">
    <label for="name">用户名</label>
    <input type="text" name="name" id="name">
    <span class="spin"></span>
  </div>
  <div class="input">
    <label for="pass">密码</label>
    <input type="password" name="passwd" id="pass">
    <span class="spin"></span>
  </div>
  <div class="button login">
    <button type="submit">
      <span>登录</span>
      <i class="fa fa-check"></i>
    </button>
  </div>
</form>
</body>
</html>

修改 securityFilterChain()方法

http.authorizeRequests()  //表示开启权限配置
  .antMatchers("/login.html").permitAll()
  .anyRequest().authenticated() //表示所有的请求都要经过认证之后才能访问
  .and()  // 链式编程写法
  .formLogin()  //开启表单登录配置
  .loginPage("/login.html") // 配置登录页面地址
  .loginProcessingUrl("/doLogin")
  .permitAll()
  .and()
  .csrf().disable();

重启项目后, 再次访问 http://localhost:8086/,会重定向到 http://localhost:8086/login.html。

最后,总结一下 HttpSecurity 的配置,示例如下:

    http.authorizeRequests()  //表示开启权限配置
        .anyRequest().authenticated() //表示所有的请求都要经过认证之后才能访问
        .and()  // 链式编程写法
        .formLogin()  //开启表单登录配置
        .loginPage("/login.html") // 配置自定义登录页面地址
        .loginProcessingUrl("/login") //配置登录接口地址
//        .defaultSuccessUrl()  //登录成功后的跳转页面
//        .failureUrl() //登录失败后的跳转页面
//        .usernameParameter("username")  //登录用户名的参数名称
//        .passwordParameter("password")  // 登录密码的参数名称
//        .successHandler(
//            myAuthenticationSuccessHandler) //前后端分离的情况,并不想通过defaultSuccessUrl进行页面跳转,只需要返回一个json数据来告知前端
//        .failureHandler(myAuthenticationFailureHandler) // 同理,替代failureUrl
//        .permitAll()
        .and()
				.csrf().disable();// 禁用CSRF防御功能,测试可以先关闭

表单验证时,loginPage 与 loginProcessingUrl 区别:

  • loginPage 配置自定义登录页面地址,loginProcessingUrl 默认与表单 action 地址一致;
  • 如果只配置 loginPage 而不配置 loginProcessingUrl,那么 loginProcessingUrl 默认就是 loginPage;
  • 如果只配置 loginProcessUrl,就会用不了自定义登陆页面,Security 会使用自带的默认登陆页面;

如果 loginProcessingUrl 默认与表单 action 地址不一致,那么它需要指向一个有效的地址,比如说 /doLogin.html,这要求我们在 static 目录下创建一个 doLogin.html 页面,此外,还需要在 controller 文件中增加如下方法:

  @PostMapping("/doLogin")
  public String doLogin() {
    return "我登录成功了";
  }

但是登录成功后并不会显示 doLogin.html 页面的内容,而是显示 /doLogin 的返回结果。同理,不配置 loginProcessingUrl,那么 loginProcessingUrl 默认就是 loginPage,即 loginProcessingUrl=login.html,与 doLogin.html 效果一样。

另外再介绍一下 Spring Security 中 defaultSuccessUrlsuccessForwardUrl 的区别:

假定在 defaultSuccessUrl 中指定登录成功的跳转页面为 /index,那么存在两种情况:

  • ① 浏览器中输入的是登录地址,登录成功后,则直接跳转到 /index
  • ② 如果浏览器中输入了其他地址,例如 http://localhost:8080/elseUrl,若登录成功,就不会跳转到 /index,而是来到 /elseUrl 页面。

defaultSuccessUrl 就是说,它会默认跳转到 Referer 来源页面,如果 Referer 为空,没有来源页,则跳转到默认设置的页面。

successForwardUrl 表示不管 Referer 从何而来,登录成功后一律跳转到指定的地址。

认证方式选择

在 WebSecurityConfigurerAdapter 类中有很多 configure()方法,除了上文提到的 HttpSecurity 和 WebSecurity 参数,还有一个 AuthenticationManagerBuilder 参数,源码如下:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  this.disableLocalConfigureAuthenticationBldr = true;
}

protected AuthenticationManager authenticationManager() throws Exception {
  if (!this.authenticationManagerInitialized) {
    this.configure(this.localConfigureAuthenticationBldr);
    if (this.disableLocalConfigureAuthenticationBldr) {
      this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
    } else {
      this.authenticationManager = (AuthenticationManager)this.localConfigureAuthenticationBldr.build();
    }

    this.authenticationManagerInitialized = true;
  }

  return this.authenticationManager;
}

该类用于设置各种用户想用的认证方式,设置用户认证数据库查询服务 UserDetailsService 类以及添加自定义 AuthenticationProvider 类实例等

Spring Security 为配置用户存储提供了多个可选解决方案,包括:

  • 基于内存的用户存储
  • 基于 JDBC 的用户存储
  • 以 LDAP 作为后端的用户存储
  • 自定义用户详情服务

关于这四种方式就不详细介绍了,可以重点关注方案二和方案四,而在本项目中,我们直接在 SecurityConfig 中重写 userDetailsService 方法,并将 UserDetailsService 对象注入到 Spring 容器中。

授权

1、首先在 HelloController 中增加 r1 和 r2 资源。

@GetMapping(value = "/r/r1")
public String r1() {
  return " 访问资源1";
}

@GetMapping(value = "/r/r2")
public String r2() {
  return " 访问资源2";
}

2、修改 SecurityConfig 文件中的 securityFilterChain()方法

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/r/r1").hasAuthority("p1")
    .antMatchers("/r/r2").hasAuthority("p2")
    .anyRequest().authenticated()
    .and()
    .formLogin()
    .successHandler(myAuthenticationSuccessHandler)
    .permitAll()
    .and()
    .csrf().disable();
  return http.build();
}

访问 r1、r2 资源,需要对应的权限,而且其他接口则只需要认证,并不需要授权。

3、测试

访问 http://localhost:8086 ,进入登录页面,输入正确的账号密码,提交后页面返回“登录成功”,如果是 zhangsan,则可以访问 r1资源,访问 r2则会报错,我们暂时未处理错误如下:

SpringSecurity无权访问

总结

关于 Spring Security 的学习先到这里,基本了解如何使用即可,我们继续后面的学习。

如果想要深入学习 Spring Security,推荐阅读《深入浅出Spring Security》,还包括配套的代码示例。

参考文献

Spring Security 核心过滤器链分析

spring security的认证和授权流程

Spring Security – Spring Boot中开启Spring Security

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

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

相关文章

树莓派Pico开发板与大功率MOSFET/IGBT器件驱动控制24V直流电机技术实践

摘要&#xff1a;本文在介绍MOSFET器件和IGBT器件作为电子开关基本原理的基础上&#xff0c;讲述了Pico与MOSFET&IGBT器件驱动控制24V直流电机硬件接口技术&#xff0c;最后给出了Pico开发板GP15端口控制24V直流电机启停的MicroPython测试程序。 一、实验设备和元器件清单 …

[附源码]Nodejs计算机毕业设计基于Java的智慧停车软件Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

React 入门:实战案例 TodoList 对组件的 props 进行限制

文章目录安装 prop-types 库给组件的 props 添加限制给 Header 组件添加限制给 List 组件添加限制给 Item 组件添加限制验证 props 限制完整代码Header 组件完整代码List 组件完整代码Item 组件完整代码本文实现对组件的 props 进行属性的类型和必要性的限制。为什么要对 props…

双十二买什么数码产品比较值?入手超值的数码好物盘点

2022年双十二正式开启倒计时模式&#xff0c;最近看到很多人问什么数码产品值得入手。现如今&#xff0c;数码产品已经贯彻在我们生活的方方面面&#xff0c;在此&#xff0c;我来给大家盘点几款入手超值的数码好物&#xff0c;可以当个参考。 一、蓝牙耳机 推荐产品&#xf…

JUC(6) : LockSupport | 优雅的线程通信工具

一、前言 前文介绍了 CompletableFuture 和 线程池的几种对线程的管理方式后&#xff0c;本质上&#xff0c;通过这些工具&#xff0c;可以直接帮我们对线程进行很好的管理和运作&#xff0c;什么时间需要启动哪个线程&#xff0c;以及线程的执行顺序等。毕竟&#xff0c;线程…

视频监控在油气长输管道巡护管理的应用解决方案

一、方案背景 由于油气产地与消费中心位置的不一致性&#xff0c;常常需要采用长距离的油气管道运输。从偏僻的矿区到繁华的街市&#xff0c;管道架设的环境十分复杂&#xff0c;一旦发生危险&#xff0c;后果将不堪设想。因此&#xff0c;为确保管道安全运行&#xff0c;消除…

redis高可用之主从复制,哨兵,集群

目录 前言 一、主从复制 1、主从复制的作用 2、主从复制流程 3、部署Redis 主从复制步骤 3.1 环境准备 3.2 首先要搭建redis&#xff0c;并关闭防火墙 3.3 修改Redis 配置文件(Master节点操作) 3.4 修改Redis 配置文件(Slave节点操作) 3.5 验证主从效果 二、哨兵 1…

App逆向案例 X嘟牛 - Frida监听 WT-JS工具还原(一)

App逆向案例 X嘟牛 - Frida监听 & WT-JS工具还原&#xff08;一&#xff09; 提示&#xff1a;文章仅供参考&#xff0c;禁止用于非法途径&#xff1b; 文章目录App逆向案例 X嘟牛 - Frida监听 & WT-JS工具还原&#xff08;一&#xff09;前言一、资源推荐二、App抓包分…

基于android平台的语音机器人服务娱乐系统

分 类 号&#xff1a;TP311 学校代码&#xff1a;11460 学 号&#xff1a;10130920 本科生毕业论文 基于android平台的语音机器人服务娱乐系统 Robot Entertainment Service System Based on Android Platform 所在系&#xff08;院&#xff09;&#xff1a; 学 生&…

如何通过快解析建设“智慧工地”,实现远程管理维护

A企业是我国某大型房地产企业&#xff0c;早在几年前就实现了全国化布局&#xff0c;目前除了住宅开发与销售、商用地产开发与销售及持有运营业务外&#xff0c;还涉猎房地产金融、物业服务与社区经营等领域。作为中国房地产的知名企业&#xff0c;从2020年起&#xff0c;A企业…

CentOS虚拟机搭建Hadoop集群

注&#xff1a;本文是对 https://www.bilibili.com/video/BV1CU4y1N7Sh 的实践。 环境 CentOS 7.7JDK 8Hadoop 3.3.0 准备 VMWare的网络设置&#xff1a;略。 准备好3台虚拟机&#xff0c;其IP地址分别为 192.168.88.151 、 192.168.88.152 、 192.168.88.153 &#xff0c…

RK3399 Android 8.1 开机动画制作全流程详解

文章目录一、开机动画包二、开机动画图片三、desc.txt编写规范四、开机动画临时生效五、开机动画内置系统一、开机动画包 N个文件夹和一个desc.txt。文件夹中是开机动画的图片资源&#xff0c;按照文件名顺序播放。desc.txt是开机动画的播放规范。压缩包必须是zip&#xff0c;…

力扣(LeetCode)1781. 所有子字符串美丽值之和(C++)

模拟 & 哈希集合 使用哈希集合&#xff0c;开字符集&#xff0c;下标对应小写字母顺序&#xff0c;值对应字符出现次数。 所有子字符串&#xff0c;根据示例看出&#xff0c;是连续子字符串。那么枚举起点&#xff0c;再枚举子字符串长度&#xff0c;就是所有连续子字符串…

web网页大作业:基于html设计与实现的茶文化网站12页(带psd)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

如何使用PyMySQL模块进行增删改查?

在正式动手之前&#xff0c;我们需要先安装 PyMySQL 模块。 &#xff08;1&#xff09;使用 pip 安装, 清华镜像&#xff1a; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pymysql &#xff08;2&#xff09;使用 conda 安装 conda install pymysql Step2: …

毕业/课程设计——基于STM32的智能灯光控制系统(智能家居、手机APP控制、语音控制)

文章首先介绍本系统所包含的功能&#xff0c;主要包含六方面功能&#xff0c;之后逐步分享开发过程&#xff0c;其流程如下&#xff1a;点亮灯带&#xff08;三极管&#xff09;→调节灯光亮度&#xff08;PWM&#xff09;→为系统添加远程控制功能→为系统添加语音识别功能→添…

【脚本项目源码】Python实现鲁迅名言查询系统

前言 本文给大家分享的是如何通过利用Python实现鲁迅名言查询系统&#xff0c;废话不多直接开整~ 开发工具 Python版本&#xff1a; 3.6 相关模块&#xff1a; PyQt5模块 fuzzywuzzy模块 环境搭建 安装Python并添加到环境变量&#xff0c;pip安装需要的相关模块即可。 …

R语言使用最优聚类簇数k-medoids聚类进行客户细分

k-medoids聚类简介 k-medoids是另一种聚类算法&#xff0c;可用于在数据集中查找分组。k-medoids聚类与k-means聚类非常相似&#xff0c;除了一些区别。k-medoids聚类算法的优化功能与k-means略有不同。最近我们被客户要求撰写关于聚类的研究报告&#xff0c;包括一些图形和统…

【JavaWeb开发-Servlet】将项目部署在云服务器

目录 1、环境 &#xff08;1&#xff09;连接服务器 &#xff08;2&#xff09;安装JDK1.8​编辑 ①到官网下载环境 ②将压缩包上传至linux中 ③解压jdk压缩包 ④修改文件名 ⑤配置环境变量 &#xff08;3&#xff09;安装MySQL5.0 ①先下载MySQL的yum库 ②选择5.7发行版本…

基于C#+SQL Server(WinForm)学生选课及成绩查询管理系统【100010027】

学生选课及成绩查询管理系统的设计与开发 1、项目背景 学生选课及成绩查询系统是一个学校不可缺少的部分&#xff0c;传统的人工管理档案的方式存在着很多的缺点&#xff0c;如&#xff1a;效率低、保密性差等&#xff0c;所以开发一套综合教务系统管理软件很有必要&#xff…