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后的页面
  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章