Spring Security 复盘

1、什么Spring Security?

        Spring Security 是一种强大的框架,它在 Spring 生态系统中扮演着保护应用安全的关键角色。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。


2、认证 和 授权

1.什么是认证?

        用户的认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

        通俗点说就是系统认为用户是否能登录

        在 Spring Security 中,认证是通过AuthenticationManager接口处理的,它通常使用一系列的AuthenticationProvider来验证用户身份。

2.什么是授权?

        验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。

        比如拿文件来说,有的用户只能进行读取,而有的用户可以进行修改。

        通俗点讲就是系统判断用户是否有权限去做某些事情。

3、为什么Spring Security  (对比)

特征Spring SecurityApache Shiro
设计哲学集成于 Spring 框架,重点关注企业级安全需求。独立的安全框架,设计简单,易于集成和使用。
主要优点和 Spring 无缝整合,全面的权限控制,适应 Web 开发。新版本可脱离 Web 环境使用。轻量级,性能优异,通用性强,不局限于 Web 环境。
局限性旧版本依赖 Web 环境,重量级。在 Web 环境下特定需求可能需手动编码。
认证支持支持表单登录、LDAP、数据库认证、OAuth等。支持多种认证策略,包括基于角色的访问控制和简单的身份验证。
授权支持支持基于角色和表达式的访问控制。提供静态和动态权限分配,支持角色基础的授权。
会话管理依赖外部容器或Spring框架的会话管理。内建会话管理,适合于非Web环境下的应用。
扩展性和定制高度可定制和可扩展,适应复杂安全需求。提供简单直接的扩展机制,易于快速部署和定制。
使用场景适合大型企业级应用,尤其是基于Spring的应用。适合不依赖Spring或需简单安全框架的应用。
常见技术栈Spring Boot/Spring Cloud + Spring SecuritySSM + Shiro

4、入门案例

1、新建SpringBoot工程securitydemo1(2.2.1.RELEASE)


2、POM

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

3、新建控制层

@RestControllerpublic class TestController {@GetMapping("/hello")public String test() {return "hello World";}}

4、启动并访问localhost:8080/help进入如下验证页面

image.png


默认用户名user,密码为如下默认密码

 5、基本原理

1. 认证(Authentication)

        认证是过程中第一步,目的是确认一个用户的身份。用户在登录时提供凭据(通常是用户名和密码),Spring Security 通过配置的认证机制来验证这些凭据是否与系统中的相匹配。这个过程通常涉及以下几个组件:

  • AuthenticationManager: 这是认证过程的主要入口点,负责协调认证过程。
  • AuthenticationProvider: 这是一个接口,定义了具体的认证逻辑。例如,可以通过数据库、LDAP等多种方式来实现。
  • UserDetailsService: 在大多数情况下,这个服务用于根据用户名查找用户的详细信息,通常是用户的密码、角色等。
  • PasswordEncoder: 用于在验证用户提交的密码时,对存储的密码进行编码比较。

2. 授权(Authorization)

        一旦用户通过认证,下一步是确定他们是否有权执行特定的操作。授权可以在访问控制列表、角色基于的控制或更细粒度的权限控制级别上进行。

  • AccessDecisionManager: 负责决定访问控制的核心接口。它使用下面的投票器来决定是否授予权限。
  • Voter: 在决定一个特定的安全对象(如HTTP请求、方法调用等)是否可以被访问时,投票器进行投票。
  • Security Interceptor: 它拦截被保护的操作请求,然后调用AccessDecisionManager来决定是否可以执行该操作。

3. 安全过滤器链(Security Filter Chain)

        Spring Security 使用一系列的过滤器来应用上述的认证和授权。这些过滤器在应用服务器处理请求的过程中工作,确保所有的HTTP请求都经过严格的安全检查。

  • SecurityContextHolder: 在整个请求处理过程中存储关于当前安全上下文的信息,如当前用户的详细信息。

