Gin 框架之jwt 介绍与基本使用

文章目录

    • 一.JWT 介绍
    • 二.JWT认证与session认证的区别
      • 2.1 基于session认证流程图
      • 2.2 基于jwt认证流程图
    • 三. JWT 的构成
      • 3.1 header : 头部
      • 3.2 payload : 负载
        • 3.2.1 标准中注册的声明 (建议但不强制使用)
        • 3.2.2 公共的声明
        • 3.2.3 私有的声明
        • 3.2.4 定义一个payload
      • 3.3 signatrue : 签名
      • 3.4 得到 token
    • 四.base64 编码和解码的使用
      • 4.1 base64 编码
      • 4.2 base64 解码
    • 五.JWT 的本质原理
      • 5.1 签发
      • 5.2 校验
      • 5.3 jwt认证开发流程(重点)
    • 六、Gin 框架中使用jwt
      • 6.1 安装JWT库
      • 6.2 导入库
      • 6.3 使用JWT 鉴权认证
        • 6.3.1 JWT中间件开发
        • 6.3.2 使用JWT中间件
        • 6.3.3 生成JWT token
        • 6.3.4 访问路由签发token
        • 6.3.5 通过 token 鉴权获取用户信息

一.JWT 介绍

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

二.JWT认证与session认证的区别

2.1 基于session认证流程图

img

服务器需要存储用户的token信息

2.2 基于jwt认证流程图

img

服务端不需要存储用户token, 都存在客户端

三. JWT 的构成

JWT就是一段字符串, 由三段信息构成, 三段信息文本使用.(点) 拼接就构成了JWT字符串 :

  • eyJhbGciOiJIUzI1sNiIsIn.eyJzdWIiOiIxMjRG9OnRydWV9.TJVArHDcEfxjoYZgeFONFh7HgQ
  • 第一部分我们称它为头部 : header
  • 第二部分我们称其为载荷 : payload (类似于飞机上承载的物品)
  • 第三部分是签证 : signature

3.1 header : 头部

头部,JWT 的元数据,也就是描述这个 token 本身的数据,一个 JSON 对象。由两部分组成 :

  1. 声明类型(当前令牌名称)
  2. 声明加密算法
// 定义头部信息
header := map[string]interface{}{
  "alg": "HS256", // 声明加密算法,可以根据需要修改
  "typ": "JWT",   // 声明类型
}

将头部使用base64编码构成第一部分 (base64编码方法, 该编码可以对称解码)

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
)

func main() {
	// 定义头部信息
	header := map[string]interface{}{
		"alg": "HS256", // 声明加密算法,可以根据需要修改
		"typ": "JWT",   // 声明类型
	}

	// 将头部信息序列化为JSON格式字符串
	headerBytes, err := json.Marshal(header)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)

	fmt.Println(headerStr)
	// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
}

3.2 payload : 负载

存放用户有效信息的地方,一个 JSON 对象, 这些有效信息包含三个部分:

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明
3.2.1 标准中注册的声明 (建议但不强制使用)
  • iss: JWT签发者
  • sub: JWT所面向的用户
  • aud: 接收JWT的一方
  • exp: JWT的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该JWT都是不可用的
  • iat: JWT的签发时间
  • jti: JWT的唯一身份标识,主要用来作为一次性token,从而回避时序攻击
3.2.2 公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。

3.2.3 私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

3.2.4 定义一个payload

除了上面的字段, 你自己也可以添加自己想要的字段, 需要注意的是:这些信息是不加密的, 所以最好不要存敏感信息

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
)

func main() {
	// 定义Payload信息
	payload := map[string]interface{}{
		"sub":   "1234567890",              // 主题,表示该JWT的所有者
		"name":  "John Doe",                // 自定义声明,可以根据需要添加其他声明
		"iat":   1516239022,                // 签发时间,表示JWT的签发时间,一般为当前时间的时间戳
		"exp":   1516239022 + 3600,         // 过期时间,表示JWT的过期时间,一般为签发时间加上有效期,以秒为单位
		"roles": []string{"admin", "user"}, // 自定义声明,可以存储用户角色等信息
	}

	// 将Payload信息序列化为JSON格式字符串
	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)

	fmt.Println(payloadStr) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
}

