【SpringSecurity】认证授权框架——SpringSecurity使用方法

2023/6/5 20:52:58

【SpringSecurity】认证授权框架——SpringSecurity使用方法

文章目录

  • 【SpringSecurity】认证授权框架——SpringSecurity使用方法
    • 1. 概述
    • 2. 准备工作
      • 2.1 引依赖
      • 2.2 测试
    • 3. 认证
      • 3.1 认证流程
      • 3.2 登录校验问题
      • 3.3 实现
        • 3.3.1 实现UserDetailsService接口
        • 3.3.2 密码存储和校验
        • 3.3.3 自定义登录接口
          • 3.3.3.1 配置
          • 3.3.3.2 定义登录方法
        • 3.3.4 自定义认证过滤器
          • 3.3.4.1 确定过滤器顺序
        • 3.3.5 自定义退出登录接口
    • 4. 授权
      • 4.1 授权流程
      • 4.2 实现
        • 4.2.1 开启全局权限功能
        • 4.2.2 封装权限信息
      • 4.3 自定义权限校验方法
      • 4.4 基于配置的权限控制
    • 5. 自定义失败处理
      • 5.1 认证失败处理器
      • 5.2 权限不足处理器
      • 5.3 配置
    • 6. 跨域问题

1. 概述

Spring Security是一个框架,提供 认证(authentication)、授权(authorization)和保护,以抵御常见的攻击。它对保护命令式和响应式应用程序有一流的支持,是保护基于Spring的应用程序的事实标准。它还提供了与其他库的集成,以简化其使用。

SpringSecurity的详细使用方法可见:Spring Security 中文文档 :: Spring Security Reference (springdoc.cn)

写这篇文章主要是为了复习SpringSecurity的相关知识,在这里也向大家推荐b站up主 三更草堂 ,他讲的SpringSecurity课程非常棒!


2. 准备工作

2.1 引依赖

创建springboot工程中引入这些依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.0</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--主要是这个依赖-->
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
     </dependency>
</dependencies>

2.2 测试

引入好依赖之后就可以启动应用了,我们访问应用的任何一个接口都会跳转到SpringSecurity的一个默认登录页面(如下所示):

image-20230330210017463

SpringSecurity提供了一个默认的登录接口 /login 和退出接口 /logout 。登录接口的默认用户名为 user ,而密码则打印在了应用的控制台:

image-20230330210243936

使用默认的用户名和控制台上的密码就能够登录系统访问接口了。在正常情况下我们需要禁用这个接口,使用我们自定义的接口。


3. 认证

SpringSecurity的认证机制有很多,比如:

  • Username and Password:使用用户名/密码进行认证
  • OAauth 2.0 Login:使用OpenID Connect 和非标准的OAuth 2.0登录(即GitHub)的OAuth 2.0登录。

我们主要了解第一种方式。SpringSecurity认证通过过滤器链实现:

image-20230330212422514

SpringSecurity的过滤器链包含15个过滤器,上图只展示了较为核心的过滤器。

  • SecurityContextPersistenceFilter:过滤器链的入口和出口,保存和清除 SecurityContextHolder 中的 SecurityContext
  • UsernamePasswordAuthenticationFilter:负责收集我们在登陆页面输入的用户名和密码并封装成Authentication对象。
  • ExceptionTranslationFilter:只处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException ,其他异常继续抛出。
  • FilterSecurityInterceptor:负责权限校验的过滤器。

3.1 认证流程

image-20230330212244925

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ailkTJHA-1680426554649)(null)]

注释:

  • Authentication:是一个接口,它的实现类表示当前访问系统的用户,封装了用户相关信息。
  • AuthenticationManager:也是一个接口,声明了认证Authentication的方法
  • UserDetailsService:还是一个接口,加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
  • UserDetails:接口,提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

3.2 登录校验问题

登录流程

  1. 自定义登录接口替代原有登录接口。

  2. 自定义实现UserDetailService接口的类A,替代原有实现类。

  3. 在类A中中查询数据库进行认证校验。

  4. 校验通过则使用jwt工具类生成token,token生成规则有两种:

    4.1 使用用户id生成token,其余用户信息存入redis。

    4.2 使用用户基本信息的json串(脱敏)生成token,服务端不必存储用户信息。