4.过滤器链的加载过程

  1. 定义 FilterChainProxy

    • Spring Security 使用 FilterChainProxy 类来管理多个安全过滤器。这个代理类是配置在 Spring 容器中的一个 Bean,它包含了一个或多个过滤器链。
  2. 配置 SecurityFilterChain

    • 在 Spring Security 的配置中,可以定义一个或多个 SecurityFilterChain 对象,每个对象代表一系列应用于特定请求模式的过滤器列表。
    • 每个 SecurityFilterChain 包含了具体的过滤器实例和这些过滤器应当应用的 URL 模式。
  3. 过滤器的初始化

    • 在应用启动时,Spring 容器初始化 FilterChainProxy 并设置所有配置的 SecurityFilterChain。这些过滤器在容器中通常是作为单例存在的。
    • 过滤器链的顺序非常关键,因为它们决定了过滤器如何拦截和处理进来的 HTTP 请求。
  4. Servlet 容器集成

    • FilterChainProxy 通常注册为一个标准的 Servlet 过滤器。在大多数情况下,这是通过在 web.xml 中配置或在 Spring Boot 应用中自动配置完成的。
    • 当一个 HTTP 请求到达应用时,请求首先通过 FilterChainProxy。代理会根据请求的 URL 模式确定应该应用哪个 SecurityFilterChain
  5. 过滤器执行

    • 一旦确定了应用于当前请求的 SecurityFilterChain,请求就会依次通过链中的每个过滤器。这些过滤器执行各种安全检查,如认证、授权、CSRF保护等。
  6. 自定义和扩展

    • 开发者可以通过添加自定义过滤器到 SecurityFilterChain 来扩展或修改安全行为。这是通过在 Spring Security 的配置中定义额外的过滤器或修改现有过滤器链来实现的。

6、接口

1.认证相关接口

  1. AuthenticationManager

    • 描述: 这是 Spring Security 的核心接口之一,用于管理认证过程。
    • 主要方法: authenticate(Authentication authentication) — 尝试认证传递的 Authentication 对象,并返回一个完全填充的 Authentication 实例(包括授予的权限)如果认证成功。
  2. AuthenticationProvider

    • 描述: 用于实现具体的认证逻辑。
    • 主要方法: authenticate(Authentication authentication) — 进行实际的认证。
    • 主要方法: supports(Class<?> authentication) — 确定此 AuthenticationProvider 是否可以处理指定的认证类型。
  3. UserDetailsService

    • 描述: 加载用户特定数据的核心接口。
    • 主要方法: loadUserByUsername(String username) — 根据用户名加载用户的数据。

            当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。

  4. PasswordEncoder

    • 描述: 为存储和验证密码提供编码和解码功能。
    • 主要方法: encode(CharSequence rawPassword) — 编码原始密码。
    • 主要方法: matches(CharSequence rawPassword, String encodedPassword) — 验证原始密码和编码后的密码是否匹配。
    • @Test
      public void test01(){// 创建密码解析器BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();// 对密码进行加密String laptoy = bCryptPasswordEncoder.encode("laptoy");// 打印加密之后的数据System.out.println("加密之后数据:\t"+laptoy);//判断原字符加密后和加密之前是否匹配boolean result = bCryptPasswordEncoder.matches("laptoy", laptoy);// 打印比较结果System.out.println("比较结果:\t"+result);
      }

2.授权相关接口

  1. AccessDecisionManager

    • 描述: 负责授权决策的处理。
    • 主要方法: decide(Authentication authentication, Object secureObject, Collection<ConfigAttribute> configAttributes) — 根据提供的权限信息和安全对象(如HTTP请求),决定是否允许访问。
  2. GrantedAuthority

    • 描述: 表示授权信息的接口,如用户的安全角色。
    • 主要方法: getAuthority() — 返回表示权限的字符串。

3.过滤器相关接口和类

  1. FilterChainProxy

    • 描述: 管理多个安全过滤器链。
    • 功能: 根据请求的URL,决定哪个安全过滤器链被触发。
  2. 安全过滤器

    • 示例: UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter, JwtRequestFilter 等。
    • 功能: 执行具体的安全检查,如检查请求头中的令牌、处理用户登录请求等。

7、web权限方案

1、用户认证

设置登录的用户名和密码

1、配置文件方式

spring:security:user:name: zNuyoahpassword: zNuyoah

2、配置类方式

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String passwd = bCryptPasswordEncoder.encode("zNuyoah");auth.inMemoryAuthentication().withUser("zNuyoah").password(passwd).roles("admin");}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

3、自定义编写实现类方式(常用)


@Service
public class MyUserDetailService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 赋予当前用户权限List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");// 这里的密码可以通过注入 mapper层 从数据库中查找return new User("zNuyoah",new BCryptPasswordEncoder().encode("zNuyoah"),auths);}
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Beanpublic PasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(password());} 
}

