使用JWT的SpringSecurity实现前后端分离

news2024/11/14 6:27:00

1. SpringSecurity完成前后端完全分离

分析:

前后端分离:响应的数据必须为JSON数据,之前响应的是网页

需要修改的代码有:

  1. 登录成功需要返回json数据
  2. 登录失败需要返回json数据
  3. 权限不足时返回json数据
  4. 未登录访问资源返回json数据

1.1 登录成功需要返回json数据

第一种方案:基于redis 实现session共享

在这里插入图片描述

该方案的缺点:

  1. redis压力太大
  2. 项目依赖于第三方组件

第二种方案:基于jwt【采用】

jwt帮你生成唯一标志,而且校验唯一标志。信息存放在jwt中,同时可以从jwt中获取

在这里插入图片描述

1.2 JWT的概述

1.2.1 什么是JWT

Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
官网: https://jwt.io/introduction/

JWT就是token的一种具体实现方式,本质就是一个字符串,它将用户信息保存到一个json字符串中,然后进行编码后得到一个JWT token ,并且这个JWT token 带有签名信息,接收后可以校验是否被篡改。所以可以用于在各方之间安全地将信息作为JSON对象传输。

1.2.2 前后端完全分离认证问题

互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录
时间等等。
3、服务器向用户返回一个session_id,写入用户的Cookie。
4、用户随后的每一次请求,都会通过Cookie,将session_id传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是前后端分离的服务导向架构,就要求session 数据共享,每台服务器都能够读取session,
举例来说,A网站和B网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大[]。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT就是这种方案的一个代表。

JWT:影响了网络宽带

1.2.3 JWT的原理

JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,就像下面这样。

{
“姓名”:“张三”,
“角色”:“管理员”,
“到期时间”:“2022年8月1日0点0分”

}
以后,用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

1.2.4 JWT的数据结构

实际的 JWT大概就像下面这样。

在这里插入图片描述

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT内部是没有换行的,这里只是为了便于展示,将它写成了几行

JWT的三个部分依次如下:

  1. Header(头部)
  2. Payload(负载,载荷)
  3. Signature(签名)

写成一行,就是下面的样子。

Header.Payload.Signature

1.2.4.1 Header
{
  "alg": "HS256",
  "typ": "JWT"
}

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存 。

1.2.4.2 Payload

Payload 部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。

iss (issuer):签发人
exp (expiration time):过期时间

sub (subject):主题 
aud (audience):受众 

nbf (Not Before):生效时间

iat (lssued At):签发时间

jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义自己的字段,下面就是一个例子。