然后将其进行base64加密,得到JWT的第二部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

3.3 signatrue : 签名

signature 是根据 headertoken 生成, 由三部分构成 :

  • base64 编码后的 header
  • base64 编码后的 payload
  • secret : 秘钥 (只有服务端知道)

这个部分需要将base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
)

func main() {
	// 定义头部信息
	header := map[string]interface{}{
		"alg": "HS256",
		"typ": "JWT",
	}

	// 定义Payload信息
	payload := map[string]interface{}{
		"sub":   "1234567890",
		"name":  "John Doe",
		"iat":   1516239022,
		"exp":   1516239022 + 3600,
		"roles": []string{"admin", "user"},
	}

	// 将头部信息序列化为JSON格式字符串
	headerBytes, err := json.Marshal(header)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)

	// 将Payload信息序列化为JSON格式字符串
	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)

	// 定义秘钥
	secret := "your-secret-key" // 替换为实际的秘钥

	// 生成签名
	signature := generateSignature(headerStr, payloadStr, secret)

	fmt.Println(signature) // C-94Wc6olGK6CEbkA9Xj0ogDQIFdPsEefZKCZrz_fvA
	// 生成的签名字符串
}

func generateSignature(headerStr, payloadStr, secret string) string {
	// 构造要签名的数据
	dataToSign := headerStr + "." + payloadStr

	// 使用HMAC-SHA256算法生成签名
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(dataToSign))
	signatureBytes := h.Sum(nil)

	// 对签名进行base64编码
	signature := base64.RawURLEncoding.EncodeToString(signatureBytes)

	return signature
}

3.4 得到 token

算出签名之后, 把 header、payload、signatrue 三部分使用 .(点) 拼接成一个大字符串, 然后返回给客户端让其存储

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
)

func main() {
	// 定义头部信息
	header := map[string]interface{}{
		"alg": "HS256",
		"typ": "JWT",
	}

	// 定义Payload信息
	payload := map[string]interface{}{
		"sub":   "1234567890",
		"name":  "John Doe",
		"iat":   1516239022,
		"exp":   1516239022 + 3600,
		"roles": []string{"admin", "user"},
	}

	// 将头部信息序列化为JSON格式字符串
	headerBytes, err := json.Marshal(header)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	headerStr := base64.RawURLEncoding.EncodeToString(headerBytes)

	// 将Payload信息序列化为JSON格式字符串
	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	payloadStr := base64.RawURLEncoding.EncodeToString(payloadBytes)

	// 将base64加密后的header和payload拼接起来
	dataToSign := headerStr + "." + payloadStr

	// 定义秘钥
	secret := "your-secret-key" // 替换为实际的秘钥

	// 生成签名
	signature := generateSignature(dataToSign, secret)

	// 最终的JWT字符串
	jwtToken := dataToSign + "." + signature

	fmt.Println(jwtToken)
	// 最终生成的JWT字符串
	// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyNDI2MjIsImlhdCI6MTUxNjIzOTAyMiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sInN1YiI6IjEyMzQ1Njc4OTAifQ.C-94Wc6olGK6CEbkA9Xj0ogDQIFdPsEefZKCZrz_fvA
}

func generateSignature(dataToSign, secret string) string {
	// 使用HMAC-SHA256算法生成签名
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(dataToSign))
	signatureBytes := h.Sum(nil)

	// 对签名进行base64编码
	signature := base64.RawURLEncoding.EncodeToString(signatureBytes)

	return signature
}

注意:secret 是保存在服务器端的,JWT的签发生成也是在服务器端的,secret 就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret,那就意味着客户端是可以自我签发JWT了。

四.base64 编码和解码的使用

首先 base64 是一种编码方式, 并非加密方式; 它跟语言无关, 任何语言都能使用 base64 编码&解码

