gopkg.in/cas.v1代码阅读

最开始可能接触到cas的地方是刚毕业,在一个想要在网上卖家具的小公司,后来因为没有找到用户的痛点,大量员工流失,最后黄了。那时,一个比较有经验的工程师,说将来有可能有多个后台,就做一个类似百度内部的passport吧,他当时设计了两天,原理上类似,用户请求Protect app, Protect app跳转到passport,passport给用户令牌,用户通过令牌访问Protect app,到这里,我就不记得当时为什么这个工程师没有做下去了,可能没有原理的支撑吧,做起来觉得用不长久,后来上线的后台在登陆这里,确实很麻烦。以后如果在参与创业,这个一定得找个靠谱的工程师,写一下。

第二次接触是在小米做话费充值,后台需要接入小米同一个CAS登陆服务,有一定技术水准的公司,确实有很多提高生产效率的内部服务,当然这种水准的公司导致效率下降的因素也不少。Java通过pom.xml中引入给Java封装的CAS client包,然后在web.xml上配置下filter就行了。这个时候接入的也是一知半解,只是知道,所有请求过CAS server,得到允许CAS server会跳转到Protect app上。

当前接触CAS是因为在小米网做后台,后台很简单,但使用语言是go,查了一下jasig,没找到官方的go client,通过google查到cas.v1这个包,看着写的还比较认真,试验了一下其中的example,决定使用这个。

cas.v1内部封装了一个handler.go,其中的clientHandler实现了ServeHTTP方法,这是因为所有请求都需要过cas client中的逻辑,然后再执行用户自身定义的handler,这个造成了不小的麻烦,因为为了快速开发出来后台,我们使用了beego,beego将选择handler的逻辑封装带框架内部,不需要app developer自己处理请求,当然会提供类似filter的机制,但即便有这个机制,cas.v1中在handler.go中ServeHTTP中的预处理部分也不能单独给filter中的方法提供调用。因为它会在执行完ServeHTTP后,直接clear调当前请求和Client的关系,导致在其他方法调用不到,当然我想到直接给package外的app,提供方法调用clear即可,但是,我想尽量还是不要在通用库上做太大的修改。所以,我选择自己将预处理封装成beego_helpers.go,代码如下:

package cas

import (
    "net/http"

    "github.com/golang/glog"
)

type BeegoCASData struct {
    userName string
}

func (bcd *BeegoCASData) GetUserName() string {
    return bcd.userName
}

func ServeBeego(w http.ResponseWriter, r *http.Request, c *Client) *BeegoCASData {
    if glog.V(2) {
        glog.Infof("cas: handling %v request for %v", r.Method, r.URL)
    }

    setClient(r, c)
    defer clear(r)

    c.getSession(w, r)

    if !IsAuthenticated(r) {
        RedirectToLogin(w, r)
        return nil
    }

    if r.URL.Path == "/logout" {
        RedirectToLogout(w, r)
        return nil
    }

    return &BeegoCASData{
        userName: Username(r),
    }
}

当然这个方法,比较挫,但能在不改动cas.v1库的情况下,满足自己的需求。

下面说下,我对于CAS client封装的一点理解。CAS-Protocol一般可以用参考这个协议进行开发,可用并不难,但是写好middleware(中间件,和github在讨论这个问题时学到的)有点难度,因为涉及到log和错误的封装,以及提供给使用者最大化的便利。

CAS交互中,需要Protect app做的工作就是:

  • 通过cookie、session、内存中存储的ticket和授权resp的map判断,用户是否可以访问
  • 如果不可以访问,跳转到CAS server,用户会和server进行登陆验证操作,server会给用户ticket
  • server完成和用户的交互,就302到Protect app,并带上ticket
  • Protect app通过ticket和CAS server交互,获取用户信息,并存储在内存中,当然可以自己实现存储在redis中(如果Protect app需要是分布式的)
  • Protect完成和server的交互,但需要用户的browser和app存储的用户信息建立map(以此作为用户是否需要登陆的依据),这里通过cookie中记录sessionkey,session记录ticket信息,这里用session中间存储一下ticket的原因,我还没明白,可能是为了安全(防君子,防不了小人)

以上可以参考CAS-Protocol中的时序图。

后续本来想写一篇怎么修改cas.v1能更合适的支持使用框架的app。我简单想了一下,没什么复杂的,只要将handler.go中ServeHTTP中的defer clear(r)修正成交给app清理即可。不过在我读cas.v1的代码时,也发现有些方法所处的文件是不合适的。

  • handler中的isSingleLogoutRequest应该放到http_helpers.go中作为通用方法
  • http_helpers.go中的读取AuthenticationResponse中属性的一系列方法应该放到,service_response.go中

不过像我一贯的风格,不用特别在意无伤大雅的细节。

最后附上我和框架作者的讨论:https://github.com/go-cas/cas/issues/6

不知道后续作者是否会为使用框架的app,做优化。

gopkg.in/cas.v1代码阅读
Share this