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

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog RSS Feed
GbyAI
GbyAI
Google DeepMind News
Google DeepMind News
B
Blog
博客园 - 司徒正美
Vercel News
Vercel News
A
About on SuperTechFans
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
Recent Announcements
Recent Announcements
S
SegmentFault 最新的问题
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
L
LINUX DO - 热门话题
Y
Y Combinator Blog
F
Full Disclosure
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
MongoDB | Blog
MongoDB | Blog
The Cloudflare Blog
The Hacker News
The Hacker News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
Arctic Wolf
Scott Helme
Scott Helme
V
Visual Studio Blog
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tor Project blog
P
Privacy International News Feed
Spread Privacy
Spread Privacy
G
GRAHAM CLULEY
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
PCI Perspectives
PCI Perspectives
小众软件
小众软件
博客园 - 【当耐特】
Cloudbric
Cloudbric
S
Secure Thoughts
L
LINUX DO - 最新话题
Google Online Security Blog
Google Online Security Blog
T
Troy Hunt's Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
W
WeLiveSecurity
V
Vulnerabilities – Threatpost
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位

小松鼠的博客

记录一次线上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访问请求体过大问题解决 关于k8s中对于SpringBoot应用TCP类型的就绪探针不准确的问题发现 K8s中的环境变量与应用程序的对应关系与操作 SpringMVC4升级为SpringBoot2实战 在Vmware中Ubuntu22.04的vm-tools和网络问题 修改k8s节点主机名并重新加入集群 离线安装Grafana插件 Spring Data Jpa 中使用CriteriaBuilder动态拼接SQL 在SpringBoot项目配置Liquibase数据库版本管理 记录Vue中父子组件传值的实战应用 实现单例模式的8种方法 三种常用的生产者消费者模式实现 使用两个线程交替打印0-100的奇偶数 关于部署于JBoss5中的Spring应用获取项目真实部署路径的问题 获取下一个完全对称日 通过短信验证码验证修改密码的解决方案 在Win10中使用Win+R快速启动软件 使用RSA加解密时注意Cipher.getInstance(String var0,Provider var1)提供的Provider是否正确 在RestEasy2.x中解决接口重复提交问题 几道简单的CTF题目思路 重温Spring---Spring事务控制与基于XML和注解的配置方法 重温Spring---Spring AOP基于XML和注解的配置 重温Spring---AOP动态代理和Spring AOP及其基本原理 重温Spring---Spring IOC基于XML和注解的配置和比较 在Windows10中安装MySQL5.7 Zip版本及常用配置 重温Spring---使用Spring IOC解决程序耦合 策略模式与责任链模式实战应用 Linux上直接打开war包修改文件 在Windows上运行两个微信的简单脚本 ThreadPoolExecutor的使用方法与分页查询数据实例 IDEA中Shelve Changes 和 Git Stash 通过resteasy发布RESTful接口 解决前端请求后台接口,后台报错Can not deserialize instance of java.util.ArrayList out of START_OBJECT token 使用VBA脚本汇总Excel文档 使用Jenkins+GitLab实现自动部署vue项目 Kubernetes:使用hostPath挂载nginx集群的配置文件和html 彻底搞定VirtualBox虚拟机的网络设定 在Docker中安装MySQL5.7并开启远程访问(附授权和修改密码方式) 利用git命令和java文件流 获取自己改动过的文件 浅谈Spring定时任务的使用(Scheduled注解) 在Spring项目简单配置Flyway(V4.2版本)数据库版本管理 解决Spring单元测试中因外键关联导致的失败integrity constraint violation:foreign key no action Redis安装与哨兵模式配置入门 关于Vue中使用Element-UI样式row-class-name失效的问题 Element-UI中实现可动态增加行列和可编辑单元格的表格 Windows系统查看端口占用、结束进程方法和命令 层次分析法(AHP)分析步骤与计算方法 源码分析之解决layui框架重载表格时额外参数不清空的问题 Spring Data Jpa 返回自定义对象(实体部分属性、多表联查) 如何将一个jar放到本地maven仓库中 关于SSM项目停止Tomcat时Log4j出现java.lang.NoClassDefFoundError: 获取el-table单元格值并根据该值对元素自定义样式渲染 解决Git每次push都要重新输入账号密码和HttpRequestException encountered的问题 解决前后端分离项目中Vue不带cookies的问题 SSM集成Shiro自定义权限过滤器不执行解决方案 SSM集成Shiro不进入自定义Realm的doGetAuthorizationInfo的解决方案 Vue+SSM中使用Token验证登录 Git拉代码推送代码提示密码错误如何修改 Git配置SSH Key(Git配置多个账户) 安装Tomcat服务器以及错误汇总(tomcat8.0、jdk8) 关于我
使用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