{

"sub": "1234567890",
"name" : "John Doe",

“userid”:2

 "admin": true
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把==秘密信息【密码】==放在这个部分。这个JSON 对象也要使用Base64URL 算法转成字符串。

JWT只是适合在网络中传输一些非敏感的信息

1.2.4.3 Signature

Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
base64UrlEncode(header) + ".”"+base64UrlEncode(payload),
secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

1.2.5 JWT的使用方式

客户端收到服务器返回的JWT,可以储存在Cookie里面,也可以储存在 localStorage、SessionStorage
此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTPs请求的头信息Authorization字段里面。

客户端收到服务器返回的JWT,可以储存在Cookie里面,也可以储存在 localStorage。SessionStorage
此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization字段里面。

步骤

1. 引入jar

 <!--引入jwt的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

2. 创建jwt的工具类

  • 通过jwt创建token令牌

        private static  String key="layZhang";
    
        //创建token
        public static String createToken(Map<String,Object> map){
            //设置头部信息
            Map<String,Object> head=new HashMap<>();
            head.put("alg","HS256");
            head.put("typ","JWT");
            //设置发布日期
            Date date=new Date();
            //设置过期时间
            Calendar instance = Calendar.getInstance();//获取当前时间
            instance.set(Calendar.SECOND,7200);//在当前时间的基础上添加两个小时
            Date time = instance.getTime();//得到Date类型的时间
    
            //创建token
            String token = JWT.create()
                    .withHeader(head)//设置头部信息
                    .withIssuedAt(date)//设置发布时间
                    .withExpiresAt(time)//设置过期时间
                    .withClaim("userInfo", map)//设置个人信息
                    .sign(Algorithm.HMAC256(key));//签名
    
            return token;
        }
    
  • 校验token

     //校验token
        public static boolean verify(String token){
            Verification require = JWT.require(Algorithm.HMAC256(key));
            try {
                require.build().verify(token);
                return true;
            }catch (Exception e){
                System.out.println("token错误");
                return false;
            }
        }
    

    JWT.require()方法: 这是JWT库中的一个方法,用于创建一个Verification【验证】对象,该对象用于配置和执行JWT的验证过程

    Algorithm.HMAC256(key): 这里指定了用于签名JWT的算法和密钥。HMAC256是一种基于哈希的消息认证码(HMAC)算法,它使用SHA-256哈希函数。key是一个密钥,用于生成和验证JWT的签名。这个密钥在生成JWT令牌和验证JWT令牌时必须相同。

    require.build(): 这个方法调用会基于之前通过JWT.require(...)方法配置的验证要求,构建一个JWTVerifier实例。这个实例包含了所有必要的验证配置(如签名算法和密钥)。

    .verify(token): 使用构建好的JWTVerifier实例来验证给定的token。如果token是有效的(即,它是由指定的密钥和算法签名的,且未被篡改),则此方法将成功执行。如果token无效,将抛出异常。

    综上所述,verify方法通过指定的密钥和算法验证给定的JWT令牌是否有效,并根据验证结果返回相应的布尔值。这种方法是Web应用中实现身份验证和授权的一种常见方式。

  • 根据token获取自定义的信息

    //根据token获取自定义的信息
        public static Map<String,Object> getInfo(String token,String mykey){
            JWTVerifier build = JWT.require(Algorithm.HMAC256(key)).build();
            Claim claim = build.verify(token).getClaim(mykey);
            return claim.asMap();
        }
    

    JWT.require(Algorithm.HMAC256(key)): 这部分代码与前面提到的验证token的方法类似,它创建了一个Verification配置,指定了用于验证JWT的算法(HMAC256)和密钥(key。这里的key应该是一个在JWT生成和验证过程中都使用的共享密钥。

    .build(): 这个方法调用基于前面配置的验证要求,构建了一个JWTVerifier实例。这个实例将用于验证JWT令牌

    build.verify(token): 使用构建的JWTVerifier实例来验证给定的token。如果token是有效的(即,它确实是由指定的密钥和算法签名的,并且没有被篡改),这个方法将返回一个DecodedJWT对象,该对象包含了JWT中的所有信息。

    .getClaim(mykey): 从验证并解码的JWT中获取与mykey键相关联的Claim对象。JWT中的信息以键值对的形式存储,其中每个键值对都是一个Claim。如果JWT中不存在与mykey对应的Claim,则此方法可能抛出异常或返回null(具体行为取决于JWT库的实现)。

    claim.asMap(): 如果Claim对象存在且不为空,这个方法将Claim中的信息转换为一个Map。这样,你就可以像操作普通Map一样方便地访问JWT中存储的自定义信息了

1.3 登录成功后返回json数据

AuthenticationSuccessHandler接口:只有一个抽象方法,为函数式接口,所以可以使用Lamda表达式重写抽象方法。这个接口是Spring Security用于处理成功认证后的行为的一个钩子。 用于自定义用户成功登录后的处理逻辑。当认证过程成功完成时(例如,用户提供了正确的用户名和密码),Spring Security会调用实现了这个接口的类的onAuthenticationSuccess方法。

    private AuthenticationSuccessHandler successHandler(){
//        return new AuthenticationSuccessHandler() {
//            @Override  //
//            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//                //设置响应的编码
//                httpServletResponse.setContentType("application/json;charset=utf-8");
//                //获取输出对象
//                PrintWriter writer = httpServletResponse.getWriter();
//                //返回json数据即可
//                Map<String,Object> map=new HashMap<>();
//                map.put("username",authentication.getName());
//                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//                //获取权限
//                List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
//
//                map.put("permissions",collect);
//                String token = JWTUtil.createToken(map);
//
//                //返回一个统一的json对象
//                R r=new R(200,"登录成功",token);
//                //转换为json字符串
//                String jsonString = JSON.toJSONString(r);
//                //servlet
//                writer.println(jsonString);
//                writer.flush();
//                writer.close();
//            }
//        };
        //使用Lambda表达式
        return (httpServletRequest, httpServletResponse, authentication) -> {
            //设置响应的编码
            httpServletResponse.setContentType("application/json;charset=utf-8");
            //获取输出对象
            PrintWriter writer = httpServletResponse.getWriter();
            //返回json数据即可
            Map<String,Object> map=new HashMap<>();
            map.put("username",authentication.getName());
            //获取权限信息列表
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            //获取响应的权限标识符
            List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());

            map.put("permissions",collect);
            String token = JWTUtil.createToken(map);

            //返回一个统一的json对象
            R r=new R(200,"登录成功",token);
            //转换为json字符串
            String jsonString = JSON.toJSONString(r);
            //servlet
            writer.println(jsonString);
            writer.flush();
            writer.close();
        };
    }

修改配置

.successHandler(successHandler())

onAuthenticationSuccess方法:

  • 这是AuthenticationSuccessHandler接口中需要实现的方法。它有三个参数:HttpServletRequestHttpServletResponseAuthentication
  • HttpServletRequest: 提供了对当前HTTP请求信息的访问。
  • HttpServletResponse: 允许你控制对客户端的响应,比如设置响应头、发送响应体等。
  • Authentication: 包含了认证成功的用户的信息,比如用户名、密码(通常加密或散列)、权限等。

设置响应编码和获取输出对象:

  • httpServletResponse.setContentType("application/json;charset=utf-8");: 设置响应的内容类型为JSON,并指定字符集为UTF-8。
  • PrintWriter writer = httpServletResponse.getWriter();: 获取一个PrintWriter对象,用于向客户端发送字符文本数据。

构建返回的JSON数据:

  • 创建一个HashMap来存储要返回给客户端的数据。
  • Authentication对象中获取用户名并添加到map中。
  • 使用Java 8的流(Stream)从Authentication对象中获取用户的权限(GrantedAuthority),并将它们转换为字符串列表,然后添加到map中。

获取权限标识符:

authorities 是一个 Collection 类型的集合,它包含了用户所拥有的权限信息。每个 GrantedAuthority 对象都代表了一个权限,通常是通过它的 getAuthority() 方法来获取权限的标识符(通常是一个字符串)。

使用 Java 8 的 Stream API,您可以将这个集合转换为一个新的 List,其中包含了所有权限的标识符。这是通过以下步骤实现的:

  1. 调用 stream() 方法:将 Collection 转换为一个 Stream,这样您就可以使用 Stream API 提供的各种操作了。
  2. 调用 map() 方法map() 方法接受一个函数作为参数,这个函数会被应用到 Stream 中的每个元素上。在这个例子中,您传递了一个 lambda 表达式 item -> item.getAuthority(),它将每个 GrantedAuthority 对象映射为其权限标识符(即调用 getAuthority() 方法的结果)。这样,Stream 中的元素就从 GrantedAuthority 对象变成了字符串。
  3. 调用 collect() 方法collect() 方法是一个终端操作,它接受一个 Collector 来将 Stream 中的元素累积成一个结果。在这个例子中,您使用了 Collectors.toList() 来收集 Stream 中的所有元素到一个新的 List 中。

这行代码的作用就是:将用户所拥有的所有权限(GrantedAuthority 对象)转换为一个包含这些权限标识符(字符串)的列表。

构建统一的响应对象并转换为JSON字符串:

  • 创建一个R对象(假设这是一个自定义的响应类,用于封装响应的状态码、消息和数据),将状态码设置为200,消息设置为"登录成功!",并将JWT令牌作为数据设置进去。
  • 使用某个JSON库(如Fastjson、Jackson等)将R对象转换为JSON字符串。

发送响应到客户端:

  • 使用PrintWriter将JSON字符串写入到HTTP响应中。
  • 调用flush()方法确保所有缓冲的输出都被发送到客户端。
  • 调用close()方法关闭PrintWriter

总的来说,这个successHandler方法在用户成功登录后,会构建一个包含用户名、权限和JWT令牌的JSON响应,并将其发送给客户端。这样,客户端就可以使用这个JWT令牌进行后续的身份验证和授权操作。

1.4 登录失败返回的json数据

//登录失败返回json数据
    private  AuthenticationFailureHandler  failureHandler(){
        return (httpServletRequest, httpServletResponse, e)->{
            //设置编码
            httpServletResponse.setContentType("application/json;charset=utf-8");
            //获取输出对象
            PrintWriter writer = httpServletResponse.getWriter();
            R r=new R(500,"登录失败!",e.getMessage());
            String s = JSON.toJSONString(r);
            writer.println(s);
            writer.flush();
            writer.close();
        };
    }

修改配置

.failureHandler(failureHandler()) 

1.5 权限不足返回的json数据

 //权限不足返回json数据
    private AccessDeniedHandler  accessDeniedHandler(){
        return (httpServletRequest, httpServletResponse, e)->{
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            R r=new R(403,"权限不足,请联系管理员",e.getMessage());
            String s = JSON.toJSONString(r);
            writer.println(s);
            writer.flush();
            writer.close();
        };
    }

修改配置

//指定权限不足跳转的页面
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());

