搭建OIDC Provider,以Golang为例

news2024/11/25 11:30:59

搭建OIDC Provider,以Golang为例

1 需求

结合对OIDC:https://blog.csdn.net/weixin_45747080/article/details/131810562的理解,我尝试自己搭建OIDC的demo。在搭建demo之前,我需要先确定我想要实现成什么样子。以上文提到的https://blog.csdn.net/weixin_45747080/article/details/131303150为例,我想要手动来实现Github授权以及用户认证的服务,此时Github就是作为一个OIDC Provider(以下简称“OP”)。同时我将手动实现第三方应用程序来作为OP的Relying Party(受OP信赖的客户端,以下简称“RP”)。

在Github注册然后登录用户后,我们就能在我的Github里创建和查看自己的Repository(代码仓库,以下简称“Repo”),同时我有两个App,一个叫Gitee,Gitlab,这两个App实现了能够访问用Github登录的用户的Repo。

Tips

这个需求非常实用,目前Gitee也有能够直接复制Github的仓库到Gitee这个功能。

2 功能实现

所以我现在要手动实现:

  1. Github的Repo的创建和查看功能
  2. 用户在登录Gitee的时候能够使用Github登录,并且访问该用户存放于Github的Repo
  3. 用户在登录Gitlab的时候能够使用Github登录,并且访问该用户存放于Github的Repo
  4. Github作为OIDC Provider,发起授权并且认证用户。

Tips

这里所提到的Github、Gitee和Gitlab并不是真正的“它们”,而是用我手动实现类似它们的上述功能,方便理解。

3 工作流程

在这里插入图片描述

  1. 已在Github注册的用户在登录Gitlab的时候选择以Github登录。
  2. 此时Gitlab向Github的OP发起请求要求用户登录Github并且确认访问的范围(scope)。
  3. OP验证用户成功后返回access_tokenid_token给Gitlab。
  4. Gitlab解析id_token以获得用户的信息并进行存储。

Gitee同理。

4 选用库

用Golang实现的话,Golang有现成的实现了OIDC的库:dex。感谢Authing提供参考:https://zhuanlan.zhihu.com/p/118037137,文章里有提到不同的OIDC Provider的实现,如在node上的实现,在Golang上的实现,在Python上的实现等。所以这里我选用dex作为OIDC的Provider。

5 Token的授权方式

demo中授权的方式采用授权码模式,即向OP发起授权请求的response_typecode

6 Provider的Endpoint

Provider会暴露一些常用的接口:如授权接口,token接口,用户信息接口。一般通过访问

{$issuer}/.well-known/openid-configuration

即可查看。

  • 授权接口:{$issuer}/auth

访问该接口的时候需携带ClientID,ClientSecret,ResponseType,Scope。OP会返回code。

  • token接口:{$issuer}/token

访问该接口的时候需携带code,用于和OP交换token。

  • 用户信息接口:{$issuer}/userinfo

访问该接口需要携带accessToken,用户获取EU的个人信息。

7 Repo的服务

首先我们需要一个resource的服务用于存放Repo。

storage

我们需要选用持久化来存储Repo同时对Repo进行增加和查看(这里为了演示方便就直接采用一次性的HashMap来暂存数据,服务重启则HashMap被清空):

type repo struct {
	Name      string
	CreatedBy string
}

type Storage struct {
	set   map[string]struct{}
	repos []*repo
}

var instance *Storage // single instance

func New() *Storage {
	if instance == nil {
		fmt.Println("create a new storage")
		instance = &Storage{
			set:   make(map[string]struct{}),
			repos: make([]*repo, 0),
		}
		return instance
	}

	fmt.Println("storage already exists")
	return instance
}

func (s *Storage) AddRepo(name, subject string) bool {
	if _, ok := s.set[name]; ok {
		return false
	}

	r := &repo{
		Name:      name,
		CreatedBy: subject,
	}
	s.repos = append(s.repos, r)
	s.set[name] = struct{}{}
	return true
}

