在Casino中增加login api接口,使用cas-proxy和rake-proxy构建Api网关

源码分析Cas Server业务流程


标准单点登录流程

WX20190112-094141@2x

启用cas-proxy后的API网关流程

WX20190112-100525@2x

改造前的准备,源码分析

1)验证账户信息

# https://github.com/rbCAS/CASino/blob/ff29323b8a9692a82dcc498498761675ee44701d/app/controllers/casino/sessions_controller.rb#L24
validate_login_credentials("admin", "xxxxxx")

# 返回 authentication_result
{:authenticator=>"rails", :user_data=>{:username=>"admin", :extra_attributes=>{:email=>"admin@test.com"}}}

2)创建TGT

# https://github.com/rbCAS/CASino/blob/ff29323b8a9692a82dcc498498761675ee44701d/app/helpers/casino/sessions_helper.rb#L35
acquire_ticket_granting_ticket(authentication_result, request.user_agent, request.remote_ip, { credentials_supplied: true })
tgt = acquire_ticket_granting_ticket(validate_login_credentials("admin", "111111"), "...", "...", {})

# 返回 TGT
<CASino::TicketGrantingTicket id: 2, ticket: "TGC-15392492409839-KRQjTRhNxDJroKhj4R3HyzYwrAZnDE9...", user_agent: "...", user_id: 1, awaiting_two_factor_authentication: false, long_term: false, created_at: "...", updated_at: "...", user_ip: "...">

3)创建ST

# https://github.com/rbCAS/CASino/blob/ff29323b8a9692a82dcc498498761675ee44701d/app/helpers/casino/sessions_helper.rb#L90
st = acquire_service_ticket(tgt, "http://192.168.1.3/users/service",{ credentials_supplied: true })

# 返回 ST
<CASino::ServiceTicket id: 1, ticket: "ST-15393101642144-5gm8J6CkxwUwSaH2lv53INcQc9EAxsIW...", service: "http://192.168.1.3/users/service", ticket_granting_ticket_id: 3, consumed: false, issued_from_credentials: true, created_at: "...", updated_at: "...">

4)创建PGT

# https://github.com/rbCAS/CASino/blob/ff29323b8a9692a82dcc498498761675ee44701d/app/controllers/casino/controller_concern/ticket_validator.rb#L10
pgt = acquire_proxy_granting_ticket("https://192.168.1.2", st)

# 返回 PGT
<CASino::ProxyGrantingTicket id: 1, ticket: "PGT-15393108621212-PPiHPz3TTmB4s5Y76lIvFDZYJrPqz0I...", iou: "PGTIOU-15393108621213-3lEOLCgJxuKzcThczeDpBrX8yo3P...", granter_id: 2, pgt_url: "https://192.168.1.2", granter_type: "CASino::ServiceTicket", created_at:"...", updated_at: "...">

二次开发Casino


增加login接口

# coutroller
class CASino::SessionsController < CASino::ApplicationController
#...
  def login_by_json
    validation_result = validate_login_credentials(params[:username], params[:password])
    if validation_result
      tgt = acquire_ticket_granting_ticket(validation_result, request.user_agent, request.remote_ip, { credentials_supplied: true })
      st = acquire_service_ticket(tgt, params[:service], { credentials_supplied: true })
      pgt = acquire_proxy_granting_ticket(params[:proxy_url], st)
      render json: { username: validation_result[:user_data][:username], tgt: tgt.ticket, pgt: pgt.ticket}
    else
      render json: nil, status: :unprocessable_entity
    end
  end

  def logout_by_json
    render json: CASino::TicketGrantingTicket.where(ticket: params[:tgt]).destroy_all
  end
  
# routers.rb
post 'api/login' => 'sessions#login_by_json'
post 'api/logout' => 'sessions#logout_by_json'

调试接口

WX20190112-112350@2x

关于前端


关于后端


WX20190112-115906@2x

API网关接收前端发来PGT,向cas-proxy申请PT,并反向代理到相应后端API

# https://github.com/ncr/rack-proxy
ENV['APP_URL'] ||= Settings.APP_URL

class AppProxy < Rack::Proxy
  def perform_request(env)
    request = Rack::Request.new(env)

    if request.path =~ %r{^/app/appname/}
        backend = URI(ENV['SERVICE_URL'])
        env["HTTP_HOST"] = backend.host
        pgt = env["HTTP_X_TOKEN"]
        if pgt
          target_service = "#{ENV['SERVICE_URL']}/users/service"
          cas = HTTParty.get("#{Settings.CAS_URL}/proxy?pgt=#{pgt}&targetService=#{target_service}")
          pt = cas.parsed_response["serviceResponse"]["proxySuccess"]["proxyTicket"]
          if env['QUERY_STRING'].blank?
            env['QUERY_STRING'] = "ticket=#{pt}"
          else
            env['QUERY_STRING'] += "&ticket=#{pt}"
          end
        end
        super(env)
    else
      @app.call(env)
    end
  end

  def rewrite_response(triplet)
    status, headers, body = triplet
    # 跨域访问
    headers["Access-Control-Allow-Origin"] = "*"
    triplet
  end
end

Rails.application.config.middleware.use AppProxy, backend: ENV['APP_URL'], streaming: false

WX20190112-120647@2x

其他的后端app接收ticket参数,集成cas server,验证ticket通过后即可拿到对应数据

总结


  1. 适用于跨语言的各类子系统集成,无需剥离各子系统的用户和权限。
  2. 通过单点登录实现统一登录状态管理,API网关实现统一后端服务对外接口。
  3. 前端只需要对应API网关接口开发即可。