校验流程

  1. 自定义jwt认证过滤器。

  2. 解析token。

    2.1 解析获得用户id,根据用户id从redis中获得用户信息,存入SecurityContextHolder。

    2.2 解析获得用户信息json串,将其反序列化为用户对象,存入SecurityContextHolder。

在这里我们采用第一种方案,使用redis。


3.3 实现

3.3.1 实现UserDetailsService接口

创建UserDetailsService接口的实现类,在其中对用户进行认证和授权。

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //认证
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);
        User user = userMapper.selectOne(queryWrapper);
        if (Objects.isNull(user)) {
            throw new RuntimeException("用户名或密码错误");
        }

        //todo 查询对应的权限信息

        //把数据封装成UserDetails返回
        UserDetails userDetails = org.springframework.security.core.userdetails.User.withUsername(user.getUserName())
                .password(user.getPassword())
                .authorities(new String[]{})
                .build();
        return userDetails;
    }
}

除了使用security提供的生成UserDetails对象的方法外,我们还可以选择自定义。

我们新创建一个类 LoginUser 实现 UserDetails 接口。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

使用我们自定义的类去创建UserDetails对象。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //认证
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);
    User user = userMapper.selectOne(queryWrapper);
    if (Objects.isNull(user)) {
        throw new RuntimeException("用户名或密码错误");
    }

    //todo 查询对应的权限信息

    //把数据封装成UserDetails返回
    return new LoginUser(user);
}

3.3.2 密码存储和校验

我们不会在数据库中使用明文的方式存储密码,我们需要存储的是加密之后的密码。

SpringSecurity默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。这种方式不太行,所以我们需要使用BCryptPasswordEncoder去替代原来的PasswordEncoder。

我们只需要定义一个SpringSecurity的配置类,并且让这个类继承 WebSecurityConfigurerAdapter

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //创建 BCryptPasswordEncoder 对象并注入容器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3.3.3 自定义登录接口

创建一个控制器 LoginController ,在其中定义一个登录接口:

@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user) {
        return userService.login(user);
    }
}
3.3.3.1 配置

我们需要让SpringSecurity对自定义的接口放行,让用户不用登陆也可以访问。然后在 login(user) 中调用AuthenticationManager的authenticate方法来进行用户认证,所以需要在SpringSecurity配置类中进行以下两点配置:

  1. 对自定义登录接口放行
  2. AuthenticationManager 对象注入容器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //创建 BCryptPasswordEncoder 对象并注入容器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //注入AuthenticationManager对象
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //对登录接口放行
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问(未登录时可以访问,登陆后不能访问)
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }
}

3.3.3.2 定义登录方法

在用户服务接口的实现类中定义 login(user) 方法:

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Override
    public ResponseResult login(User user) {
        //调用 AuthenticationManager 对象的 authenticate() 方法进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
        Authentication authenticate = null;
        try {
            authenticate = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        if (authenticate == null) {
            throw new RuntimeException("登陆失败");
        }
        //如果认证未通过,则给出对应提示
        //通过,则使用userid生成一个jwt,jwt存入ResponseResult
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        Map<String, String> map = new HashMap<>();
        map.put("token", jwt);
        //把用户信息存入redis
        stringRedisTemplate.opsForValue().set("login:" + userId, JSON.toJSONString(loginUser));
        return new ResponseResult(200, "登录成功", map);
    }
}

3.3.4 自定义认证过滤器

我们需要一个认证过滤器,作用是取出每次请求的请求头,看它是否携带 token 。再对 token 进行校验,判断用户的登录状态。

@Component
public class JwtAuthenticactionTokenFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            //没有token
            //放行,让后面的认证过滤器去拒绝
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //拼接redis的key
        String key = "login:" + userId;
        String userJson = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(userJson)) {
            throw new RuntimeException("用户未登录");
        }
        LoginUser loginUser = JSON.parseObject(userJson, LoginUser.class);
        //存入SecurityContextHolder
        //todo 还未授权
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);

    }
}

3.3.4.1 确定过滤器顺序

定义了认证过滤器后,我们还需要确定过滤器的执行顺序,我们必须保证这个过滤器的执行在Security的认证过滤器之前,所以我们把他放在 UsernamePasswordAuthenticationFilter 之前比较合适。