func (s *Storage) GetRepoBySubject(subject string) []*repo {
	var res []*repo
	for _, r := range s.repos {
		if r.CreatedBy == subject {
			res = append(res, r)
		}
	}
	return res
}

func (s *Storage) AllRepo() []*repo {
	return s.repos
}

有三个对数据的操作,分别是添加Repo根据subject查询Repo列出所有的Repo

APIs

向外暴露3个API:添加Repo查看用户自己创建的Repo列出所有Repo

func AddRepo(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	if name = strings.TrimSpace(name); name == "" {
		http.Error(w, fmt.Sprintf("invalid repo name: empty repo name"), http.StatusBadRequest)
		return
	}

	ok := s.AddRepo(name, ui.Subject)
	fmt.Fprintf(w, "%t", ok)
}

func MyRepo(w http.ResponseWriter, r *http.Request) {
	books := s.GetRepoBySubject(ui.Subject)
	bytes, _ := json.Marshal(books)
	w.Write(bytes)
}

func AllRepo(w http.ResponseWriter, r *http.Request) {
	// for admin

	books := s.AllRepo()
	bytes, _ := json.Marshal(books)
	w.Write(bytes)
}

其中添加Repo和查看自己的Repo需要将用户创建的资源与用户进行绑定,首先需要拿到access_token(这里是从http header中取的),再用access_token去userinfo_endpoint拿到用户的个人信息。

Tips

OAuth2.0中可以通过访问userinfo_endpoint来获取用户的个人信息,并且个人信息中的subject就能唯一标识一个用户,所以这里将subject与用户创建的资源进行绑定。

从header中获得access_token:

func tokenFromHeader(header http.Header) (typ string, token string, err error) {
	token = header.Get("Authorization")
	splits := strings.SplitN(token, " ", 2)
	if len(splits) < 2 {
		return "", "", fmt.Errorf("invalid authorization: empty authorization")
	}

	typ = splits[0]
	token = splits[1]
	if typ != "Bearer" && typ != "bearer" {
		return "", "", fmt.Errorf("invalid authorization type: %s", typ)
	}

	return typ, token, nil
}

用access_token访问userinfo_endpoint:

func getUserInfo(typ, accessToken string) (*userinfo.Userinfo, error) {
	client := http.DefaultClient

	// get the configurations from {issuer}/.well-known/openid-configuration
	u, _ := url.Parse(OidcIssuer)
	u.Path = filepath.Join(u.Path, "/.well-known/openid-configuration")
	res, err := client.Get(u.String())
	if err != nil {
		return nil, fmt.Errorf("list openid-configuration from %s failed: %s", u.String(), err)
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		msg, _ := ioutil.ReadAll(res.Body)
		return nil, fmt.Errorf("list openid-configuration from %s failed: %s", u.String(), msg)
	}

	var configurations map[string]interface{}
	if err = json.NewDecoder(res.Body).Decode(&configurations); err != nil {
		return nil, fmt.Errorf("parse configurations from %s failed: %s", u.String(), err)
	}

	// get the userinfo from {issuer}/{userinfo_endpoint}
	userinfoEndpoint := configurations["userinfo_endpoint"].(string)
	req, _ := http.NewRequest("GET", userinfoEndpoint, nil)
	req.Header.Set("Authorization", fmt.Sprintf("%s %s", typ, accessToken))
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("get userinfo from %s failed: %s", u.String(), err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		msg, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("get userinfo from %s failed: %s", u.String(), msg)
	}

	var ui userinfo.Userinfo
	if err = json.NewDecoder(resp.Body).Decode(&ui); err != nil {
		return nil, fmt.Errorf("parse userinfo from %s failed: %s", userinfoEndpoint, err)
	}
	return &ui, nil
}