4.1 base64 编码

	// 定义一个信息字段
	dic := map[string]interface{}{"id": 1, "name": "jarvis", "age": "male"}
	
	// 将其序列化成json格式字符串
	jsonBytes, err := json.Marshal(dic)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return
	}
	jsonStr := string(jsonBytes)
	
	// 将json格式字符串encode再使用base64编码成一串Bytes格式编码
	base64Str := base64.StdEncoding.EncodeToString([]byte(jsonStr))
	
	fmt.Println([]byte(base64Str))
	// [101 121 74 112 90 67 73 54 73 68 69 115 73 67 50 70 109 90 121 66 67 74 112 73 106 111 103 73 109 70 48 105 71 108 112 77 97 86 120 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 109 116 65 87 120 108 73 106 111 103 73 61]
	fmt.Println(base64Str)
	// eyJhZ2UiOiJtYWxlIiwiaWQiOjEsIm5hbWUiOiJqYXJ2aXMifQ==

4.2 base64 解码

// 替换为你的 base64 编码字符串
	base64Str := "eyJhZ2UiOiJtYWxlIiwiaWQiOjEsIm5hbWUiOiJqYXJ2aXMifQ=="

	// base64 解码
	decodedBytes, err := base64.StdEncoding.DecodeString(base64Str)
	if err != nil {
		fmt.Println("Base64 decoding error:", err)
		return
	}

	// JSON 反序列化
	var dic map[string]interface{}
	err = json.Unmarshal(decodedBytes, &dic)
	if err != nil {
		fmt.Println("JSON decoding error:", err)
		return
	}

	fmt.Println(dic)
	// map[age:male id:1 name:jarvis]

五.JWT 的本质原理

/*
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
	"company": "公司信息",
	...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
	"user_id": 1,
	...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
	"head": "头的加密字符串",
	"payload": "体的加密字符串",
	"secret_key": "安全码"
}
*/

5.1 签发

根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

  • 用基本信息存储 json 字典, 采用 base64 编码得到头字符串
  • 用关键信息存储 json 字典,采用 base64 编码得到体字符串
  • 用头、体编码的字符串再加安全码信息(secret)存储 json 字典, 采用 header 中指定的算法加密得到签名字符串
  • 最后形成的三段字符串用 . 拼接成token字符串返回给前台

5.2 校验

根据客户端带 token 的请求 反解出 user 对象

  • 将 token 按 .(点) 拆分为三段字符串, 第一段编码后的头字符串一般不需要做任何处理
  • 第二段编码后的体字符串, 要解码出用户主键, 通过主键从 User 表中就能得到登录用户, 过期时间和设备信息都是安全信息, 确保 token 没过期, 且是同一设备来的
  • 再将第一段 + 第二段 + 服务器安全码使用header中指定的不可逆算法加密, 与第三段 签名字符串进行对比校验, 通过后才能代表第二段校验得到的 user 对象就是合法的登录用户

5.3 jwt认证开发流程(重点)

  1. 用账号密码访问登录接口,登录接口逻辑中调用签发token算法,得到token,返回给客户端,客户端自己存到cookies中。

  2. 校验token的算法应该写在中间件中,所有请求都会进行认证校验,所以请求带了token,就会反解出用户信息。

六、Gin 框架中使用jwt

6.1 安装JWT库

使用Gin框架时,你可以选择一个适用于Go语言的JWT库。一个流行的选择是github.com/dgrijalva/jwt-go库。

go get -u github.com/golang-jwt/jwt/v5

6.2 导入库

在你的Go代码中导入github.com/golang-jwt/jwt/v5github.com/gin-gonic/gin

import (
    "github.com/golang-jwt/jwt/v5"
    "github.com/gin-gonic/gin"
)

6.3 使用JWT 鉴权认证

6.3.1 JWT中间件开发

JWT中间件: 创建一个JWT中间件,它将用于保护需要身份验证的路由。

package middleware

import (
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"net/http"
	"strings"
	"webook/internal/web"
)

// LoginJWTMiddlewareBuilder JWT 登录校验
type LoginJWTMiddlewareBuilder struct {
	paths []string
}