2、整合MybatisPlus

1、POM、YML

<!--mybatis-plus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok 用来简化实体类-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8username: rootpassword: root

 2、创建数据库 security

create table users (id bigint primary key auto_increment,username varchar(20) unique not null,password varchar(100)
);
insert into users values(1,'zNuyoah','zNuyoah');

3、实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {private Integer id;private String username;private String password;
}

4、数据交互层

public interface UserMapper extends BaseMapper<Users> {
}

5、业务层

@Service
public class MyUserDetailService implements UserDetailsService {@AutowiredUserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<Users> wrapper = new QueryWrapper<>();wrapper.eq("username", username);Users users = userMapper.selectOne(wrapper);if (users == null) {throw new UsernameNotFoundException("用户名不存在!");}// 授予权限(后面有用)List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);}
}

6、配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(password());}@Beanpublic PasswordEncoder password() {return new BCryptPasswordEncoder();}
}

7、控制层

@RestController
public class TestController {@GetMapping("/hello")public String test() {return "hello World";}
}

8、启动类


@MapperScan("com.zNuyoah.mapper")
@SpringBootApplication
public class SecurityDemo1Application {public static void main(String[] args) {SpringApplication.run(SecurityDemo1Application.class, args);}
}

3、自定义用户登录页面

1、配置类配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyUserDetailService myUserDetailService;@Beanpublic PasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailService).passwordEncoder(password());}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 自定义登录页面http.formLogin().loginPage("/login.html")           // 自定义页面设置.loginProcessingUrl("/login") 		// 登录表单提交路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到// 权限认证http.authorizeRequests().antMatchers("/", "/hello").permitAll() // 设置忽略的路径.anyRequest().authenticated()       // 设置所有路径都需认证(不包括忽略的路径)// 关闭csrf保护功能        http.csrf().disable();}
}

2、resource/static/login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="/login" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br><input type="submit" value="login">
</form></body>
</html>

3、控制层

@RestController
public class TestController {@GetMapping("/hello")public String test() {return "hello World";}@GetMapping("/index")public String index() {return "hello index";}
}

4、测试

1、/hello 可以直接访问

2、可以配置自定义登录页面

3、登录成功跳转到 /index

4、用户授权

1、hasAuthority 方法

如果当前的主体具有指定的权限,则返回 true,否则返回 false

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {// 自定义登录页面http.formLogin().loginPage("/login.html")          // 自定义页面设置.loginProcessingUrl("/login")	   // 登录访问路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到// 权限认证http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径$   .antMatchers("/index").hasAuthority("admin") // 当前登录用户只有具有admin权限才能访问.anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)// 关闭csrf保护功能        http.csrf().disable();}
}

当前登录用户只有具有admin权限才能访问

.antMatchers("/index").hasAuthority("admin")

接口

@Service
public class MyUserDetailService implements UserDetailsService {@AutowiredUserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<Users> wrapper = new QueryWrapper<>();wrapper.eq("username", username);Users users = userMapper.selectOne(wrapper);if (users == null) {throw new UsernameNotFoundException("用户名不存在!");}// 授予admin权限$   List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);}
}

授予admin权限

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

若当前登录用户无对应权限,返回 403状态码

2、hasAnyAuthority 方法

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true

@Override
protected void configure(HttpSecurity http) throws Exception {// 自定义登录页面http.formLogin().loginPage("/login.html")           // 自定义页面设置.loginProcessingUrl("/login") 		// 登录访问路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径//.antMatchers("/index").hasAuthority("admin")  // 当前登录用户只有具有admin权限才能访问$   .antMatchers("/index").hasAnyAuthority("admin", "root") // 有其中一个权限就能访问.anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)http.csrf().disable();
}

3、hasRole 方法

如果当前主体具有指定的角色,则返回 true

源码