这里先访问{issuer}/.well-known/openid-configuration获得OP所提供的openid-configurations,是以json格式返回的各种endpoint,这里拿到userinfo_endpoint的值,然后通过http Get请求携带access_token向userinfo_endpoint发起访问返回json格式的用户信息。

所以现在将token的获取和用户个人信息的获取加入到APIs中,代码如下:

func AddRepo(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	if name = strings.TrimSpace(name); name == "" {
		http.Error(w, fmt.Sprintf("invalid repo name: empty repo name"), http.StatusBadRequest)
		return
	}

	typ, accessToken, err := tokenFromHeader(r.Header)
	if err != nil {
		http.Error(w, fmt.Sprintf("get token from header failed: %s", err), http.StatusUnauthorized)
		return
	}
	ui, err := getUserInfo(typ, accessToken)
	if err != nil {
		http.Error(w, fmt.Sprintf("get userinfo failed: %s", err), http.StatusUnauthorized)
		return
	}

	ok := s.AddRepo(name, ui.Subject)
	fmt.Fprintf(w, "%t", ok)
}

func MyRepo(w http.ResponseWriter, r *http.Request) {
	typ, accessToken, err := tokenFromHeader(r.Header)
	if err != nil {
		http.Error(w, fmt.Sprintf("get token from header failed: %s", err), http.StatusUnauthorized)
		return
	}
	ui, err := getUserInfo(typ, accessToken)
	if err != nil {
		http.Error(w, fmt.Sprintf("get userinfo failed: %s", err), http.StatusUnauthorized)
		return
	}

	books := s.GetRepoBySubject(ui.Subject)
	bytes, _ := json.Marshal(books)
	w.Write(bytes)
}

此时资源服务就能增加和查看用户自己的Repo了。

8 gitee

storage

得益于IDToken,gitee能够在用户使用github的账户登录后得到他在github的信息,还可以存储在自己的数据库里。所以这里用storage来存储Github登录后的该用户的个人信息。

type user struct {
	Subject  string
	Name     string
	Audience string
	Email    string
}

type Storage struct {
	set   map[string]struct{}
	users []*user
}

var instance *Storage

func New() *Storage {
	if instance == nil {
		fmt.Println("create a new storage")
		instance = &Storage{
			set:   make(map[string]struct{}),
			users: make([]*user, 0),
		}
		return instance
	}

	fmt.Println("storage already exists")
	return instance
}

func (s *Storage) AddUser(subject, name, audience, email string) bool {
	if _, ok := s.set[subject]; ok {
		return false
	}

	u := &user{
		Subject:  subject,
		Name:     name,
		Audience: audience,
		Email:    email,
	}
	s.users = append(s.users, u)
	s.set[subject] = struct{}{}

	return true
}

func (s *Storage) AllUser() []*user {
	return s.users
}

OP授权

login

在用户尝试登录后,gitee就会拼凑ClientId,ClientSecret,ResponseType等发送给OP请求获取code。

func Oauth2Config(provider *oidc.Provider) *oauth2.Config {
	return &oauth2.Config{
		ClientID:     ClientID,
		ClientSecret: ClientSecret,
		RedirectURL:  RedirectURL,
		Endpoint:     provider.Endpoint(),
		Scopes:       []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email", "groups"},
	}
}

// Login http redirect to oidc provider
func Login(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	provider, err := oidc.NewProvider(ctx, OidcIssuer)
	if err != nil {
		http.Error(w, fmt.Sprintf("init provider failed: %s", err), http.StatusInternalServerError)
		return
	}

	config := Oauth2Config(provider)
	url := config.AuthCodeURL("state")

	http.Redirect(w, r, url, http.StatusFound)
}

loginCallback

此时OP会带着code发回给回调地址redirect_url。在回调中,用code与OP交换token,然后解析IDToken获取用户个人信息并存储到gitee的数据库中。