1.6 未登录访问资源返回json数据

需要自定义一个过滤器

@Component //交于spring容器管理
public class LoginFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //若是登录路径,放行
        String requestURI = httpServletRequest.getRequestURI();
        String method = httpServletRequest.getMethod();
        if("/login".equals(requestURI)&&"POST".equals(method)){
            //放行
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //统一编码格式
        httpServletResponse.setContentType("application/json;charset=utf-8");
        //1. 从请求头中获取token令牌
        String token = httpServletRequest.getHeader("token");
        //2. 判断token是否为null
        if(StringUtils.isEmpty(token)){
            //获取传输对象
            PrintWriter writer = httpServletResponse.getWriter();
            R r =new R(500,"未登录",null);
            String s = JSON.toJSONString(r);
            writer.write(s);
            writer.flush();
            writer.close();
            return;
        }
        //3. 验证token
     if(!JWTUtil.verify(token)){
         PrintWriter writer = httpServletResponse.getWriter();
         //返回一个token失效的json数据
         R r=new R(500,"token失效!",null);
         String s = JSON.toJSONString(r);
         writer.write(s);
         writer.flush();
         writer.close();
         return;
     }
     //把当前用户的信息封装到Authentication对象中
        SecurityContext context = SecurityContextHolder.getContext();
        Map<String, Object> userInfo = JWTUtil.getInfo(token, "userInfo");

        Object username = userInfo.get("username");
        //权限标识符
       // List<String> permissions = (List<String>) userInfo.get("permissions");
        List<String> permissions = (List<String>) userInfo.get("permission");

        //通过stream流转换类型
        List<SimpleGrantedAuthority> collect = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
       // List<SimpleGrantedAuthority> collect = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
        //三个参数
        //Object principal,账号
        //Object credentials,密码 null
        //Collection<? extends GrantedAuthority> authorities:权限
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(username,null,collect);
        context.setAuthentication(usernamePasswordAuthenticationToken);

        //放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);

    }
}

