Go CAS认证实现 gin+cas认证 单点登录

实现cas登陆后可以查看这篇文章:gin CAS登陆后使用session保持登陆状态

背景

在用golang对接cas认证时,发现网上的资料都是使用Golang的 cas 客户端的包:gopkg.in/cas.v2实现。经测试,在接入 cas 服务器后,不断跳转,重定向而不继续执行认证成功后的操作

为了解决该问题,基于cas认证原理,自己实现了一个cas认证客户端

定义响应结构体

首先,我们需要定义 CAS 认证成功后的响应结构体:

// model/cas.go
type CasServiceResponse struct {
	XMLName xml.Name `xml:"serviceResponse"`
	Data    struct {
		SFRZH      string `xml:"user"`
		Attributes struct {
			Uid      string `xml:"uid"`
			UserName string `xml:"userName"`
		} `xml:"attributes"`
	} `xml:"authenticationSuccess"`
}

编写 CAS 认证逻辑

接下来,我们编写 CAS 认证的核心逻辑:

// utils/cas.go
package utils

import (
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"io"
	"net/http"
	"roomlive-go/global"
	"roomlive-go/model/cas"
	"roomlive-go/model/user"
	"strings"
)

func IsAuthentication(w http.ResponseWriter, r *http.Request, casServerUrl string) (bool, *cas.CasServiceResponse) {
	if !hasTicket(r) {
		redirectToCasServer(w, r, casServerUrl)
		return false, nil
	}
	localUrl := getLocalUrl(r)
	ok, err, res := validateTicket(localUrl, casServerUrl)
	global.SYSLOG.Debug("cas validateTicket", zap.Bool("ok", ok), zap.Error(err), zap.Any("res", res))
	if !ok {
		redirectToCasServer(w, r, casServerUrl)
		return false, nil
	}
	global.SYSLOG.Info("user authenticated", zap.String("sfrzh", res.Data.SFRZH))
	return true, res
}
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, error, *cas.CasServiceResponse) {
	casServerUrl = casServerUrl + "/serviceValidate?service=" + localUrl
	res, err := http.Get(casServerUrl)
	if err != nil {
		return false, err, nil
	}
	defer res.Body.Close()
	data, err := io.ReadAll(res.Body)
	if err != nil {
		return false, err, nil
	}
	casRes, err := ParseCasUserInfo(data)
	if err != nil {
		return false, err, nil
	}
	if casRes.Data.SFRZH == "" {
		return false, errors.New("authentication failed"), nil
	}
	//dataStr := string(data)
	//if !strings.Contains(dataStr, "cas:authenticationSuccess") {
	//	return false, err, nil
	//}
	return true, nil, casRes
}

/*
从请求中获取访问路径
*/
func getLocalUrl(r *http.Request) string {
	scheme := "http://"
	if r.TLS != nil {
		scheme = "https://"
	}
	url := strings.Join([]string{scheme, r.Host, r.RequestURI}, "")
	fmt.Printf("url: %v\n", url)
	slice := strings.Split(url, "?")
	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)
	return len(t) != 0
}

func ParseCasUserInfo(data []byte) (*cas.CasServiceResponse, error) {
	var casResponse cas.CasServiceResponse
	if err := xml.Unmarshal(data, &casResponse); err != nil {
		return nil, err
	}
	return &casResponse, nil
}

func GetUser(c *gin.Context) (*user.User, error) {
	if res, exists := c.Get("casResponse"); !exists {
		return nil, errors.New("cas authentication failed")
	} else {
		casRes := res.(*cas.CasServiceResponse)
		waitUser := &user.User{
			UserName: casRes.Data.Attributes.UserName,
			SFRZH:    casRes.Data.SFRZH,
		}
		return waitUser, nil
	}
}

在 Gin 中间件中应用

将 CAS 认证逻辑应用到 Gin 的中间件中:

func CASMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		isAuth, casResponse := utils.IsAuthentication(c.Writer, c.Request, utils.CASServer)
		if !isAuth {
			c.Abort()
			return
		}
		c.Set("casResponse", casResponse)
		c.Next()
		return
	}
}

总结

这里只根据cas原理实现了一个基本的CAS客户端认证流程,包括了请求检查、重定向处理、票据验证和用户信息解析,并通过Gin中间件集成到了Web应用程序中。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go微服务开发是利用Go语言进行微服务架构的开发方式。在这个问题中,使用了gin、grpc和etcd进行重构grpc-todolist项目。 Gin是一个轻量级的Web框架,使用它可以快速构建高性能的Web应用程序。它具有简单易用、性能出色和灵活的特点。在微服务开发中,Gin可以作为HTTP服务器框架,处理和响应客户端的HTTP请求。 gRPC是一种高性能、开源的远程过程调用(RPC)框架。它支持多种编程语言,并使用带有协议缓冲区的Google Protocol Buffers进行数据交换。在微服务架构中,gRPC可以用于服务之间的通信,通过定义接口和消息格式,实现服务间的数据传输和调用。 Etcd是一个高可靠、分布式的键值存储系统。它使用Raft一致性算法来保证数据的可靠性和一致性。在微服务开发中,Etcd可以作为服务发现和配置管理的工具,用于注册和发现各个微服务的信息。 对于重构grpc-todolist项目来说,使用gin可以将原有的HTTP接口改写为更加高性能的接口,提高整个系统的性能。通过使用gRPC,可以将原有的接口定义为gRPC接口,实现服务间的高效通信,并且易于扩展和维护。同时,借助Etcd实现服务注册和发现,提高系统的可用性和灵活性。 总而言之,通过使用gin、grpc和etcd对grpc-todolist项目进行重构,可以提高系统性能、扩展性和可维护性。这种微服务开发方式能够更好地适应大规模分布式系统的需求,使得系统更加稳定和可靠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值