// LoginCallback the callback that the oidc provider with call when the user login successfully
func LoginCallback(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	provider, err := oidc.NewProvider(ctx, OidcIssuer)
	if err != nil {
		http.Error(w, fmt.Sprintf("init provider failed: %s", err), http.StatusInternalServerError)
		return
	}

	// exchange token with the server using authorization code
	config := Oauth2Config(provider)
	oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
	if err != nil {
		http.Error(w, fmt.Sprintf("exchange token with server failed: %s", err), http.StatusUnauthorized)
		return
	}

	// get rawIDToken with token
	rawIDToken, ok := oauth2Token.Extra("id_token").(string)
	if !ok {
		http.Error(w, fmt.Sprintf("get rawIDToken with token failed"), http.StatusUnauthorized)
		return
	}

	// verify IDToken with idTokenVerifier, the idTokenVerifier is generated by provider
	idTokenVerifier := provider.Verifier(&oidc.Config{ClientID: ClientID})
	idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
	if err != nil {
		http.Error(w, fmt.Sprintf("verify IDToken with oidc provider failed: %s", err), http.StatusUnauthorized)
		return
	}

	var ui userinfo.UserInfo
	if err = idToken.Claims(&ui); err != nil {
		http.Error(w, fmt.Sprintf("parse id token failed: %s", err), http.StatusInternalServerError)
		return
	}
	ui.AccessToken = oauth2Token.AccessToken
	ui.IDToken = rawIDToken

	s.AddUser(ui.Subject, ui.Name, ui.Audience, ui.Email)

	bytes, _ := json.Marshal(&ui)
	w.Write(bytes) // output the userinfo structure as json
}

这里最好将获取到的access_token、refresh_token、expiry、id_token等存入cookie中,方便下次用户登录的时候不需要重新登录。

Tips

此时的IDToken照理说只能在gitee中使用的,该IDToken的aud是gitee。

API

访问用户存储在github的repo:

func ReadMyRepo(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	token, err := auth.GetFromCookie(r)
	if err != nil {
		http.Error(w, fmt.Sprintf("get token from request failed: %s", err), http.StatusUnauthorized)
		return
	}
	accessToken := token.AccessToken
	tokenType := token.TokenType
	rawIDToken := token.IdToken

	provider, err := oidc.NewProvider(ctx, OidcIssuer)
	if err != nil {
		http.Error(w, fmt.Sprintf("init oidc provider failed: %s", err), http.StatusInternalServerError)
		return
	}
	idTokenVerifier := provider.Verifier(&oidc.Config{ClientID: ClientID})
	idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
	if err = idToken.VerifyAccessToken(accessToken); err != nil {
		http.Error(w, fmt.Sprintf("id_token does not match access_token"), http.StatusUnauthorized)
		return
	} // check if id_token matches access_token

	client := http.DefaultClient
	req, _ := http.NewRequest("GET", ResourceMyBook, nil)
	req.Header.Set("Authorization", fmt.Sprintf("%s %s", tokenType, accessToken))
	res, err := client.Do(req) // do get request with Authorization (access_token)
	if err != nil {
		http.Error(w, fmt.Sprintf("get my book from resource failed: %s", err), http.StatusInternalServerError)
		return
	}
	defer res.Body.Close()
	bytes, _ := ioutil.ReadAll(res.Body)
	w.Write(bytes)
}

先从cookie中取出accessToken和IDToken,然后校验并解析IDToken,同时检查accessToken和IDToken是否匹配。provider.Verifier(&oidc.Config{ClientID: ClientID})的目的是校验器需要校验IDToken的aud是否与ClientID匹配,即校验该IDToken是否是发给其他Client的而不是gitee。此时我们拿着登录用户的AccessToken发送Get请求给Repo的服务查看自己Repo的API。

需要注意的是,来自cookie中的token可能会过期,于是需要利用refresh_token来刷新token以保证即使access_token过期了也能刷新access_token去访问资源。