找到之前的配置类,从容器中取出自定义的 JwtAuthenticactionTokenFilter 过滤器并在 configure() 方法中加入最后一行代码。

@Autowired
private JwtAuthenticactionTokenFilter jwtAuthenticactionTokenFilter;

protected void configure(HttpSecurity http) throws Exception {
    http
            //关闭csrf
            .csrf().disable()
            //不通过Session获取SecurityContext
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // 对于登录接口 允许匿名访问(未登录时可以访问,登陆后不能访问)
            .antMatchers("/user/login").anonymous()
            // 除上面外的所有请求全部需要鉴权认证
            .anyRequest().authenticated();

    //把自定义认证过滤器放在 UsernamePasswordAuthenticationFilter 过滤波器前面
    http.addFilterBefore(jwtAuthenticactionTokenFilter, UsernamePasswordAuthenticationFilter.class);
}

3.3.5 自定义退出登录接口

退出流程:

  1. 通过 SecurityContextHolder 获得用户id
  2. 利用用户id凭借redis的key值
  3. 通过key值把redis中的用户信息删除

首先定义一个退出接口:

@RequestMapping("/user/logout")
public ResponseResult logout() {
    return userService.logout();
}

在实现类中定义 logout() 方法:

@Override
public ResponseResult logout() {
    //获取SecurityContextHolder中的用户id
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    Long id = loginUser.getUser().getId();
    //删除redis中的值
    stringRedisTemplate.delete("login:" + id);
    return new ResponseResult(200, "退出成功");
}

4. 授权

4.1 授权流程

在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。


4.2 实现

4.2.1 开启全局权限功能

@EnableGlobalMethodSecurity(prePostEnabled = true)

开启功能之后,我们就可以在接口方法上面加上 @PreAuthorize 注解了。

@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')")
public String hello() {
    return "hello";
}

4.2.2 封装权限信息

权限的封装是在登录流程时发生的,所以我们在登录方法中把权限信息封装进入 UserDetails 的实现类中:

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    //权限字符串集合
    private List<String> permissions;

    //权限集合,不用序列化到redis
    @JSONField(serialize = false)
    private Set<SimpleGrantedAuthority> authorities;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    //将权限字符串集合封装成权限集合
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities == null) {
            authorities = permissions.stream()
                    .map(item -> new SimpleGrantedAuthority(item))
                    .collect(Collectors.toSet());
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

修改完毕,我们先在登录校验方法 loadUserByUsername(String username) 中查询数据库得到权限字符串集合:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //认证
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(StringUtils.isNotBlank(username), User::getUserName, username);
    User user = userMapper.selectOne(queryWrapper);
    if (Objects.isNull(user)) {
        throw new RuntimeException("用户名或密码错误!");
    }

    //查询对应的权限信息
    List<String> list = menuMapper.selectPermsByUserId(user.getId());
    return new LoginUser(user, list);
}

然后在自定义过滤器中补充权限设置:

UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

4.3 自定义权限校验方法

Security提供的权限校验方法比较单一,且不灵活。所以我们可以自定义权限校验方法并且在注解中使用我们的方法。

1)自定义权限校验方法

创建一个类并注入容器中,在其中编写我们的校验方法:

@Component(value = "ex")
public class ExpressionRoot {

    public boolean hasAuthority(String authority) {
        //获取当前用户的权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        List<String> permissions = loginUser.getPermissions();
        //判断用户是否拥有访问权限
        return permissions.contains(authority);
    }
}

2)在注解中使用我们的方法

@RequestMapping("/hello")
//@PreAuthorize("hasAuthority('system:dept:list')")
@PreAuthorize("@ex.hasAuthority('system:dept:list')")
public String hello() {
    return "hello";
}

4.4 基于配置的权限控制

我们上面的示例都是基于注解的权限控制,我们也可以通过配置来进行权限的控制,如下所示:

 http.authorizeRequests()
     // 对于登录接口 允许匿名访问
     .antMatchers("/user/login").anonymous()
     //基于配置的权限控制
     .antMatchers("/testCors").hasAuthority("system:dept:list222")
     // 除上面外的所有请求全部需要鉴权认证
     .anyRequest().authenticated();

5. 自定义失败处理

我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

​ 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

