searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

CAS单点登录对接实践

2023-09-26 08:53:38
86
0

介绍

概念

CAS(Central Authentication Service)

  • 对应协议,有v1.0,v2.0,v3.0
  • 对应apereo基金会的开源项目

SSO(Single-Sign-On)/ SLO(Single Log-Out)

  • 单点登录
  • 单点登出

CAS server

  • 服务端,单独部署的cas应用

CAS client

  • 需要利用统一认证的其他应用

几种协议

  • CAS
  • OAuth2.0
  • SAML
  • OpenID

原理

CAS协议

几种票据

  • ticket-granting ticket (TGT)
    • 俗称大令牌,或者说票根,他可以签发ST
  • ticket-granting cookie (TGC)
    • cas server的url下会存储的TGC cookie
    • 是给cas server看的
  • service ticket (ST)
    • CAS server回传的,在URL上的票据
    • 是给app看的
    • app会去后台校验它

登录流程

  • 可以通过一个通俗的例子来理解:
    • 假设有一个大景区,会有不同的小景区,共用一个售票大厅(CAS系统)
    • 不同的App可以理解为不同的小景区
    • 用户/游客来到某一小景区的入口,准备进入的时候,会被入园管理人员告知(重定向)到售票大厅的售票窗口
    • 游客在售票窗口,登记认证了自己的身份之后,拿到了一张景区门票(ST)
    • 然后游客拿着门票,再来到景区入口,入园管理人员会拿着门票进行验票
    • 通过验票的游客可以进入园区,且门票是限时不限次,有效期内再次入园,只用验门票
    • 用户在进入其他小景区的时候,第一次依然会被告知(重定向)去售票大厅,但是之前已经登记了游客信息,可以直接快速通道拿到门票

实操

搭建一个CAS server

docker pull apereo/cas:6.6.6
docker run  --name cas -p 8443:8443 -p 8442:8080  55.235.30.100:60000/library/apereo/cas:6.6.6  /bin/sh /cas-overlay/bin/run-cas.sh


# 会有certificates的问题
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 10000
docker cp debug.keystore cas:/etc/cas/thekeystore

配置

/etc/cas/services/app-8900.json

  • 这里可以配置支持的平台URL规则,仅支持自己的平台才能跳转CAS server。
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|http)://.*",
  "name" : "app",
  "id" : 8900,
  "logoutType" : "BACK_CHANNEL",
  "logoutUrl" : "localhost:8900/logout_callback"
}

/etc/cas/config/cas.properties

  • cas.properties
cas.service-registry.core.init-from-json=true
cas.service-registry.json.location=file:/etc/cas/services

FastAPI实现案例

依赖包

  • pip3 install python-cas

代码

  • FastAPI的简单例子:
cas_client = CASClient(
    version=3,
    service_url='127.0.0.1:8002/login?next=%2Fprofile',
    server_url='127.0.0.1:8000/cas/'
)
app.add_middleware(SessionMiddleware, secret_key="!secret")



@app.get('/')
async def index(request: Request):
    return RedirectResponse(request.url_for('login'))


@app.get('/profile')
async def profile(request: Request):
    print(request.session.get("user"))
    user = request.session.get("user")
    if user:
        return HTMLResponse('Logged in as %s. <a href="/logout">Logout</a>' % user['user'])
    return HTMLResponse('Login required. <a href="/login">Login</a>', status_code=403)


@app.get('/login')
def login(
    request: Request, next: Optional[str] = None,
    ticket: Optional[str] = None):
    if request.session.get("user", None):
        # Already logged in
        return RedirectResponse(request.url_for('profile'))

    # next = request.args.get('next')
    # ticket = request.args.get('ticket')
    if not ticket:
        # No ticket, the request come from end user, send to CAS login
        cas_login_url = cas_client.get_login_url()
        print('CAS login URL: %s', cas_login_url)
        return RedirectResponse(cas_login_url)

    # There is a ticket, the request come from CAS as callback.
    # need call `verify_ticket()` to validate ticket and get user profile.
    print('ticket: %s', ticket)
    print('next: %s', next)

    user, attributes, pgtiou = cas_client.verify_ticket(ticket)

    print(
        'CAS verify ticket response: user: %s, attributes: %s, pgtiou: %s',
        user, attributes, pgtiou)

    if not user:
        return HTMLResponse('Failed to verify ticket. <a href="/login">Login</a>')
    else:  # Login successfully, redirect according `next` query parameter.
        response = RedirectResponse(next)
        request.session['user'] = dict(user=user)
        return response


@app.get('/logout')
def logout(request: Request):
    redirect_url = request.url_for('logout_callback')
    cas_logout_url = cas_client.get_logout_url(redirect_url)
    print('CAS logout URL: %s', cas_logout_url)
    return RedirectResponse(cas_logout_url)


@app.get('/logout_callback')
def logout_callback(request: Request):
    # redirect from CAS logout request after CAS logout successfully
    # response.delete_cookie('username')
    request.session.pop("user", None)
    return HTMLResponse('Logged out from CAS. <a href="/login">Login</a>')
