欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.net 单点登录的设计与实践

程序员文章站 2022-07-22 12:51:18
前言 最近轮到我在小组晨会来分享知识点,突然想到单点登录,准备来分享下如何实现单点登录,所以有了下文。实现方案以及代码可能写得不是很严谨,有漏洞的地方或者错误的地方欢迎大...

前言

最近轮到我在小组晨会来分享知识点,突然想到单点登录,准备来分享下如何实现单点登录,所以有了下文。实现方案以及代码可能写得不是很严谨,有漏洞的地方或者错误的地方欢迎大家指正。  

刚开始头脑中没有思路,直接在博客园里面看看别人是如何来实现的,看了几篇文章发现,发现解决方案有点问题,或者说不算实现了单点登录

名称定义

为了方便说明先说明几个文中出现的名词的含义:

p站:统一登录授权验证中心,demo中 域名是www.passport.com:801

a站:处于不同域名下的测试网站,demo中 域名是www.a.com:802

b站:处于不同域名下的测试网站,demo中 域名是www.b.com:803

token:用户访问p站的秘钥

ticket:用来保存用户信息的加密字符串

单点登录

访问a站需要登陆的就跳转p站中进行登陆,p站登陆之后跳转回至a站,用户再次访问b站需要登陆的页面,用户不需要进行登陆操作就可以正常访问。

实现思路

未登录用户访问a站,首先会重定向跳转至p站授权中心,p站首先通过检测cookie来判断当前不是处于登陆状态,就跳转至登陆页面进行登陆操作,登陆成功之后把用户信息加密ticket附在a的请求地址上返回,a站通过解密ticket来获取用户信息,解密成功并存进session中(这样用户在a中就处于登陆状态了),访问通过;当用户再次访问b站的时候,对于b站来说,用户是处于未登录状态,则同样会重定向跳转至p站授权中心,p站检测cookie,判断当前用户处于登陆状态,就把当前用户信息加密成ticket附在b的请求地址上返回,后面的操作就和a站处理一样;这样都登陆之后再次访问a或者b,a和b中session中都存储了用户信息,就不会再次请求p站了。

简单关系图
.net 单点登录的设计与实践

泳道流程图.net 单点登录的设计与实践

主要逻辑说明

a站主要逻辑

用户首先访问a站,a站中会生成token,并存入cache中。token是a访问p的钥匙,p在回调给a的时候需要携带这个token。a请求p,p验证token,p回调a,a检测token是否是发送出去的token,验证之后token即失效,防止token被再次使用。

token的生成是通过取时间戳的不同字段进行md5加密生成,当然这里可以再加个盐进行防伪。

/// <summary>
    /// 生成秘钥
    /// </summary>
    /// <param name="timestamp"></param>
    /// <returns></returns>
    public static string createtoken(datetime timestamp)
    {
      stringbuilder securitykey = new stringbuilder(md5encypt(timestamp.tostring("yyyy")));
      securitykey.append(md5encypt(timestamp.tostring("mm")));
      securitykey.append(md5encypt(timestamp.tostring("dd")));
      securitykey.append(md5encypt(timestamp.tostring("hh")));
      securitykey.append(md5encypt(timestamp.tostring("mm")));
      securitykey.append(md5encypt(timestamp.tostring("ss")));
      return md5encypt(securitykey.tostring());
    }

p回调a的时候进行,a中对token进行校验,校验不成功则请求p站统一授权验证。

