• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Golang实践录使用gin实现 cas 单点登录

武飞扬头像
李迟
帮助1

本文介绍使用 Golang 语言实现 cas 单点登录。

起因

新年伊始,上班第一天收到消息。原来那台用于部署内部网页工具的服务器因安全问题被停止使用,需更新服务器部署,但从中带出一个问题,那个应用服务程序必须使用登录平台做跳转,不能直接通过URL访问网页。

时间急任务不熟悉,所幸搜索发现有现成的方案。因同时做维护方面的事,共了几天时间完成。

分析

经多方请教,得知线上服务均已使用某平台统一认证、转发,使用同一 cas 服务器。而该应用服务程序只是漏网之鱼。

应用服务使用 bootstrap Golang 实现,不是主流的那个前后端分离架构,其它部门现有的模块不能直接使用,而再实现前后端分离,又要花费人力时间,且自己也不熟悉。因此只能自己找方法。

经分析,要做的事是:在 gin 接口中,当请求页面时,先请求 cas 服务器,如认证通过,再继续后续逻辑。

使用cas.v2包实现

代码

Golang 有 cas 客户端的包:gopkg.in/cas.v2,使用即可。

思路如下:

  • 调用gin.BasicAuth()创建需认证的路由组,账号密码由配置文件读取(可多个,本文示例略去)。
  • 在该路由组下指定相应的URL及响应函数。
  • 响应函数从gin.Context获取账号,与配置文件的对比,如不同,认证不通过,返回。否则继续后面处理流程。

关键代码示例:

import (
	"conf"
	"net/http"
	"net/url"

	"github.com/gin-gonic/gin"
	"gopkg.in/cas.v2"

)
// 说明:测试用的cas服务器,URL是带有login的,所用的cas客户端会自动加上,
// 故下面的URL不再使用
// conf.CasUrl="https://192.168.18.18:8180/cas/"
// cas检测及登录
func checkCas(ctx*gin.Context) {
	if conf.CasUrl == "" {
		return
	}
	u, _ := url.Parse(conf.CasUrl)
	client := cas.NewClient(&cas.Options{URL: u})
	h := client.HandleFunc(func(w http.ResponseWriter, r *http.Request) {
		if !cas.IsAuthenticated(r) { // 没有授权,跳转之
			// klog.Println("go to cas url ", conf.CasUrl)
			client.RedirectToLogin(w, r)
			return
		}
		// else {
		// 	klog.Println("cas login ok")
		// }
	})
	h.ServeHTTP(ctx.Writer, ctx.Request) // 必须要这句
}

func pageIndex(ctx *gin.Context) {
	checkCas(ctx)

	page := "index.html"

	ctx.HTML(http.StatusOK, page, gin.H{
		"Title": conf.AppName,
	})
}
学新通

问题

经测试,发现接入生产环境的 cas 服务器后,观察gin 日志,不断跳转,即循环重定向而不继续执行认证成功后的操作。附录的文章评论也有类似问题。因无法解决,只好继续寻找客户端。

自实现

代码

幸好,无意间发现go cas实现这篇文章,里面给出了自行实现的cas客户端代码,于是借鉴使用之。此处列出所有代码,方便与文章的代码对比。

import (
	"io/ioutil"
	"net/http"
	"strings"

	"webdemo/pkg/klog"
)

/*
判断当前访问是否已认证
*/
func IsAuthentication(w http.ResponseWriter, r *http.Request, casServerUrl string) bool {
	if !hasTicket(r) {
		klog.Println("cas debug has no ticket...")
		redirectToCasServer(w, r, casServerUrl)
		return false
	}

	localUrl := getLocalUrl(r)
	klog.Println("cas debug  IsAuthentication hasticket.... url: ", localUrl)
	if !validateTicket(localUrl, casServerUrl) {
		klog.Println("cas debug  validateTicket no ok")
		redirectToCasServer(w, r, casServerUrl)
		return false
	}
	return true
}

/*
重定向到CAS认证中心
*/
func redirectToCasServer(w http.ResponseWriter, r *http.Request, casServerUrl string) {
	casServerUrl = casServerUrl   "login?service="   getLocalUrl(r)
	http.Redirect(w, r, casServerUrl, http.StatusFound)
}

/*
验证访问路径中的ticket是否有效
*/
func validateTicket(localUrl, casServerUrl string) bool {
	casServerUrl = casServerUrl   "serviceValidate?service="   localUrl

	klog.Println("cas debug validateTicket casServerUrl.... ", casServerUrl)
	res, err := http.Get(casServerUrl)
	if err != nil {
		klog.Println("cas debug validateTicket 111 BUT RETURN TRUEEEEEE", err.Error())
		//return false
		return true //
	}
	defer res.Body.Close()

	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		klog.Println("cas debug validateTicket 222 ", err.Error())
		return false
	}

	dataStr := string(data)
	if !strings.Contains(dataStr, "cas:authenticationSuccess") {
		klog.Println("cas debug validateTicket 333 ", err.Error())
		return false
	}

	klog.Println("cas debug  444 ", err.Error())
	return true
}

/*
从请求中获取访问路径
*/
func getLocalUrl(r *http.Request) string {
	scheme := "http://"
	if r.TLS != nil || 1 == 0 { // 经测试,有时输入了https,还得到还是http,这里可强制之
		scheme = "https://"
	}
	url := strings.Join([]string{scheme, r.Host, r.RequestURI}, "")
	slice := strings.Split(url, "?")
	//klog.Println("----- slice", slice)
	if len(slice) > 1 {
		localUrl := slice[0]
		urlParamStr := ensureOneTicketParam(slice[1])
		url = localUrl   "?"   urlParamStr
	}
	return url
}

/*
处理并确保路径中只有一个ticket参数
*/
func ensureOneTicketParam(urlParams string) string {
	if len(urlParams) == 0 || !strings.Contains(urlParams, "ticket") {
		return urlParams
	}

	sep := "&"
	params := strings.Split(urlParams, sep)

	newParams := ""
	ticket := ""
	for _, value := range params {
		if strings.Contains(value, "ticket") {
			ticket = value
			continue
		}

		if len(newParams) == 0 {
			newParams = value
		} else {
			newParams = newParams   sep   value
		}

	}
	newParams = newParams   sep   ticket
	return newParams
}

/*
获取ticket
*/
func getTicket(r *http.Request) string {
	return r.FormValue("ticket")
}

/*
判断是否有ticket
*/
func hasTicket(r *http.Request) bool {
	t := getTicket(r)
	//klog.Println("----- hasTicket", t)
	return len(t) != 0
}

学新通

问题

依然有循环重定向问题,但通过修改绕过。但使用其它 cas 服务器测试,又是正常的。着实不知何故。

测试

由于接入现有的cas服务器,需使用https协议,在gin中使用该协议也比较简单,因证书问题,只能在现有nginx服务器中做代理实现https协议。

小结

目前未找到根本解决方法,只是绕过 cas 循环的问题。

附录

参考:

go gin框架使用cas单点登录

go cas实现

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfbeci
系列文章
更多 icon
同类精品
更多 icon
继续加载