func NewLoginJWTMiddlewareBuilder() *LoginJWTMiddlewareBuilder {
	return &LoginJWTMiddlewareBuilder{}
}

// IgnorePaths 忽略的路径
func (l *LoginJWTMiddlewareBuilder) IgnorePaths(path string) *LoginJWTMiddlewareBuilder {
	l.paths = append(l.paths, path)
	return l
}

func (l *LoginJWTMiddlewareBuilder) Build() gin.HandlerFunc {
	// 用 Go 的方式编码解码
	return func(ctx *gin.Context) {
		// 不需要登录校验的
		for _, path := range l.paths {
			if ctx.Request.URL.Path == path {
				return
			}
		}
		// 用 JWT 来校验
		tokenHeader := ctx.GetHeader("Authorization")
		if tokenHeader == "" {
			// 没登录
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		segs := strings.Split(tokenHeader, " ")
		if len(segs) != 2 {
			// 没登录,有人瞎搞
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		tokenStr := segs[1]
		claims := &web.UserClaims{}
		// ParseWithClaims 里面,一定要传入指针
		// 这里的95osj3fUD7fo0mlYdDbncXz4VD2igvf0 代表的是签发的时候的key,并且key 要和签发的时候一样
		token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
			return []byte("95osj3fUD7fo0mlYdDbncXz4VD2igvf0"), nil
		})
		if err != nil {
			// 没登录
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		// token 验证不通过
		if token == nil || !token.Valid {
			// 没登录
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		// 将用户信息存储到上下文中
		ctx.Set("claims", claims)
	}
}
6.3.2 使用JWT中间件

使用JWT中间件: 在需要身份验证的路由上使用JWT中间件。

func initWebServer() *gin.Engine {
	ser := gin.Default()
	ser.Use(cors.New(cors.Config{
		//AllowOrigins: []string{"*"},
		//AllowMethods: []string{"POST", "GET"},
		AllowHeaders: []string{"Content-Type", "Authorization"},
		// 允许跨域访问的响应头,不加这个前端拿不到token响应头
		ExposeHeaders: []string{"x-jwt-token"},
		// 是否允许你带 cookie 之类的东西
		AllowCredentials: true,
		AllowOriginFunc: func(origin string) bool {
			if strings.HasPrefix(origin, "http://localhost") {
				// 你的开发环境
				return true
			}
			return strings.Contains(origin, "http://你的公司域名.com")
		},
		MaxAge: 12 * time.Hour,
	}))
  // 注册登录校验中间件以及不要登录校验的路径
	ser.Use(middleware.NewLoginJWTMiddlewareBuilder().
		IgnorePaths("/users/signup").
		IgnorePaths("/users/login").Build())
	return ser
}
6.3.3 生成JWT token

生成JWT token: 在用户登录成功后,你可以生成JWT并将其返回给客户端。

// UserClaims 自定义的声明结构体并内嵌 jwt.StandardClaims
type UserClaims struct {
	jwt.RegisteredClaims
	// 声明你自己的要放进去 token 里面的数据
	Uid int64
	// 后续需要什么字段,就在这里添加
}

func (u *UserHandler) LoginJWT(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	var req LoginReq
	if err := ctx.Bind(&req); err != nil {
		return
	}
	user, err := u.svc.Login(ctx, req.Email, req.Password)
	if err == service.ErrInvalidUserOrPassword {
		ctx.String(http.StatusOK, "用户名或密码不对")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	// 步骤2
	// 在这里用 JWT 设置登录态
	// 生成一个 JWT token
	// 将用户信息存储到token中
	claims := UserClaims{
		Uid: user.Id,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
	tokenStr, err := token.SignedString([]byte("95osj3fUD7fo0mlYdDbncXz4VD2igvf0"))
	if err != nil {
		ctx.String(http.StatusInternalServerError, "系统错误")
		return
	}
	ctx.Header("x-jwt-token", tokenStr)
	fmt.Println(user)
	ctx.String(http.StatusOK, "登录成功")
	return
}
6.3.4 访问路由签发token

我们通过接口调试工具访问路由127.0.0.1:8080/users/login 签发用户tokenheader 中就会有X-Jwt-Token这个字段以及生成的token 对应值。

6.3.5 通过 token 鉴权获取用户信息

在平时开发中,我们一般不会直接传user_id 过来,一般是通过token来获取用户信息,比如我们需要查询用户信息,之前我们已经将用户ID放入到token中了,直接通过c, _ := ctx.Get("claims")来获取我们存放的用户信息,以下是具体代码;

func (u *UserHandler) ProfileJWT(ctx *gin.Context) {
	c, _ := ctx.Get("claims")
	// 你可以断定,必然有 claims
	//if !ok {
	//	// 你可以考虑监控住这里
	//	ctx.String(http.StatusOK, "系统错误")
	//	return
	//}
	// ok 代表是不是 *UserClaims
	claims, ok := c.(*UserClaims)
	if !ok {
		// 你可以考虑监控住这里
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	fmt.Println("当前用户ID为:", claims.Uid)
	ctx.String(http.StatusOK, "查询成功")
}

最后我们只需要访问路由:127.0.0.1:8080/users/profile,在header中加入token 即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文掌握SpringBoot注解之@Component 知识文集(5)

🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…

操作系统(5)-----操作系统进程相关

目录 一.进程的组成 1.PCB(进程控制块) 2.程序段与数据段 二.进程的特征 三.进程的状态以及状态的转换 四.进程的组织 1.链接方式 2.索引方式 五.进程控制 六.进程控制相关原语 1.创建原语 2.撤销原语 3.阻塞原语 4.唤醒原语 5.切换原语 …

Spring MVC 请求流程

SpringMVC 请求流程 一、DispatcherServlet 是一个 Servlet二、Spring MVC 的完整请求流程 Spring MVC 框架是基于 Servlet 技术的。以请求为驱动,围绕 Servlet 设计的。Spring MVC 处理用户请求与访问一个 Servlet 是类似的,请求发送给 Servlet&#xf…

5G赋能智慧文旅:科技与文化的完美结合,打造无缝旅游体验,重塑旅游业的未来

一、5G技术:智慧文旅的强大引擎 5G技术的起源可以追溯到2010年,当时世界各国开始意识到4G技术已经达到了瓶颈,无法满足日益增长的移动通信需求。2013年,国际电信联盟(ITU)成立了5G技术研究组,开…

力扣516. 最长回文子序列

动态规划 思路: 字符串最长回文子序列问题可以转换为原字符串 s 和逆串 s 的最长公共子序列长度问题,具体推断过程可以参考 力扣1312. 让字符串成为回文串的最少插入次数问题变成了求两个字符串最长公共子序列长度问题,具体思路可以参考 力扣…

物联网协议Coap之C#基于Mozi的CoapClient调用解析

目录 前言 一、CoapClient相关类介绍 1、CoapClient类图 2、CoapClient的设计与实现 3、SendMessage解析 二、Client调用分析 1、创建CoapClient对象 2、实际发送请求 3、Server端请求响应 4、控制器寻址 总结 前言 在之前的博客内容中,关于在ASP.Net Co…

《向量数据库指南》——Milvus Cloud 2023 年关键数据

10 倍 - 内存节省 在 2023 年,RAG 模型被广泛认为是向量数据库的最主要应用之一。我们发现,在文档被分割成块之后,一个含有 500 个 Token 的 Chunk(通常 1000 字节)会转换成一个 1536 维的 float32 向量(通常 6000 字节),这意味着向量数据的体积可能会超过原始文档的…

Linux服务器配置与管理(第三次实验)

实验目的及具体要求 目的 1.熟悉Shell 脚本语法 2.掌握创建脚本的方法 3.掌握运行脚本的方法 4.掌握变量及表达式 5.掌握Shell 控制结构 6.掌握Shell 函数 任务 1.显示当前日期时间、执行路径、用户账户及所在的目录位置 2.判断一个文件是不是字符设备文件&#xff0…

Github 2024-01-28 开源项目日报Top10

根据Github Trendings的统计,今日(2024-01-28统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目3TypeScript项目2Rust项目1HTML项目1JavaScript项目1Cuda项目1C#项目1非开发语言项目1 Nuxt&#…

有手就行!阿里云上3分钟搞定幻兽帕鲁联机服务器搭建

幻兽帕鲁最近在社区呈现了爆火的趋势,在线人数已突破百万级别,官方服务器也开始出现不稳定,卡人闪退的情况。对于有一定财力的小伙伴,搭建一个私人服务器是一个最稳定而舒服的解决方案。 本文萝卜哥将讲解一下如何快速搭建 palwo…

Nginx解析漏洞(nginx_parsing_vulnerability)

目录 Nginx解析漏洞 环境搭建 复现 漏洞利用 Nginx解析漏洞 NGINX解析漏洞主要是由于NGINX配置文件以及PHP配置文件的错误配置导致的。这个漏洞与NGINX、PHP版本无关,属于用户配置不当造成的解析漏洞。具体来说,由于nginx.conf的配置导致nginx把…

【Java】Spring注解开发

一、Spring注解开发 1 注解开发定义Bean对象【重点】 目的:xml配置Bean对象有些繁琐,使用注解简化Bean对象的定义 问题导入 问题1:使用什么标签进行Spring注解包扫描? 问题2:Component注解和Controller、Service、R…

MQ面试题之Kafka

前言 前文介绍了消息队列相关知识,并未针对某个具体的产品,所以略显抽象。本人毕业到现在使用的都是公司内部产品,对于通用产品无实际经验,但是各种消息中间件大差不差,故而本次选择一个相对较熟悉的Kafka进行详细介绍…

应用机器学习的建议

一、决定下一步做什么 在你得到你的学习参数以后,如果你要将你的假设函数放到一组新的房屋样本上进行测试,假如说你在预测房价时产生了巨大的误差,你想改进这个算法,接下来应该怎么办?实际上你可以考虑先采用下面的几种…

深度强化学习(王树森)笔记03

深度强化学习(DRL) 本文是学习笔记,如有侵权,请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接:https://github.com/wangshusen/DRL 源代码链接:https://github.c…

STM正点mini-新建工程模板,GPIO及寄存器(介绍)

一.新建工程模板(基于固件库) 1.1库函数与寄存器的区别 这里的启动文件都是根据容量来进行区分的 对MDK而言即使include了,也不知道在哪里找头文件 STM32F10X_HD,USE_STDPERIPH_DRIVER 二.新建工程模板(基于寄存器) 上面的大部分配置与固件库的一样 具体可以看手…

【面试】测试开发面试题

帝王之气,定是你和万里江山,我都护得周全 文章目录 前言1. 网络原理get与post的区别TCP/IP各层是如何传输数据的IP头部包含哪些内容TCP头部为什么有浮动网络层协议1. 路由协议2. 路由信息3. OSPF与RIP的区别Cookie与Session,Token的区别http与…

Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )

目录 事务事务概念什么是事务事务四个特性(ACID) 搭建事务操作环境Spring 事务管理介绍注解声明式事务管理声明式事务管理参数配置XML 声明式事务管理事务操作(完全注解声明式事务管理)感谢阅读 开篇: 欢迎再次来到 Spring 5 学习…

Java笔记 --- 一、双列集合

一、双列集合 双列集合的特点 Map 创建Map对象时,要规定键和值的泛型 Map是一个接口,不能直接创建,要创建实例化对象 Map的遍历 通过键找值 先获取到键的对象,并放到一个单列集合中(map.KeySet()方法)…

【Git】项目管理笔记

文章目录 本地电脑初始化docker报错.gitignoregit loggit resetgit statusgit ls-filesgit rm -r -f --cached拉取仓库文件更新本地的项目报错处理! [rejected] master -> master (fetch first)gitgitee.com: Permission denied (publickey).error: remote origin already e…