用于处理HTTP请求的过滤器方法,通常用于在请求到达控制器之前执行一些预处理操作

检查登录路径并放行

  • 首先,方法通过检查请求的URI和方法来确定是否是一个登录请求(通常是/login路径且方法为POST)。
  • 如果是登录请求,则直接调用filterChain.doFilter(httpServletRequest, httpServletResponse);来放行请求,允许它继续通过过滤器链到达相应的控制器。

从请求头中获取Token

通过httpServletRequest.getHeader("token")从HTTP请求头中获取名为token的值,这个值通常是一个JWT(JSON Web Token),用于身份验证和授权。

检查Token是否为空

  • 使用StringUtils.isEmpty(token)(这里假设StringUtils是一个工具类,用于字符串操作)来检查Token是否为空或null。
  • 如果Token为空,则构造一个包含错误信息的JSON响应(状态码500,消息“未登录”),并写入响应体中,然后结束方法执行。

验证Token:

  • 调用JWTUtil.verify(token)(这里假设JWTUtil是一个工具类,用于处理JWT)来验证Token的有效性。
  • 如果Token无效(例如,签名不匹配、过期等),则构造一个包含错误信息的JSON响应(状态码500,消息“token失效!”),并写入响应体中,然后结束方法执行。

从Token中提取用户信息并封装

  • 使用JWTUtil.getInfo(token, "userInfo")(这里假设getInfo方法从Token中提取特定字段的信息,"userInfo"是字段名)从Token中提取用户信息。
  • 从用户信息中提取用户名和权限列表。注意,这里权限列表的键名从permissions更改为permission,这取决于Token中实际存储的键名。
  • 使用Java 8的Stream API将权限列表中的每个权限字符串转换为SimpleGrantedAuthority对象,这些对象代表了Spring Security中的权限。

