在Casino中增加login api接口,使用cas-proxy和rake-proxy构建Api网关
源码分析Cas Server业务流程
标准单点登录流程
启用cas-proxy后的API网关流程
改造前的准备,源码分析
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'
调试接口
关于前端
- 前端只需要向api/login相应数据后就能得到TGC和PGT并保存
- PGT用于向cas-proxy申请一次性的PT(即ticket)
- TGC用于logout销毁(TGC->PGT->PT)
关于后端
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
其他的后端app接收ticket参数,集成cas server,验证ticket通过后即可拿到对应数据
总结
- 适用于跨语言的各类子系统集成,无需剥离各子系统的用户和权限。
- 通过单点登录实现统一登录状态管理,API网关实现统一后端服务对外接口。
- 前端只需要对应API网关接口开发即可。