惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
罗磊的独立博客
量子位
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
M
MIT News - Artificial intelligence
月光博客
月光博客
IT之家
IT之家
D
DataBreaches.Net
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
D
Docker
The GitHub Blog
The GitHub Blog
B
Blog
V
Visual Studio Blog
博客园 - Franky
N
Netflix TechBlog - Medium
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
博客园 - 聂微东
U
Unit 42

小松鼠的博客

记录一次线上k8s工作节点无法创建容器的问题排查思路与解决办法 记一次线上GoLang项目OOM排查过程 从LastPass转向拥抱开源KeePass的心路历程 故障定位与 AI 结合前后端编码实践 FileBeat收集nginx-ingress-controller日志 K8s云原生环境下文件描述符占用过高查询思路 2024年最新关闭火绒安全工具的开机自启方法 Kubernetes任务调度实践-Go语言实现Job和CronJob对比分析 离线更新k8s环境下的trivy漏洞库方法 在Vue2中自定义Switch组件并实现父子组件双向数据绑定 关于docker jdk1.8镜像中的GB18030-2022标准支持及验证 Go框架gin中的session存储gin-contrib-sessions和go-session 关于修改node_module中的源码问题记录 docker-compose网络和内网服务IP冲突问题 慎用存储过程:一条语句引发的数据库存储100%占用 Spring Boot中4种文件下载方法的实现 避坑-不能将specific类型的gitlab-runner改变为share类型 Docker compose中的MySQL主从复制模式和percona-toolkit工具使用 在minio中开启https访问以及使用rclone备份minio桶 在多机Docker环境下部署Choerodon的解决方案 Prometheus中Monitor添加对SpringBoot Actuator的Basic认证 在Nginx的容器镜像中隐藏Nginx的Server响应头 K8s中的两种nginx-ingress-controller及其区别 两个docker工具:runlike和whaler Grafana中的邮件报警和截图插件grafana-image-enderer K8s中externalName-service和services-without-selectors maven配置文件settings.xml中的一些概念总结 K8s中flexvolume插件驱动的安装 K8s中的coredns无法解析svc问题排查 K8s中使用Ingress访问请求体过大问题解决
使用Go语言接入Choerodon实现基于OAuth2的统一身份认证登录
ycyin · 2023-08-18 · via 小松鼠的博客

2023年8月17日大约 5 分钟Web技术&安全GoOAuth2Choerodon


本文主要记录使用Go语言的 <golang.org/x/oauth2> 库接入OAuth2实现统一身份认证登录的过程。由于我们内网部署了Choerodon开源版1.0,所以OAuth2的认证服务端直接使用Choerodon的OAuth服务。即在我们的自研服务接入Choerodon的OAuth2服务实现统一身份认证登录。

Choerodon的OAuth服务是对Spring Security的二开,和接入Spring Security的OAuth2流程类似。并且OAuth2是一种用于授权的开放标准,理论上所有的OAuth2服务端的实现都是大同小异的,规范RFC6749

实现步骤

OAuth有4种授权模式,我们选择最为安全的授权码模式,如下图:

这是一个简化的流程图,User代表我们用户和浏览器端,Client代表我们的Go程序即要接入OAuth2的客户端,OAuth2 Provider代表授权服务端这里就是Choerodon OAuth服务(以下简称OAuth2服务)。

1. 配置OAuth2

首先,我们需要在OAuth2服务注册我们的客户端,得到配置OAuth2的一些参数,如ClientIDClientSecretRedirectURL等。这些参数将用于在与OAuth2服务进行交互时进行身份验证。

客户端初始化认证处理器

var (
	issuerUri = 'http://127.0.0.1:8000'
	logoutUrl = 'https://api.c7n.x/oauth/logout?access_token=%s'
	conf      *oauth2.Config
)

// 创建认证处理器
func init() {
	E := gin.New()
	E.Use(gin.Recovery())
	
    // ....
    
	auth := E.Group("/auth")

	// 重定向到认证
	auth.GET("/login", Redirect)
	// 认证后回调
	auth.GET("/callback", CallBack)
	// 本地登出
	auth.GET("/logout", LogOut)

	conf = &oauth2.Config{
		ClientID:     'test', // 在OAuth2服务注册的客户端ID和Secret
		ClientSecret: 'testsecretAbv23lnH',
		RedirectURL:  'http://127.0.0.1:8000/auth/callback', // 在OAuth2服务注册时需要填写,请求授权码时需要带上这个地址,OAuth2服务生成授权码后需要回调这个地址
		Scopes:       []string{"default"},
		Endpoint: oauth2.Endpoint{
			AuthURL:   'https://api.c7n.x/oauth/oauth/authorize', // 客户端请求OAuth2服务授权地址
			TokenURL:  'https://api.c7n.x/oauth/oauth/token', // 客户端请求OAuth2服务授权码换取token地址
			AuthStyle: 0,
		},
	}
}

2. 重定向到认证页面

