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

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";//创建tokenpublic 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类型的时间//创建tokenString token = JWT.create().withHeader(head)//设置头部信息.withIssuedAt(date)//设置发布时间.withExpiresAt(time)//设置过期时间.withClaim("userInfo", map)//设置个人信息.sign(Algorithm.HMAC256(key));//签名return token;}
    
  • 校验token

     //校验tokenpublic 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);//servletwriter.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 {@Overrideprotected 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是否为nullif(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. 验证tokenif(!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.rhkb.cn/news/391021.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉树的前序遍历 - 力扣(LeetCode)C语言

144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09;(点击前面链接即可查看题目) 一、题目 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; …

文心智能体【MBTI速测小精灵】:趣味速测,精准解析你的性格密码!

文章目录 一、文心智能体平台是什么&#xff1f;二、创建文心智能体智能体创建智能体调试分析智能体基础配置智能体高级配置智能体高级调试 三、文心智能体发布四、文心智能体体验总结 一、文心智能体平台是什么&#xff1f; AgentBuilder文心智能体平台是基于文心大模型的智能…

适用于 Android 的 6 大视频恢复软件榜单 – 恢复您的珍贵回忆!

失去珍贵的回忆可能是一种令人心碎的经历&#xff0c;尤其是在您的 Android 设备上拍摄视频时。无论是由于意外删除、格式化、系统崩溃还是任何其他不可预见的情况&#xff0c;丢失珍贵视频的想法都会造成巨大的痛苦。但不要担心&#xff01;在这篇博文中&#xff0c;我们将深入…

临床随机对照试验中的分层问题及其解决方法

在临床随机对照试验&#xff08;Randomized Controlled Trials, RCTs&#xff09;中&#xff0c;分层问题&#xff08;Stratification Issues&#xff09;是影响研究结果有效性的重要因素之一。RCTs是评估医疗干预效果的金标准&#xff0c;旨在通过随机分组和对照来消除干扰因素…

在亚马逊云科技AWS上利用ElasticSearch和RAG搭建个性化推荐系统

简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次介绍用当下热门的RAG和大语言模型&#xf…

[WUSTCTF2020]朴实无华1

打开题目 扫目录用dirsearch扫&#xff0c;为节省建议只扫常见的目录&#xff0c;配置是&#xff1a; ./dirsearch.py -e bak,zip,txt,tgz,php -u http:..... -s 3 -t 20 访问一下 根据提示&#xff0c;再访问一次 提示不在这&#xff0c;抓包看看 根据提示&#xff0c;改ge…

页面根据sse返回的流,逐句展示内容,达到gpt效果

之前的文章里&#xff0c;我写到了关于怎么获取sse中的流&#xff0c;但是缺少逐句展示的效果&#xff0c;这次来补齐。 比如这种&#xff0c;实现难点在于&#xff0c;当返回的markdown语法&#xff0c;不是完整的语句时&#xff0c;展示的代码块会错乱。 实现代码 app.vue …

P10477 题解

题目传送门 题目传送门&#xff08;洛谷&#xff09; Step1 理解题意 一共有 T T T 组数据有一个地铁&#xff0c;有一个中心车站&#xff08;即为根&#xff09;&#xff0c;有一个人从中心车站出发。对于每组数据&#xff0c;给定两个同样长度的01串 s 1 s_1 s1​ , s …

内网安全:多种横向移动方式

1.MMC20.Application远程执行命令 2.ShellWindows远程执行命令 3.ShellBrowserWindow远程执行命令 4.WinRM远程执行命令横向移动 5.使用系统漏洞ms17010横向移动 DCOM&#xff1a; DCOM&#xff08;分布式组件对象模型&#xff09;是微软的一系列概念和程序接口。它支持不同…

C语言--函数

1. 函数定义 语法&#xff1a; 类型标识符 函数名&#xff08;形式参数&#xff09; {函数体代码 } &#xff08;1&#xff09;类型标识符 --- 数据类型&#xff08;函数要带出的结果的类型&#xff09; 注&#xff1a;数组类型不能做函数返回结果的类型&#xff0c;如果函…

Tomcat 8.5 下载、安装、启动及各种问题

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 本期内容主要介绍 Tomcat 8 的安装&#xff0c;以及可能会遇到的问题 文章目录 1. Tomcat 安装2. 可能会遇到的问题2.…

【vluhub】skywalking

SkyWalking是一个开源监控平台&#xff0c;用于从服务和云原生基础设施收集、分析、聚合和可视化数据 低版本存在sql注入漏洞 访问地址 http://192.168.203.12:8080/graphql burpsuite抓数据包 替换 {"query":"query queryLogs($condition: LogQueryConditi…

机器学习 第9章-聚类

机器学习 第9章-聚类 9.1 聚类任务 在“无监督学习”(unsupervised learning)中&#xff0c;训练样本的标记信息是未知的&#xff0c;目标是通过对无标记训练样本的学习来揭示数据的内在性质及规律&#xff0c;为进一步的数据分析提供基础。此类学习任务中研究最多、应用最广…

计算机毕业设计选题推荐-学生作业管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

errno错误码列举

errno&#xff0c;int变量&#xff0c;表示系统最近一次错误码。 当系统调用和一些库函数发生错误时&#xff0c;会给errno赋值&#xff0c;以指示哪里出了问题。 目录 errno值列表 errno值获取示例 errno值列表 <errno.h>头文件定义了errno的一些值&#xff0c;部分…

【C++ STL】list

文章目录 list1. list的使用1.1 增删查改1.2 功能接口 2. list的模拟实现2.1 list的定义2.2 默认成员函数2.3 迭代器正向迭代器解引用箭头 反向迭代器迭代器接口 2.4 基本功能 3. list对比vector list 与 vector 相比&#xff0c;list 的好处就是每次插⼊或删除 ⼀个 元素 就 …

pydal,一个实用的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个实用的 Python 库 - pydal。 Github地址&#xff1a;https://github.com/web2py/pydal/ 在现代应用开发中&#xff0c;数据库操作是一个核心部分。为了简化与数据库的交互…

PMP–知识卡片--Scrum框架

定义 Scrum框架包含由产品负责人、开发团队、敏捷专家构成的Scrum团队&#xff0c;以及活动工件。框架中的每一个组件都服务于一个特定的目标&#xff0c;且是Scrum成功和运用的基本要素。 Scrum的规则将角色、活动和工件绑定在一起&#xff0c;管理它们之间的关系和交互。 …

【Vue3】组件通信之$parent

【Vue3】组件通信之$parent 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

基于Java的网络考试系统的设计与实现

点击下载源码 基于Java的网络考试系统的设计与实现 摘 要 科技在进步&#xff0c;人们生活和工作的方式正发生着改变&#xff0c;不仅体现在人们的衣食住行&#xff0c;也体现在与时俱进的考试形式上。以前的考试需要组织者投入大量的时间和精力&#xff0c;需要对考试的试题…