private static String hasRole(String role) {Assert.notNull(role, "role cannot be null");if (role.startsWith("ROLE_")) {throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");} else {return "hasRole('ROLE_" + role + "')";}
}    

源码显示拼接用户名时为 ROLE_xxx

return "hasRole('ROLE_" + role + "')";

配置类添加角色授权

@Override
protected void configure(HttpSecurity http) throws Exception {// 自定义登录页面http.formLogin().loginPage("/login.html")          // 自定义页面设置.loginProcessingUrl("/login") // 登录访问路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径$    .antMatchers("/index").hasRole("admin") .anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)http.csrf().disable();
}

此时接口方法授权限必须为

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");

4、hasAnyRole

表示用户具备任何一个条件都可以访问

@Override
protected void configure(HttpSecurity http) throws Exception {// 自定义登录页面http.formLogin().loginPage("/login.html")          // 自定义页面设置.loginProcessingUrl("/login") // 登录访问路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径$      .antMatchers("/index").hasAnyRole("admin", "root") .anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)http.csrf().disable();
}

5、自定义403页面

1、新建页面 /static/unauth.html

<body>
<h1>抱歉,你没有权限访问</h1>
</body>

2、配置类

@Override
protected void configure(HttpSecurity http) throws Exception {// 配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");
@Override
protected void configure(HttpSecurity http) throws Exception {// 配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");// 自定义登录页面http.formLogin().loginPage("/login.html")           // 自定义页面设置.loginProcessingUrl("/login") 		// 登录访问路径.defaultSuccessUrl("/index").permitAll() // 登录成功后跳转到http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径.antMatchers("/index").hasAuthority("admin").anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)http.csrf().disable();
}

 

3、授权

6、注解使用

1、@Secured:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀 ROLE_

主启动类开启注解功能:@EnableGlobalMethodSecurity(securedEnabled=true)

控制层

@GetMapping("/update")
@Secured({"ROLE_sale", "ROLE_xxx"})
public String update() {return "hello update";
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_sale");

 

2、@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的

roles/permissions 参数传到方法中

主启动类开启注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

@GetMapping("/update")
@PreAuthorize("hasAnyAuthority('admin')")
public String update() {return "hello update";
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

 

3、@PostAuthorize:在方法执行后再进行权限验证,适合验证带有返回值的权限

主启动类开启注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

@GetMapping("/update")
@PostAuthorize("hasAnyAuthority('xxx')")
public String update() {System.out.println("@@@@@@@@@@@@@@@@@@@@");return "hello update";
}

虽然无法访问页面,但是控制台输出了业务逻辑

4、@PostFilter:权限验证之后对返回值进行过滤


@GetMapping("/getAll")
@PreAuthorize("hasAnyAuthority('admin')")
@PostFilter("filterObject.username == 'zNuyoah'")
public List<Users> getAll() {ArrayList<Users> list = new ArrayList<>();list.add(new Users(1, "zNuyoah", "123"));list.add(new Users(2, "z_Nuyoah", "123"));System.out.println(list);return list;
}

5、@PreFilter:进入控制器之前对数据进行过滤

@GetMapping("/getAll")
@PreAuthorize("hasAnyAuthority('admin')")
@PreFilter("filterObject.username == 'laptoy'")
public List<Users> getAll(@RequestBody List<Users> list) {list.forEach(l -> {System.out.println(l.getUsername());});return list;
}

先登陆后使用PostMan进行测试

7、用户注销

1、login.html

<body>
<form action="/login" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br><input type="submit" value="login">
</form>
</body>

2、success.html

<body>
<h1>登录成功</h1>
<a href="/logout">登出</a>
</body>

3、配置类

@Override
protected void configure(HttpSecurity http) throws Exception {// 登出http.logout().logoutUrl("/logout").logoutSuccessUrl("/login.html").permitAll();// 自定义登录页面http.formLogin().loginPage("/login.html")           // 自定义页面设置.loginProcessingUrl("/login")       // 登录访问路径.defaultSuccessUrl("/success.html").permitAll() // 登录成功后跳转到http.authorizeRequests().antMatchers("/", "/login").permitAll() // 设置忽略的路径.anyRequest().authenticated()      // 设置所有路径都需认证(不包括忽略的路径)http.csrf().disable();
}

4、控制层

@RestController
public class TestController {@GetMapping("/index")public String index() {return "hello index";}
}

5、测试

1.访问 localhost:8080/login.html 登录成功后 点击 登出

2.访问 /index 需要重新进行登录验证

8、记住我功能

1、实现原理

2、认证及存放 Token 过程

代码实现

1、建表

CREATE TABLE `persistent_logins` (`username` varchar(64) NOT NULL,`series` varchar(64) NOT NULL,`token` varchar(64) NOT NULL,`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、配置类 注入数据源,配置操作数据库对象

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {...// 注入数据源@Autowiredprivate DataSource dataSource;// 配置对象@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;}...
}

3、配置类配置自动登录

protected void configure(HttpSecurity http) throws Exception {http.formLogin().and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60) // 设置有效时长 秒.userDetailsService(userDetailsService())
}

4、login.html

<form action="/login" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br>记住我:<input type="checkbox" name="remember-me"><br><input type="submit" value="login">
</form>在这里插入代码片

配置类全部代码展示

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailService myUserDetailService;// 注入数据源@Autowiredprivate DataSource dataSource;// 配置对象@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;}@Beanpublic PasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailService).passwordEncoder(password());}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 登出http.logout().logoutUrl("/logout").logoutSuccessUrl("/login.html").permitAll();// 配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");// 自定义登录页面http.formLogin().loginPage("/login.html")                        // 自定义页面设置.loginProcessingUrl("/login")                    // 登录访问路径.defaultSuccessUrl("/success.html").permitAll(); // 登录成功后跳转到// 权限认证http.authorizeRequests().antMatchers("/", "/hello", "/login").permitAll() // 设置忽略的路径.anyRequest().authenticated();       // 设置所有路径都需认证(不包括忽略的路径)// 记住我http.rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60) // 设置有效时长 秒.userDetailsService(userDetailsService());// 关闭csrf保护功能http.csrf().disable();}
}

9、CSRF

        跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session

riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非

本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,

CSRF 利用的是网站对用户网页浏览器的信任。
 

        跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾

经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏

览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身

份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是

用户自愿发出的。
 

        从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,

Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。
 

1、登录页面添加隐藏域

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

2、配置类开启CSRF

// http.csrf().disable();

8、微服务权限方案

1、认证授权过程分析

如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于token 的形式进行授权

与认证

        1、用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值

        2、以 用户名为 key,权限列表为 value 的形式存入 redis 缓存中

        3、根据用户名相关信息 生成 token返回

        4、浏览器将 token记录到 cookie 中,每次调用 api 接口都默认将 token 携带 到 header 请求头中

        5、Spring-security 解析 header 头获取 token 信息,解析 token 获取当前 用户名,根据用户

名就可以从 redis 中获取权限列表,

        6、这样 Spring-security 就能够判断当前 请求是否有权限访问

2、数据模型

执行资料里的 SQL脚本

3、搭建项目工程

1、新建Springboot父工程 acl_parent (2.2.1.RELEASE)

●删除 src目录

●POM添加 <packaging>pom</packaging>

<?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><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.laptoy</groupId><artifactId>acl_parent</artifactId><version>0.0.1-SNAPSHOT</version><packaging>pom</packaging><name>acl_parent</name><description>acl_parent</description><modules><module>common</module><module>infrastructure</module><module>service</module></modules><properties><java.version>1.8</java.version><mybatis-plus.version>3.0.5</mybatis-plus.version><velocity.version>2.0</velocity.version><swagger.version>2.7.0</swagger.version><jwt.version>0.7.0</jwt.version><fastjson.version>1.2.28</fastjson.version><gson.version>2.8.2</gson.version><json.version>20170516</json.version><cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version></properties><dependencyManagement><dependencies><!--Spring Cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus 持久层--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>${velocity.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>${json.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2、在父工程创建子模块(以下都是maven工程)

1)common (删除 src目录 POM添加 <packaging>pom</packaging>)

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>acl_parent</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>common</artifactId><packaging>pom</packaging><modules><module>service_base</module><module>spring_security</module></modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><scope>provided </scope></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided </scope></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided </scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies></project>

●service_base:工具类

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>common</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service_base</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

●spring_security:权限配置

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>common</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version><relativePath>../../pom.xml</relativePath></parent><modelVersion>4.0.0</modelVersion><artifactId>spring_security</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>com.laptoy</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!-- Spring Security依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency></dependencies></project>

2)infrastructure(删除 src目录 POM添加 <packaging>pom</packaging>)

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>acl_parent</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>infrastructure</artifactId><packaging>pom</packaging><modules><module>api_gateway</module></modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

●api_gateway:网关

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>infrastructure </artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version><relativePath>../../pom.xml</relativePath></parent><modelVersion>4.0.0</modelVersion><artifactId>api_gateway</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>com.laptoy</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies></project>

3)service(删除 src目录 POM添加 <packaging>pom</packaging>)

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>acl_parent</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service</artifactId><packaging>pom</packaging><modules><module>service_acl</module></modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>com.laptoy</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--服务注册--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency></dependencies><build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources></build></project>

●service_acl:权限管理微服务模块

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>service</artifactId><groupId>com.laptoy</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service_acl</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>com.laptoy</groupId><artifactId>spring_security</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency></dependencies></project>

4、security 工具类

1、密码处理工具类

package com.laptoy.security.security;import com.laptoy.utils.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;/*** 密码处理工具类*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder() {this(-1);}public DefaultPasswordEncoder(int strength) {}// 进行 MD5 加密@Overridepublic String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}/*** 传入的密码 与 数据库密码进行比较** @param rawPassword     传入的密码* @param encodedPassword 数据库中的密码*/@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}
}

2、Token工具类

package com.laptoy.security.security;import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;public class TokenManager {// token 有效事件private long tokenExpiration = 24 * 60 * 60 * 1000;// 编码密钥用于签名private String tokenSignKey = "123456";// 1、根据用户名生成tokenpublic String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}// 2、根据token字符串得到用户信息public String getUserInfoFormToken(String token) {String userInfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userInfo;}// 3、删除tokenpublic void removeToken(String token) {}
}

3、退出处理器

package com.laptoy.security.security;import com.laptoy.utils.utils.R;
import com.laptoy.utils.utils.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class TokenLogoutHandler implements LogoutHandler {private TokenManager tokenManager;@Autowiredprivate RedisTemplate redisTemplate;//构造方法public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {// 从header获取tokenString token = request.getHeader("token");if (token != null) {// 从header移除tokentokenManager.removeToken(token);// 从token获取用户名String username = tokenManager.getUserInfoFormToken(token);// 从redis移除tokenredisTemplate.delete(username);}ResponseUtil.out(response, R.ok());}
}

4、未授权统一处理类

public class UnAuthEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}

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

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

相关文章

基于JAVA的微信小程序二手车交易平台(源码)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

uniapp 安卓证书导出成cer文件 查看公钥

// your_alias 换成 证书详情中的别名&#xff0c;your_keystore.keystore 改成自己的证书文件名 keytool -export -alias your_alias -file certificate.cer -keystore your_keystore.keystore双击生成的cer文件 可以查看到证书的详细信息 其中就包括证书的公钥

安捷伦E8363B详情资料e8363b矢量网络分析仪E8363B 40G

E8363B E8363B E8363B 品牌&#xff1a;Agilent 型号&#xff1a;E8363B PNA系列网络分析仪 频率范围&#xff1a;10MHz~40GHz 动态范围&#xff1a;123dB 主要特性与技术指标 #110 dB的动态范围&#xff0c;<0.006 dB的迹线噪声 #<26微秒/点的测量速度&#xff0c;32个…

定期更新与维护:技术与生活的同步律动

在这个数字化时代&#xff0c;科技的温暖之光照进了盲人朋友们的日常生活中&#xff0c;特别是那些辅助出行的应用程序&#xff0c;它们如同贴心的向导&#xff0c;引领着用户穿越城市的喧嚣与宁静。然而&#xff0c;要确保这些应用始终能够高效、安全地服务于盲人用户&#xf…

令牌桶算法:如何优雅地处理突发流量?

令牌桶算法的介绍 在网络流量控制和请求限流中&#xff0c;令牌桶算法是一种常用的策略。那么&#xff0c;令牌桶算法到底是什么呢&#xff1f;它的工作原理又是怎样的呢&#xff1f;让我们一起来探索一下。 令牌桶算法&#xff0c;顾名思义&#xff0c;就是有一个存放令牌的…

【VTKExamples::Rendering】第六期 TestFlatVersusGround

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例TestFlatVersusGround,希望对各位小伙伴有所帮助! 感悟:自身优秀很重要,让别人觉得你很优秀更重要! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! …

云计算第十二课

安装虚拟机 第一步新建虚拟机 选择自定义安装 下一步 选择稍后安装操作系统 选择系统类型和版本 选择虚拟机文件路径&#xff08;建议每台虚拟机单独存放并且路径不要有中文&#xff09;点击下一步 选择bios下一步 选择虚拟机处理器内核数量 默认硬盘或者自行调大硬盘 选择虚…

【Java】:向上转型、向下转型和ClassCastException异常

目录 先用一个生动形象的例子来解释向上转型和向下转型 向上转型&#xff08;Upcasting&#xff09; 向下转型&#xff08;Downcasting&#xff09; 向上转型 概念 例子 发生向上转型的情况 1.子类对象赋值给父类引用 2.方法参数传递 3.返回值 向下转型 概念 注意…

Electron学习笔记(一)

文章目录 相关笔记笔记说明 一、轻松入门 1、搭建开发环境2、创建窗口界面3、调试主进程 二、主进程和渲染进程1、进程互访2、渲染进程访问主进程类型3、渲染进程访问主进程自定义内容4、渲染进程向主进程发送消息5、主进程向渲染进程发送消息6、多个窗口的渲染进程接收主进程发…

如何取消格式化SD卡并恢复丢失的数据?

在相机中格式化SD卡后&#xff0c;您将丢失卡上的所有文件。如果有恢复形成操作的选项&#xff0c;您可以轻松取回文件。然而&#xff0c;相机或任何其他设备中没有这样的选项。它无法直接取消格式化相机SD卡&#xff0c;但您仍然可以从格式化的SD卡中恢复文件。 为什么格式化后…

pycharm本地文件更新至虚拟机

tools–>deployment–>configuration root path的路径要跟远程路径对齐&#xff0c;方便后续运行 mapping映射&#xff0c;本地路径和远程路径 点击Browse 可以在右侧同步查看更新情况

Jmeter使用While控制器

1.前言 对于性能测试场景中&#xff0c;需要用”执行某个事物&#xff0c;直到一个条件停止“的概念时&#xff0c;While控制器控制器无疑是首选&#xff0c;但是在编写脚本时&#xff0c;经常会出现推出循环异常&#xff0c;获取参数异常等问题&#xff0c;下面总结两种常用的…

一文详解多模态智能体(LMAs)最新进展(核心组件/分类/评估/应用)

大型多模态智能体 文章链接&#xff1a;https://arxiv.org/pdf/2402.15116 github地址&#xff1a;https://github.com/jun0wanan/awesome-large-multimodal-agents 大语言模型&#xff08;LLMs&#xff09;在为基于文本的AI智能体提供动力方面取得了卓越的表现&#xff0c;赋…

微信小程序 17:小程序使用 npm 包和组件应用

目前&#xff0c;小程序中已经支持实用 npm 安装第三方包&#xff0c;从而提高小程序的开发效率&#xff0c;但是在小程序中使用 npm 包有三个限制&#xff1a; 不支持 Node.js内置库的包不支持依赖于浏览器内置对象的包不支持依赖于 C插件的包 Vant Weapp Vant Weapp是有赞…

山东济南最出名的起名大师颜廷利教授:当代中国教育界的泰斗

山东济南最出名的起名大师颜廷利教授&#xff1a;当代中国教育界的泰斗。 四川成都、重庆、香港、台湾、辽宁沈阳、大连、湖南长沙、宁夏第一起名大师的老师颜廷利教授被誉为世界上思想境界最高的人之一。 北京、上海、天津、浙江杭州、江苏南京、宁夏银川最权威起名大师的老师…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《规模化屋顶光伏接入配电网的建设决策》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Scratch四级:第08讲 排序算法

第08讲 排序算法 教练&#xff1a;老马的程序人生 微信&#xff1a;ProgrammingAssistant 博客&#xff1a;https://lsgogroup.blog.csdn.net/ 讲课目录 常考的排序算法项目制作&#xff1a;“三个数排序”项目制作&#xff1a;“成绩查询”项目制作&#xff1a;“排序”项目制…

【机器学习】LoFTR:革命性图像特征批评技术等领跑者

LoFTR&#xff1a;革命性图像特征匹配技术的领跑者 一、引言二、LoFTR技术的创新之处三、LoFTR技术的实现原理四、LoFTR技术的代码实例五、结语 一、引言 在3D计算机视觉领域&#xff0c;图像特征匹配技术一直是研究的热点和难点。随着技术的不断发展&#xff0c;传统的特征检…

语言:C#

一、VSCode生成exe 二、

java sql中 大于 小于 大于等于 小于等于 代替符号

在写java时sql会经常会忘记大于小于号的表示方法导致无法运行&#xff0c;总结一下 第一种方法&#xff1a; < &#xff1a;< < &#xff1a; < &#xff1a;> &#xff1a; > sql如下&#xff1a; create_at > #{startTime} and create_at < #{end…