0条评论
0 / 1000
刘****雷
3文章数
0粉丝数
刘****雷
3 文章 | 0 粉丝
刘****雷
3文章数
0粉丝数
刘****雷
3 文章 | 0 粉丝
原创

CAS单点登录对接实践

2023-09-26 08:53:38
86
0

介绍

概念

CAS(Central Authentication Service)

  • 对应协议,有v1.0,v2.0,v3.0
  • 对应apereo基金会的开源项目

SSO(Single-Sign-On)/ SLO(Single Log-Out)

  • 单点登录
  • 单点登出

CAS server

  • 服务端,单独部署的cas应用

CAS client

  • 需要利用统一认证的其他应用

几种协议

  • CAS
  • OAuth2.0
  • SAML
  • OpenID

原理

CAS协议

几种票据

  • ticket-granting ticket (TGT)
    • 俗称大令牌,或者说票根,他可以签发ST
  • ticket-granting cookie (TGC)
    • cas server的url下会存储的TGC cookie
    • 是给cas server看的
  • service ticket (ST)
    • CAS server回传的,在URL上的票据
    • 是给app看的
    • app会去后台校验它

登录流程

  • 可以通过一个通俗的例子来理解:
    • 假设有一个大景区,会有不同的小景区,共用一个售票大厅(CAS系统)
    • 不同的App可以理解为不同的小景区
    • 用户/游客来到某一小景区的入口,准备进入的时候,会被入园管理人员告知(重定向)到售票大厅的售票窗口
    • 游客在售票窗口,登记认证了自己的身份之后,拿到了一张景区门票(ST)
    • 然后游客拿着门票,再来到景区入口,入园管理人员会拿着门票进行验票
    • 通过验票的游客可以进入园区,且门票是限时不限次,有效期内再次入园,只用验门票
    • 用户在进入其他小景区的时候,第一次依然会被告知(重定向)去售票大厅,但是之前已经登记了游客信息,可以直接快速通道拿到门票

实操

搭建一个CAS server

docker pull apereo/cas:6.6.6
docker run  --name cas -p 8443:8443 -p 8442:8080  55.235.30.100:60000/library/apereo/cas:6.6.6  /bin/sh /cas-overlay/bin/run-cas.sh


# 会有certificates的问题
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 10000
docker cp debug.keystore cas:/etc/cas/thekeystore

配置

/etc/cas/services/app-8900.json

  • 这里可以配置支持的平台URL规则,仅支持自己的平台才能跳转CAS server。
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|http)://.*",
  "name" : "app",
  "id" : 8900,
  "logoutType" : "BACK_CHANNEL",
  "logoutUrl" : "localhost:8900/logout_callback"
}

/etc/cas/config/cas.properties

  • cas.properties
cas.service-registry.core.init-from-json=true
cas.service-registry.json.location=file:/etc/cas/services

FastAPI实现案例

依赖包

  • pip3 install python-cas

代码

  • FastAPI的简单例子:
cas_client = CASClient(
    version=3,
    service_url='127.0.0.1:8002/login?next=%2Fprofile',
    server_url='127.0.0.1:8000/cas/'
)
app.add_middleware(SessionMiddleware, secret_key="!secret")



@app.get('/')
async def index(request: Request):
    return RedirectResponse(request.url_for('login'))


@app.get('/profile')
async def profile(request: Request):
    print(request.session.get("user"))
    user = request.session.get("user")
    if user:
        return HTMLResponse('Logged in as %s. <a href="/logout">Logout</a>' % user['user'])
    return HTMLResponse('Login required. <a href="/login">Login</a>', status_code=403)


@app.get('/login')
def login(
    request: Request, next: Optional[str] = None,
    ticket: Optional[str] = None):
    if request.session.get("user", None):
        # Already logged in
        return RedirectResponse(request.url_for('profile'))

    # next = request.args.get('next')
    # ticket = request.args.get('ticket')
    if not ticket:
        # No ticket, the request come from end user, send to CAS login
        cas_login_url = cas_client.get_login_url()
        print('CAS login URL: %s', cas_login_url)
        return RedirectResponse(cas_login_url)

    # There is a ticket, the request come from CAS as callback.
    # need call `verify_ticket()` to validate ticket and get user profile.
    print('ticket: %s', ticket)
    print('next: %s', next)

    user, attributes, pgtiou = cas_client.verify_ticket(ticket)

    print(
        'CAS verify ticket response: user: %s, attributes: %s, pgtiou: %s',
        user, attributes, pgtiou)

    if not user:
        return HTMLResponse('Failed to verify ticket. <a href="/login">Login</a>')
    else:  # Login successfully, redirect according `next` query parameter.
        response = RedirectResponse(next)
        request.session['user'] = dict(user=user)
        return response


@app.get('/logout')
def logout(request: Request):
    redirect_url = request.url_for('logout_callback')
    cas_logout_url = cas_client.get_logout_url(redirect_url)
    print('CAS logout URL: %s', cas_logout_url)
    return RedirectResponse(cas_logout_url)


@app.get('/logout_callback')
def logout_callback(request: Request):
    # redirect from CAS logout request after CAS logout successfully
    # response.delete_cookie('username')
    request.session.pop("user", None)
    return HTMLResponse('Logged out from CAS. <a href="/login">Login</a>')
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0