如果access_token过期可以使用refresh_token去获取新的token

if err != nil && strings.Contains(err.Error(), "oidc: token is expired") {
		// use refresh_token to refresh token
		config := auth.Oauth2Config(provider)
		ts := config.TokenSource(ctx, &oauth2.Token{RefreshToken: token.RefreshToken})
		newToken, err := ts.Token()
		if err != nil {
			http.Error(w, fmt.Sprintf("refresh token failed: %s", err), http.StatusInternalServerError)
			return
		}
		auth.SetIntoCookie(w, newToken) // set new token(contains access_token, refresh_token, id_token...) into cookie
		accessToken = newToken.AccessToken
		tokenType = newToken.TokenType
		rawIDToken = newToken.Extra("id_token").(string)
		idToken, _ = idTokenVerifier.Verify(ctx, rawIDToken)
	}

Tips

具体要不要在访问资源的时候检查token是否过期可以根据需求,也可以在前端采用各种策略(如轮询)来检查用户token是否过期,过期即要求用户重新登录,此时的access_token就会是最新的了,访问资源的时候就不需要再重新刷新access_token了。

9 gitlab

gitlab同理,只是同gitee的clientId和clientSecret以及服务启动的端口不同。

10 OIDC Provider

根据dex的说明,启动Provider需要先修改配置文件,在./examples/config-dev.yaml里的staticClients里添加gitee和gitlab两个client:

staticClients:
- id: example-app
  redirectURIs:
  - 'http://127.0.0.1:5555/callback'
  name: 'Example App'
  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
#  - id: example-device-client
#    redirectURIs:
#      - /device/callback
#    name: 'Static Client for Device Flow'
#    public: true
- id: gitee
  secret: gitee-secret
  name: 'Example Gitee'
  redirectURIs:
  - 'http://app1:8080/callback'
- id: gitlab
  secret: gitlab-secret
  name: 'Example Github'
  redirectURIs:
  - 'http://app2:8081/callback'

所以gitee的地址就是http://app1:8080,gitlab的地址就是http://app2:8081

Tips

gitee和gitlab的回调地址分别是http://app1:8080/callbackhttp://app2:8081/callback。这里需要修改本机的host地址,修改127.0.0.1映射到app1app2上。这里这么做的目的是如果不修改的话gitee和gitlab的监听地址分别是http://localhost:8080/callbackhttp://localhost:8081/callback此时由于浏览器cookie的存储策略问题(同一domain不同port仍然共用cookie),这两App就会共用同一个cookie,这显然是不正确的,所以这里需要修改host的映射地址,最终在浏览器中gitee和gitlab就不会共用同一cookie了。

11 测试演示

  1. 登录github

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  2. 添加Repo

    使用access_token添加名为test、test1、test2、test3的Repo

    在这里插入图片描述

  3. 查看Repo

    使用access_token查看Repo

    在这里插入图片描述

  4. 在gitee使用github登录

    浏览器输入http://app1:8080/login登录在github中注册的用户确认访问范围

    在这里插入图片描述

    此时浏览器的cookie中已经存了该用户的access_token、id_token等。

  5. 在gitee中查看github中的Repo

    浏览器输入http://app1:8080/read

    在这里插入图片描述

    此时该用户就成功在gitee访问到了该用户存在github的Repo。

  6. 同样地,我在gitee登录另外一个账号

  7. 调用gitee的管理员接口就能查看到登录过gitee的用户了

    在这里插入图片描述

至此,便完成了我的需求。在gitee使用github登录,并且存储登录用户的个人信息,同时还可以在gitee直接访问登录用户存储于github的资源。

那么同样地,gitlab也可以使用github登录,也可以存储登录用户的个人信息,同时也可以在gitlab直接访问登录用户存储与github的资源。

仓库地址

https://github.com/FanGaoXS/oidc-demo

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

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

相关文章