将用户信息封装到Authentication对象中

创建一个UsernamePasswordAuthenticationToken(或其他适合的Authentication子类)实例,设置用户名、密码(对于JWT通常不需要,但可以使用null或特殊值)、权限列表等,并将该实例设置到SecurityContextHolder

设置安全上下文

通过SecurityContextHolder.getContext().setAuthentication(...)将身份验证信息设置到当前线程的安全上下文中。这是必要的,因为Spring Security会在后续的处理过程中(如访问控制决策)检查这个上下文来确定当前用户的身份和权限。

修改配置类

在配置类中注入自定义的过滤器

在方法中将自定义的过滤器放在之前

 //把自定义的过滤器放在UsernamePasswordAuthenticationFilter之前
        http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);

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

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

相关文章

英国AI大学排名

计算机学科英国Top10 “计算机科学与信息系统”学科除了最受关注的“计算机科学”专业&#xff0c;还包括了“人工智能”“软件工程”“计算机金融”等众多分支专业。 1.帝国理工学院 Imperial College London 单以计算机专业本科来讲&#xff0c;仅Computing这个专业&#x…

双线性插值(Bilinear Interpolation)

文章目录 一.双线性插值3.双线性插值的优化 一.双线性插值 假设源图像大小为mxn&#xff0c;目标图像为axb。那么两幅图像的边长比分别为&#xff1a;m/a和n/b。注意&#xff0c;通常这个比例不是整数&#xff0c;编程存储的时候要用浮点型。目标图像的第&#xff08;i,j&…

正点原子imx6ull-mini-Linux驱动之Linux LCD 驱动实验(19)

LCD 是很常用的一个外设&#xff0c;在裸机篇中我们讲解了如何编写 LCD 裸机驱动&#xff0c;在 Linux 下 LCD 的使用更加广泛&#xff0c;在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。本章我们 就来学习一下如何在 Linux 下驱动 LCD 屏幕。 1&#xff1a;Linux …

奇安信高管合计套现7.7亿,总裁个人套现1.9亿

【文末送&#xff1a;技战法】 昨天网安一哥&#xff0c;奇安信发布《关于中电金投增持公司股份暨持股 5% 以上股东协议转让公司股份的权益变动的提示性公告》&#xff0c;公告显示中国电子将再次收购奇安信5%的股份。 公告显示&#xff0c;奇安壹号合伙人中&#xff1a;天津…

[Meachines] [Easy] OpenAdmin OpenNetAdmin-RCE+RSA私钥解密+Nano权限提升

信息收集 IP AddressOpening Ports10.10.10.171TCP:22,80 $ nmap -p- 10.10.10.171 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 4b:98:df:85:d1:7…

深入理解操作系统--进程(1)

文章目录 概述进程&#xff0c;轻量级进程和线程进程描述符linux进程四要素创建进程linux3个系统调用创建新的进程do_fork函数copy_process函数 撤销进程 小结 概述 这一章&#xff0c;主要讲的是进程的概念&#xff0c;即程序执行的一个实例。在linux源代码中&#xff0c;通常…

图像像素增强albumentations库的使用

albumentations是一个快速的图像增强库,用于机器学习任务。它支持各种类型的图像变换,包括但不限于旋转、平移、缩放、剪切、翻转、噪声注入、遮挡等。albumentations库可以与深度学习框架如PyTorch和TensorFlow很好地集成, 支持种类丰富的像素级变换,包括雨天、雾天、色域变…

使用Python绘制雷达图的简单示例

雷达图&#xff08;Radar Chart&#xff09;也被称为蜘蛛网图、星形图或极坐标图&#xff0c;是一种用于显示多变量数据的图形方法。它以一个中心点为起点&#xff0c;从中心点向外延伸出多条射线&#xff0c;每条射线代表一个特定的变量或指标。每条射线上的点或线段表示该变量…

面试官:如何保证缓存和数据库的一致性?

你好呀&#xff0c;我是苍何&#xff01; 办公室里鸦雀无声&#xff0c;我木然的看着窗外射进来的阳光&#xff0c;它照在光滑的地板上&#xff0c;又反射到天花板上&#xff0c;再从天花板上反射下来时&#xff0c;就变成一片弥散的白光。 我在白光里偷偷放了一个恶毒的臭屁…