​ 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。


5.1 认证失败处理器

自定义认证失败处理器:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请重新登录");
        String json = JSON.toJSONString(result);
        //处理异常
        WebUtils.renderString(httpServletResponse, json);
    }
}

WebUtils 工具类中对响应进行封装,具体方法如下:

public static String renderString(HttpServletResponse response, String string) {
    try
    {
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(string);
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
    return null;
}

5.2 权限不足处理器

自定义权限不足处理器:

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        //处理异常
        WebUtils.renderString(httpServletResponse, json);
    }
}

5.3 配置

我们将这两个处理器注入容器,再将其配置到Security中:

@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;

@Autowired
private AccessDeniedHandler accessDeniedHandler;

//对登录接口放行
@Override
protected void configure(HttpSecurity http) throws Exception {
    //省略其他配置......
    
    //配置异常处理器
    http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)//认证失败处理器
            .accessDeniedHandler(accessDeniedHandler);//授权失败处理器
}

6. 跨域问题

只有浏览器和服务端的之间的请求有跨域问题,服务端和服务端之间是不存在跨域问题的。

我们首先在我们的springboot项目中进行跨域配置:

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

由于我们的资源都会收到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问。

然后在security的配置类中添加跨域配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    //省略其他配置......
    
    //允许跨域
    http.cors();
}

http://www.jnnr.cn/a/369293.html

相关文章

蓝桥ROS→f1tenth和PID绕圈←外传

#勤写标兵挑战赛# 本节实现后&#xff0c;效果如下&#xff1a; 主要有3个点&#xff1a; 车体模型的简单配置赛道环境的修改算法参数的简要解释↓开启本节之前&#xff0c;需要完成↓&#xff1a; 一键配置←f1tenth和PID绕圈 (*Φ皿Φ*) 车体模型&#xff1a; 原有的车…

hexo stellar主题站点信息设置(title/avatar/url)

在搭建了hexo博客以后&#xff0c;想给博客加个头像&#xff0c;这是我最后的效果&#xff0c;但是一开始我的hexo博客一直不显示头像&#xff0c;在看了网上的很多教程并且配置数次以后&#xff0c;终于成功&#xff0c;整理一下。 官网的配置教程是这样说的&#xff1a; 设置…

ssm入门

文章目录1.介绍ssm2.Spring篇基础内容&#x1fa85;什么是IOC&#xff08;控制反转&#xff09;Spring和IOC之间的关系IOC容器的作用以及内部存放IoC入门案例&#x1f4ec;DI&#xff08;Dependency Injection&#xff09;依赖注入依赖注入的概念IOC容器中哪些bean之间要建立依…

【CSS系列】第二章 · CSS选择器

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

kafka经典面试题

这里写目录标题1.生产者1.1 生产者发送原理1.2 分区有什么好处?1.3 生产消息时, 是如何决定消息落盘到哪个分区的?1.4 生产者如何提高吞吐量1.5 如何保证生产的消息不丢失(能成功落盘)1.6 ack为-1, 就肯定不会丢失数据吗?1.7 生产者重复发送消息的场景1.8 生产者如何保证数据…

NIO 缓冲区

摘要 Java提供了NIO操作的API&#xff0c;但真正处理NIO流&#xff0c;经常会出现如下代码&#xff1a; SocketChannel channel (SocketChannel) key.channel();ByteBuffer buffer ByteBuffer.allocate(1024);while (channel.read(buffer)!-1){//复位&#xff0c;转化为读模式…

前端-微前端

实现方式&#xff1a; &#xff08;1&#xff09;微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web应用的技术手段及方法策略。 &#xff08;2&#xff09;微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用&#xff0c;这样才能确保微应用真正具…

windows使用YOLOv8训练自己的模型(0基础保姆级教学)

目录 前言 一、使用labelimg制作数据集 1.1、下载labelimg 1.2、安装库并启动labelimg 1.4、制作YOLO数据集 二、使用YOLOv8训练模型 2.1、下载库——ultralytics &#xff08;记得换源&#xff09; 2.2、数据模板下载 2.3、开始训练 1、启动train.py&#xff0c;进行…

重磅音视频开发资料库!!!