func Redirect(c *gin.Context) {
	state, err := randString(16)
	if err != nil {
		log.Fatal(err)
	}
	util.SetSession(c, "state", state)
	c.Redirect(302, conf.AuthCodeURL(state))
}

通过创建路由,我们可以将用户重定向到OAuth2服务的认证授权页面,以获取授权码。在代码中的Redirect函数中,我们可以看到使用conf.AuthCodeURL(state)来生成认证页面的URL同时带上state参数,并将用户重定向到该URL(https://api.c7n.x/oauth/oauth/authorize)。

3. 认证授权回调处理

func CallBack(c *gin.Context) {
	state := util.GetSession(c, "state")
	util.RemoveSession(c, "state")
	//log.Println(state)
	log.Println(c.Query("state"))
	if c.Query("state") != state {
		c.JSON(http.StatusBadRequest, gin.H{
			"code": "BadRequest",
			"msg":  "state did not match",
		})
		c.Abort()
		return
	}
	// 创建 HTTP 客户端,并配置 Transport 忽略 x509 错误
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	sslcli := &http.Client{Transport: tr}
	ctx := context.TODO()
	ctx = context.WithValue(ctx, oauth2.HTTPClient, sslcli)

	oauth2Token, err := conf.Exchange(ctx, c.Query("code"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": "InternalServerError",
			"msg":  "Failed to exchange token: " + err.Error(),
		})
		c.Abort()
		return
	}

	client := conf.Client(ctx, oauth2Token)

	req, _ := http.NewRequest("GET", "https://api.c7n.x/v1/users/queryPermissions", nil)

	if resp, err := client.Do(req); err == nil {
		defer resp.Body.Close()
		permissionInfo, _ := io.ReadAll(resp.Body)
		// 组装用户和权限信息并保存到session中
		util.SetSession(c, "permissionInfo", permissionInfo)
		util.SetSession(c, "accessToken", oauth2Token.AccessToken)
		u := util.GetSession(c, "url")

		// 跳转到主页
		c.Redirect(302, u.(string))
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": "InternalServerError",
			"msg":  "Failed to get userinfo: " + err.Error(),
		})
		c.Abort()
		return
	}
}

用户在OAuth2服务授权成功后,将会被重定向回你的应用,并在URL参数中携带授权码和state参数。在CallBack函数中,我们首先校验传递的授权码是否与之前生成的state匹配,以确保安全性。然后,使用OAuth2的conf.Exchange方法来交换授权码以获取访问令牌。

4. 获取和处理用户权限信息

一旦我们获得了访问令牌,我们可以使用它来进行API请求,访问受保护的资源(比如获取用户的权限信息)。在代码中,我们可以看到构建HTTP请求并在请求头中添加访问令牌,然后发送请求以获取用户的权限信息。

通过解析返回的权限信息,我们可以获取用户的权限列表,并进行一些操作。在这个示例中,我们获取到用户权限信息并进行了保存。

5. 登出功能

func LogOut(c *gin.Context) {
	accessToken := util.GetSession(c, "accessToken")
	util.ClearSession(c)
	// 不为空就返回登出地址
	if accessToken != nil {
		c.JSON(http.StatusOK, gin.H{
			"logoutUrl": fmt.Sprintf(logoutUrl, accessToken.(string)),
		})
	}
	// 为空直接跳转到主页
	c.Redirect(302, issuerUri)
}

LogOut函数中,我们实现了用户登出的功能。这个函数会清除会话信息,并根据用户的访问令牌构建OAuth2服务的登出URL返回给前端由前端请求OAuth2服务进行登出以销毁accessToken。

6.其它思考

  1. 上述示例代码将用户的信息和token直接保存在Session中,可能在某些场景下(比如移动APP等非Web场景)就不能直接保存在Session中了,可以考虑在客户端置换Jwt来实现。
  2. 上述流程并未实现刷新Token的功能,在session失效后需要重新授权和登录。其实在授权服务返回AccessToken时同时也返回了RefreshToken,我这里没有做处理。

本文主要参考以下文章完成,部分内容值得研读,记录一下:

  1. RFC Reader - An online reader for IETF RFCs
  2. 理解OAuth 2.0 - 阮一峰的网络日志
  3. 理解OAuth2.0认证与客户端授权码模式详解-腾讯云开发者社区
  4. 如何验证和刷新 OAuth2 Token - 知乎
  5. OAuth2.0用refresh_token更新access_token令牌_reuserefreshtokens_CSDN博客
  6. 泛微移动办公云OA(eteams):竹云统一身份认证对接接口文档
  7. Spring Security Oauth2之scope作用域机制使用详解_oauth2 scope-CSDN博客
  8. 汉得焱牛开放平台 (hand-china.com)-OAuth认证服务
  9. 汉得焱牛开放平台 (hand-china.com)-身份认证标准登录
  10. https://github.com/open-hand/choerodon-oauth/blob/1.0.3/src/main/java/org/hzero/oauth/config/AuthorizationServerConfig.java