sso 單點登錄

  • 原理
    • 使用同域名(如A.test.com,B.test.com)cookies共享的方式,第一次認證之後,將cookies存放於redis緩存中,每次請求都驗證cookies
  • nginx+lua腳本實現
    1.nginx 配置
    server {
    listen 80;
    server_name devops.test.com;
    error_log /data/logs/nginx/error-devops.log;
    location ^~ /proxy/ {
    internal; #指定規則爲internal規則,防止外部請求命中此規則
    rewrite '^/proxy/(http?)/([^/]+)/(\d+)/(.*)' /$4 break;
    proxy_pass $1://$2:$3;
    }
    location @client{
    proxy_pass http://10.47.137.120:8001; #devops 服務地址及端口
    }
    location /api/v1{
    proxy_pass http://10.47.137.120:8001; #devops 服務地址及端口
    }
    location /static{
    proxy_pass http://10.47.137.120:8001;
    }
    location / {
    access_by_lua_file 'conf/vhosts/lua/devops.lua';
    }
    }
    1.從nginx的配置文件中可以看出,/api/v1/ 和/static 是沒有調用lua腳本,而請求/ 都要走lua腳本認證

    1. @client 請求是lua腳本中指定的uri
    2. /proxy/ 是lua腳本中使用的uri,主要是用來跳轉到認證登錄平臺
      2.lua 腳本實現 devops.lua
      local sso = 'http://sso.test.com'
      local mysite = 'http://devops.test.com/'
      local login_url = sso .. '?next=' .. mysite

      function Logout()
      cookie_key = ngx.var.cookie_cookie_key
      if cookie_key ~= nil then
      local check_url = 'http/100.114.64.185/8000/logout/'
      local checkSign = 'cookie_key='.. cookie_key
      checkUrl = '/proxy/' .. check_url .. '?' .. checkSign
      local res = ngx.location.capture(checkUrl, {
      method = ngx.HTTP_GET,
      })
      ngx.redirect(login_url)
      else
      ngx.redirect(login_url)
      end
      end
      function getSSOUser()
      cookie_key = ngx.var.cookie_cookie_key
      if cookie_key == nil then
      ngx.redirect(login_url)
      end
      -- check user cookie
      local check_url = 'http/100.114.64.185/8000/sso/' -- format used by ngx.location.capture && proxy_pass(a httpclient simulation)
      local checkSign = 'cookie_key='.. cookie_key..'&&url='..mysite
      checkUrl = '/proxy/' .. check_url .. '?' .. checkSign
      --ngx.say(checkUrl)
      local res = ngx.location.capture(checkUrl, {
      method = ngx.HTTP_GET,
      })
      local cjson = require("cjson")
      --ngx.say(res.body)
      if 'false' == res.body then
      ngx.redirect(login_url)
      --ngx.say(res.body)
      else
      obj = cjson.decode(res.body)
      user = obj['user']
      return user
      end
      end
      function doLogin()
      local user = getSSOUser()
      ngx.header['Set-Cookie'] = {'x_webauth_user =' .. user}
      ngx.req.set_header("USER", user)br/>ngx.exec("@client")
      end
      if ngx.re.match(ngx.var.request_uri, "logout") then
      ngx.header['Set-Cookie'] = {'x_webauth_user = false'}
      Logout()
      else
      local x_user = ngx.var.cookie_x_webauth_user
      local cookie_key = ngx.var.cookie_cookie_key
      if cookie_key then
      doLogin()
      else
      ngx.redirect(login_url)
      end
      end

    3. lua腳本看起來沒那麼難,相對比較好理解
    4. 最前面的三個local 是定義變量
      if ngx.re.match(ngx.var.request_uri, "logout") then
      ngx.header['Set-Cookie'] = {'x_webauth_user = false'}
      Logout()
      #判斷請求的uri裏面是否有“logout”,匹配則設置cookies的 x_webauth_user值
      爲false,並調用logout函數
      3.如果uri不包括logout
      local cookie_key = ngx.var.cookie_cookie_key 獲取cookies下cookie_key的值
      如果值爲空,直接從定向到單點登錄,login_url爲
      http://sso.test.com?next=http://devops.test.com/ ,單點登錄平臺需要做的
      是獲取next後面的uri及認證,認證成功之後,redis緩存cookie_key,
      將cookie_key信息寫入cookies中,讓下次請求的時候帶上cookie_key
      4.如果cookie_key值存在,則調用認證函數doLogin()
      function doLogin()
      local user = getSSOUser() #調用getSSOUser 函數
      ngx.header['Set-Cookie'] = {'x_webauth_user =' .. user}
      ngx.req.set_header("USER", user)
      ngx.exec("@client") #匹配nginx @client url
      end

      function getSSOUser()
          cookie_key = ngx.var.cookie_cookie_key
          if cookie_key == nil then
                  ngx.redirect(login_url)
          end
          -- check user cookie
          local check_url = 'http/100.114.64.185/8000/sso/'  -- format used by ngx.location.capture && proxy_pass(a httpclient simulation)
          local checkSign = 'cookie_key='.. cookie_key..'&&url='..mysite
          checkUrl = '/proxy/' .. check_url .. '?' .. checkSign
          --ngx.say(checkUrl)
       local res = ngx.location.capture(checkUrl, {
                  method = ngx.HTTP_GET,
          })  #get請求單點登錄平臺的接口/sso/cookie_key=* 驗證cookie_key 的有效性,錯誤返回false
          local cjson = require("cjson")
          --ngx.say(res.body)
          if 'false' == res.body then  #如果返回false,重新跳回登錄界面
                  ngx.redirect(login_url)
                  --ngx.say(res.body)
          else
                  obj = cjson.decode(res.body)
                  user = obj['user']
                  return user   #正確則返回認證用戶及cookie信息寫入
          end

      end
      5.註銷函數
      function Logout()
      cookie_key = ngx.var.cookie_cookie_key #獲取cookie_key 值
      if cookie_key ~= nil then #如果值不爲空
      local check_url = 'http/100.114.64.185/8000/logout/'
      local checkSign = 'cookie_key='.. cookie_key
      checkUrl = '/proxy/' .. check_url .. '?' .. checkSign
      local res = ngx.location.capture(checkUrl, {
      method = ngx.HTTP_GET,
      }) #請求http://100.114.64.185:8000/logout/?cookie_key=****,通過get請求刪除redis中的cookie值,達到註銷的功能
      ngx.redirect(login_url)
      else
      ngx.redirect(login_url)
      end
      end
      6.http://100.114.64.185:8000 是一個阿里雲內網負載均衡,後端是兩臺單點認證服務

  • 登錄平臺
    1.可以用django寫一個認證平臺,也可以用flask寫,個人推薦flask,輕量級
    2.平臺需要的功能
    認證用戶名及密碼(對接LDAP)
    提供cookie值判斷接口API
    刪除cookie值的接口API
    3.django實現,部分代碼
    redis 連接:
    def rds(key='',name='',status=''):
    import redis
    Rds = rdsconfig()
    host = Rds['host']
    port = Rds['port']
    passwd = Rds['passwd']
    if passwd:
    r = redis.Redis(host=host,port=port,password=passwd)
    else:
    r = redis.Redis(host=host,port=port)
    if status == 'add':
    r.setex(key,name,21600)
    elif status == 'get':
    return r.get(key)
    elif status == 'del':
    return r.delete(key)
    登錄部分:
    def login(request):
    cip=get_client_ip(request)
    cap_form = CaptchaLoginForm()
    #print cap_form
    if request.method == 'GET':
    #cap_form = CaptchaLoginForm()
    try:
    url=request.get_full_path()
    url=url.split('=')[1]
    logger.info(color_print('{} - access index'.format(cip),'info'))
    except:
    return render(request,'login.html',locals())
    return render(request,'login.html',locals())
    elif request.method == 'POST':
    username = request.POST.get('username').strip(' ')
    url = request.POST.get('url').strip(' ')
    response=HttpResponseRedirect(url)
    secret=MkPasswd.Passwd() #生成隨機數
    secret=MkPasswd.Passwd()+username #md5 加密隨機數,生成cookie_key
    secret_key=md5Encode(secret)
    rds(secret_key,username,'add')
    response.set_cookie('cookie_key',secret_key,\
    max_age=606012,domain='.yongqianbao.com')
    #response.set_cookie('x_webauth_user',username)
    if url == '/':
    return HttpResponseRedirect('http://intra.yongqianbao.com')
    return response
    else:
    return render(request,'login.html',locals())
    判斷cookie_key 是否存在於redis中
    def sso(request):
    import json
    cip=get_client_ip(request)
    if request.method == 'GET':
    cookie_key=request.GET.get('cookie_key')
    url=request.GET.get('url')
    #print url
    #print cookie_key
    time_now = int(time.time())
    user=rds(key=cookie_key,status='get')
    if user:
    watch={'user':user}
    login=Login(username=user,url=url,ip=cip,time=time_now,status=1)
    login.save()
    response=HttpResponse(json.dumps(watch),content_type="application/json")
    return response
    else:
    return HttpResponse('false')
    註銷函數:
    def logout(request):
    if request.method == 'GET':
    cookie_key=request.GET.get('cookie_key')
    #url=request.GET.get('url')
    is_succ=rds(key=cookie_key,status='del')
    #print is_succ
    return HttpResponse(True)
  • 作品展示
    • 登錄認證
      sso 單點登錄
    • 重置密碼
      sso 單點登錄
      • 認證成功後,自動跳轉到next後的頁面
  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章