为了更好的阅读请前往GitBook 一、前言 这里整理有着丰富的音视频开发的学习资源、开发工具、优秀书籍、教程和开源项目&#xff0c;旨在帮助开发者和爱好者更好地学习、实践和工作。而下图是开发处理的过程&#xff1a; 二、学习技能 语言重要度作用C/C★★★★★作为底层开…

3.30--Redis之常用数据结构--跳表之总结篇(总结篇)------加油呀

跳表 跳表是在链表基础上改进过来的&#xff0c;实现了一种「多层」的有序链表&#xff0c;这样的好处是能快读定位数据 优势是能支持平均 O(logN) 复杂度的节点查找。 只有 Zset 对象的底层实现用到了跳表,zset 结构体里有两个数据结构&#xff1a;一个是跳表&#xff0c;一个…

FasterNet实战:使用FasterNet实现图像分类任务(二)

文章目录训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整算法设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法运行以及结果查看测试热力图可视化展示一…

【数据库管理】⑤归档日志Archive Log

1.日志归档的概述和用途 日志归档是指将数据库的归档日志文件保存到指定的位置&#xff0c;以便在需要时进行恢复和回滚操作。在Oracle数据库中&#xff0c;日志归档是一种重要的备份和恢复策略&#xff0c;可以保证数据库的数据完整性和可靠性。 日志归档的主要用途包括&#…

CVE-2020-1948 Apache dubbo远程命令执行漏洞

预备知识 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架&#xff0c;使得应用可通过高性能的RPC实现服务的输出和输入功能&#xff0c;可以和Spring框架无缝集成。 RPC是远程过程调用的简称&#xff0c;广泛应用在大规模分布式应用中&#xff0c;作用是有助于系统的垂直…

Netty组件

Netty组件 EventLoop 事件循环对象 EventLoop本质是一个单线程执行器(同时维护了一个Selector&#xff0c;里面有run方法处理Channel上源源不断的io事件 它的继承关系比较复杂 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法另一条线是继承…

R 语言基础

R 语言基础 一门新的语言学习一般是从输出 “Hello, World!” 程序开始&#xff0c;R 语言的 “Hello, World!” 程序代码如下&#xff1a; ## 实例&#xff08;helloworld.R&#xff09;myString <- "Hello, World!"print ( myString )以上实例将字符串 “Hell…

SQL注入进阶练习(二)常见绕过手段、防御的解决方案

常见绕过手段、防御的解决方案1.常用SQL注入绕过手段1.1 注释符绕过1.2 大小写绕过1.3 内联注释绕过1.4 双写关键字绕过1.5 特殊编码绕过1.6 空格过滤绕过1.7 过滤 or and xor (异或) not 绕过1.8 过滤等号绕过1.9 过滤大小于号绕过1.10 过滤引号绕过1.11 过滤逗号绕过1.12 过滤…

★LDO相关

1.型号 TPS79501 TPS79301 2.PSRR值&#xff0c;频率 TPS795_50dB&#xff0c;10kHz TPS793_70dB&#xff0c;10kHz 电源抑制比&#xff1a;供电电压纹波对输出电压影响&#xff0c;值越高越好&#xff08;某个频段的AC从输入到输出的衰减程度&#xff0c;衰减越高&#x…

提升集群吞吐量与稳定性的秘诀: Dubbo 自适应负载均衡与限流策略实现解析

作者&#xff1a;刘泉禄 整体介绍 本文所说的“柔性服务”主要是指 consumer 端的负载均衡和 provider 端的限流两个功能。在之前的 Dubbo 版本中&#xff0c;负载均衡部分更多的考虑的是公平性原则&#xff0c;即 consumer 端尽可能平等的从 provider 中作出选择&#xff0c;…

笔记本电脑自带录屏在哪?一步教您找到

案例&#xff1a;怎么找到笔记本电脑上的自带录屏功能&#xff1f; “从网上了解到笔记本电脑有自带的录屏功能&#xff0c;但我不知道笔记本自带的录屏叫什么名字&#xff0c;也不知道笔记本自带录屏在哪。有没有小伙伴知道&#xff1f;” 随着科技的不断进步&#xff0c;越…

【面试题】简单的说说对原型链的了解

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 前言 作为Javascript的基础之一&#xff0c;原型一直贯穿我们的JS代码并且成为面试的常考…