【算法|动态规划No.6】leetcode63. 不同路径Ⅱ

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

使用diagrams画架构图

序 最近发现一个画架构图的神器diagrams&#xff0c;提供了很多云厂商及开源组件的图标&#xff0c;相比于C4-PlantUML显得更专业一点。 之前写过技术文档画图工具箱&#xff0c;diagrams属于diagram as code工具派别。 mac安装 brew install graphviz pip install diagrams…

【Matlab】基于遗传算法优化 BP 神经网络的时间序列预测(Excel可直接替换数据)

【Matlab】基于遗传算法优化 BP 神经网络的时间序列预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.文件结构3.Excel数据4.分块代码4.1 arithXover.m4.2 delta.m4.3 ga.m4.4 gabpEval.m4.5 initializega.m4.6 maxGenTerm.m4.7 nonUnifMutation.m4.8 normGeomSel…

【模型压缩】 LPPN论文阅读笔记

LPPN论文阅读笔记 LPPN: A Lightweight Network for Fast Phase Picking 背景 深度学习模型的问题在于计算复杂度较高&#xff0c;在实际数据处理中需要面临较高的处理代价&#xff0c;且需要专用的加速处理设备&#xff0c;如GPU。随着数据累积&#xff0c;迫切需要设计一种…

IDE/mingw下动态库(.dll和.a文件)的生成和部署使用(对比MSVC下.dll和.lib)

文章目录 概述问题的产生基于mingw的DLL动态库基于mingw的EXE可执行程序Makefile文件中使用Qt库的\*.a文件mingw下的*.a 文件 和 *.dll 到底谁起作用小插曲 mingw 生成的 \*.a文件到底是什么为啥mingw的dll可用以编译链接过程转换为lib引导文件 概述 本文介绍了 QtCreator mi…

17 界面布局--登录界面

要点&#xff1a; 利用widgets做布局&#xff1a;水平&#xff0c;垂直&#xff0c;栅格 利用弹簧设置收缩 widget宽高比实际控件大很多&#xff1a;设置Fixed 如果需要去除其余边框间隙可以设置layout 将最小尺寸和最大尺寸设置为固定即为固定尺寸 设置窗口标题&#xff1a;wi…

基于DeepFace模型设计的人脸识别软件

完整资料进入【数字空间】查看——baidu搜索"writebug" 人脸识别软件(无外部API) V2.0 基于DeepFace模型设计的人脸识别软件 V1.0 基于PCA模型设计的人脸识别软件 V2.0 更新时间&#xff1a;2018-08-15 在观看了吴恩达老师的“深度学习课程”&#xff0c;了解了深…

关于新手学习Ubuntu使用vim,如何使用c/cpp的编译器以及如何使用makefile的详细记录

ubuntu下 首先如何编辑 1.启动vim编辑器 打开终端&#xff0c;输入vim&#xff0c;按回车键。 vim gcc.c 2.进入编辑模式 输入i ,进入插入模式。就可以修改文件内容了。 按“ESC”退出编辑模式。 3.退出 Shift键 “:”&#xff0c;切换到命令模式。 输入“q”后回车&…

基于OpenCV的红绿灯识别

基于OpenCV的红绿灯识别 技术背景 为了实现轻舟航天机器人实现红绿灯的识别&#xff0c;决定采用传统算法OpenCV视觉技术。 技术介绍 航天机器人的红绿灯识别主要基于传统计算机视觉技术&#xff0c;利用OpenCV算法对视频流进行处理&#xff0c;以获取红绿灯的状态信息。具…

【Linux】Tcp服务器的三种与客户端通信方法及守护进程化

全是干货~ 文章目录 前言一、多进程版二、多线程版三、线程池版四、Tcp服务器日志的改进五、将Tcp服务器守护进程化总结 前言 在上一篇文章中&#xff0c;我们实现了Tcp服务器&#xff0c;但是为了演示多进程和多线程的效果&#xff0c;我们将服务器与客户通通信写成了一下死循…