二百五十四、OceanBase——Linux上安装OceanBase数据库(四):登录ocp-express,配置租户管理等信息

一、目的 在部署OceanBase成功后&#xff0c;接下来就是登录ocp-express&#xff0c;配置租户管理等信息&#xff01; 二、ocp-express网址以及账密信息 三、实施步骤 1 登录ocp-express 2 集群总览 3 租户管理 3.1 新建租户 3.2 配置新租户信息 剩下的几个模块了解即可&am…

redis实现的分布式锁redisson

redis服务宕机出现的概率很低&#xff0c;redis集群整体的思想是AP思想&#xff08;优先保证高可用性&#xff09; 如果非要保证业务数据强一致性建议采用CP思想&#xff0c;用zookeeper实现分布式锁。

C++自定义接口类设计器之模板代码生成四

关键代码 QStringList multis templateStr.split(\n);bool startConfig false;bool startVar false;bool startTemplate false;for (const auto& line : multis) {if(startConfig) {if(line.trimmed().startsWith("camealCase")) {auto name_val line.split…

Web开发-html篇-上

HTML发展史 HTML的历史可以追溯到20世纪90年代初。当时&#xff0c;互联网尚处于起步阶段&#xff0c;Web浏览器也刚刚问世。HTML的创建者是蒂姆伯纳斯-李&#xff08;Tim Berners-Lee&#xff09;&#xff0c;他在1991年首次提出了HTML的概念。HTML的初衷是为了方便不同计算机…

TOA/TDOA测距定位,三维任意(>3)个锚节点,对一个未知点进行定位|MATLAB源代码

目录 程序介绍程序截图和运行结果程序截图运行结果 源代码代码修改建议 程序介绍 TOA/TDOA使用三点法测距&#xff0c;在空间中&#xff0c;有4个锚节点就可以定位&#xff0c;但如果有多个节点&#xff0c;定位效果会更好。 锚点不同时&#xff0c;修改程序中的向量和矩阵维度…

C++——哈希结构

1.unordered系列关联式容器 本节主要介绍unordered_map和unordered_set两个容器&#xff0c;底层使用哈希实现的 unordered_map 1.unordered_map是储存<key,value>键值对的关联式容器&#xff0c;其允许通过key快速查找到对应的value&#xff0c;和map非常相似&#x…

JavaFX布局-ToolBar

JavaFX布局-ToolBar 常用属性orientationpadding 实现方式Java实现fxml实现 容纳一组按钮的容器支持水平、垂直布局内容太多&#xff0c;会自动折叠 常用属性 orientation 排列方式&#xff0c;Orientation.VERTICAL、Orientation.HORIZONTAL flowPane.setOrientation(Orient…

【时时三省】(C语言基础)函数递归练习

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 求字符串长度 求的是arr里面字符串的长度 abc后面还有一个\0为结束标志 在结算字符串长度的时候不算\0 所以它的长度是3 模拟实现一个strlen函数 str等于\0的时候就会结束返回count 如果…

纯css的loading效果

在之前的文章里面实现loading组件的封装 其实在日常生活中我们可以采用纯css的组件方式实现loading 的效果 其中<p>元素被绝对定位在其父元素的中心&#xff0c;并且其内部的文本大小和对行间距&#xff08;line-height&#xff09;是响应式的&#xff0c;基于视口宽度&…

《工程检索增强生成系统时的七个失败点》论文 AI 解读

周末使用 AI 速度了一篇 RAG 相关的论文&#xff0c;文中提到的【设计 RAG 系统时需要考虑的七个失败点】非常有价值&#xff0c;简单整理一下分享出来&#xff0c;大家如果感兴趣可以继续阅读原文。 论文名称&#xff1a;Seven Failure Points When Engineering a Retrieval A…

php反序列化靶机serial实战

扫描ip,找到靶机ip后进入 他说这是cookie的测试网页&#xff0c;我们抓个包&#xff0c;得到cookie值 base64解码 扫描一下靶机ip的目录 发现http://192.168.88.153/backup/&#xff0c;访问 下载一下发现是他的网页源码 通过代码审计&#xff0c;发现 通过代码审计得知&…