/// <summary>
  /// 授权枚举
  /// </summary>
  public enum authcodeenum
  {
    public = 1,
    login = 2
  }

  /// <summary>
  /// 授权过滤器
  /// </summary>
  public class authattribute : actionfilterattribute
  {
    /// <summary> 
    /// 权限代码 
    /// </summary> 
    public authcodeenum code { get; set; }

    /// <summary> 
    /// 验证权限
    /// </summary> 
    /// <param name="filtercontext"></param> 
    public override void onactionexecuting(actionexecutingcontext filtercontext)
    {
      var request = filtercontext.httpcontext.request;
      var session = filtercontext.httpcontext.session;
      //如果存在身份信息 
      if (common.currentuser == null)
      {
        if (code == authcodeenum.public)
        {
          return;
        }
        string reqtoken = request["token"];
        string ticket = request["ticket"];
        cache cache = httpcontext.current.cache;
        //没有获取到token或者token验证不通过或者没有取到从p回调的ticket 都进行再次请求p
        tokenmodel tokenmodel= cache.get(constanthelper.token_key)==null?null:(tokenmodel)cache.get(constanthelper.token_key);
        if (string.isnullorempty(reqtoken) || tokenmodel == null || tokenmodel.token!= reqtoken ||
          string.isnullorempty(ticket))
        {
          datetime timestamp = datetime.now;
          string returnurl = request.url.absoluteuri;
          tokenmodel = new tokenmodel
          {
            timestamp = timestamp,
            token = authernutil.createtoken(timestamp)
          };
          //token加入缓存中,设计过期时间为20分钟
          cache.add(constanthelper.token_key, tokenmodel, null, datetime.now.addminutes(20),cache.noslidingexpiration,cacheitempriority.default, null);
          filtercontext.result = new contentresult
          {
            content = getauthernscript(authernutil.getautherurl(tokenmodel.token, timestamp), returnurl)
          };
          return;
        }
        loginservice service = new loginservice();
        var userinfo = service.getuserinfo(ticket);
        session[constanthelper.user_session_key] = userinfo;
        //验证通过,cache中去掉token,保证每个token只能使用一次
        cache.remove(constanthelper.token_key);
      }
    }

    /// <summary>
    /// 生成跳转脚本
    /// </summary>
    /// <param name="authernurl">统一授权地址</param>
    /// <param name="returnurl">回调地址</param>
    /// <returns></returns>
    private string getauthernscript(string authernurl, string returnurl)
    {
      stringbuilder sbscript = new stringbuilder();
      sbscript.append("<script type='text/javascript'>");
      sbscript.appendformat("window.location.href='{0}&returnurl=' + encodeuricomponent('{1}');", authernurl, returnurl);
      sbscript.append("</script>");
      return sbscript.tostring();
    }
  }

代码说明:这里为了方便设置token的过期时间,所以使用cache来存取token,设定token的失效时间为两分钟,当验证成功则从cache中移除token。

调取过滤器

[auth(code = authcodeenum.login)]
     public actionresult index()
     {
       return view();
    }

p站主要逻辑

p站收到授权请求,p站首先通过coookie来判断是否登陆,未登录则跳转至登陆页面进行登陆操作。

/// <summary>
    /// 授权登陆验证
    /// </summary>
    /// <returns></returns>
    [httppost]
    public actionresult passportvertify()
    {
      var cookie=request.cookies[constanthelper.user_cookie_key];
      if (cookie == null ||string.isnullorempty(cookie.tostring()))
      {
        return redirecttoaction("login", new { returnurl = request["returnurl"] ,token= request["token"] });
      }
      string userinfo = cookie.tostring();
      var success= passportservice.authernvertify(request["token"], convert.todatetime(request["timestamp"]));
      if (!success)
      {
        return redirecttoaction("login", new { returnurl = request["returnurl"], token = request["token"] });
      }
      return redirect(passportservice.getreturnurl(userinfo, request["token"],request["returnurl"]));
    }

已登陆则验证token

/// <summary>
    /// 验证令牌
    /// </summary>
    /// <param name="token">令牌</param>
    /// <param name="timestamp">时间戳</param>
    /// <returns></returns>
    public bool authernvertify(string token,datetime timestamp)
    {
      return authernutil.createtoken(timestamp) == token;
    }

测试说明

1、修改host

127.0.0.1 www.passport.com

127.0.0.1 www.a.com

127.0.0.1 www.b.com

2、部署iis

p www.passport.com:801

a www.a.com:802

b www.b.com:803

3、测试账号和webconfig

<add key="passportcenterurl" value="http://www.passport.com:801"/>

用户名:admin  密码:123

demo

下载地址:

原文链接:http://www.cnblogs.com/minesnil-forfaith/p/6062943.html

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。