【Linux】 由“进程”过渡到“线程” -- 什么是线程(thread)?

知识引入初识线程1.什么叫做进程&#xff1f;2.什么叫做线程&#xff1f;3.如何看待我们之前学习的进程&#xff1f; 理解线程创建线程函数调用1.线程一旦被创建&#xff0c;几乎所有资源都是被线程所共享的2.与进程之间切换相比&#xff0c;线程的切换 初识线程总结&#xff1…

JWT 的使用

一、简介 JWT将用户的一些信息存储在客户端&#xff0c;访问后台时会带着JWT&#xff0c;服务器要对这个JWT进行检验。 由于signKey是存放在服务器端的&#xff0c;所以比较安全只要JWT被篡改就会立刻发现。 JWT认证的优势 1.简洁&#xff1a;JWT Token数据量小&#xff0c;传…

WebRTC带宽评估 -- Transport-wide Congestion Control

简述&#xff1a;在RTP包中增加transport-wide-cc扩展头&#xff0c;放置传输层面的包序号。视频接收端记录RTP包的接收时间&#xff0c;并通过RTCP Feedback消息反馈到视频发送端&#xff0c;发送端结合缓存的RTP包发送时间&#xff0c;基于丢包和延迟估算当前带宽&#xff0c…

zabbix 企业级监控 (3)Zabbix-server监控mysql及httpd服务

目录 web界面设置 server.zabbix.com 服务器操作 编辑 chk_mysql.sh脚本 查看web效果 web界面设置 1. 2. 3. 4. 5. 6. 7. 8. server.zabbix.com 服务器操作 [rootserver ~]# cd /usr/local/zabbix/etc/ [rootserver etc]# vim zabbix_agentd.conf UnsafeUserParameters1 Us…

Java当中的栈

栈的理解 栈&#xff08;Stack&#xff09;是一种受限的线性数据结构&#xff0c;所谓受限是指栈只暴露栈顶和栈底的操作&#xff0c;其底层是由数组实现的。栈的特性是先进后出。 常用方法 注意上面的peek()方法和pop()方法的区别&#xff01; 实例 import java.util.Stack…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 19 日论文合集)

文章目录 一、分割|语义相关(12篇)1.1 Disentangle then Parse:Night-time Semantic Segmentation with Illumination Disentanglement1.2 OnlineRefer: A Simple Online Baseline for Referring Video Object Segmentation1.3 MarS3D: A Plug-and-Play Motion-Aware Model for…

LeetCode74.Search-A-2d-Matrix<搜索二维矩阵>

题目&#xff1a; 思路&#xff1a; 矩阵&#xff0c;搜索数是否在矩阵内。那就查找他是否在每一行中。如果符合这一行的范围&#xff0c;那就一直找这一列是否存在&#xff0c;如果存在返回true&#xff1b;否则false&#xff1b; 代码是&#xff1a; //codeclass Solution …

Istio 安全管理 加密证书中心

1 tls认证 2 设置ACL 允许哪些客户端可以访问 哪些客户端不能访问 3 istio里面的认证 加密是可以分为三种类型 对称加密&#xff08;加密和解密用的是同一个密钥&#xff09;非对称加密哈希函数 对称加密 A要发送数据传送给B&#xff0c;那么A要使用一个密钥&#xff0c;里面…

MySQL-数据库读写分离(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

Verilog 学习之路二——基础学习总结(摘取自菜鸟教程)

目录 1 Verilog 设计方法2. 基础语法2.1 格式2.2 数值表示数值种类表示方法 2.3 数据类型2.4 表达式 3. 编译指令4. 连续赋值5. 过程结构6 过程赋值7 时序控制8 语句块9 循环10 函数例子-数码管译码 1 Verilog 设计方法 Verilog 的设计多采用自上而下的设计方法&#xff08;to…