当前位置: 移动技术网 > IT编程>开发语言>.net > ASP.NET MVC SSO单点登录设计与实现代码

ASP.NET MVC SSO单点登录设计与实现代码

2017年12月12日  | 移动技术网IT编程  | 我要评论

11022,公务员个人年终总结,青州乔静

实验环境配置

host文件配置如下:

127.0.0.1 app.com
127.0.0.1 sso.com

iis配置如下:

应用程序池采用.net framework 4.0

注意iis绑定的域名,两个完全不同域的域名。

app.com网站配置如下:

 

 sso.com网站配置如下:

memcached缓存:

 数据库配置:

 数据库采用entityframework 6.0.0,首次运行会自动创建相应的数据库和表结构。

授权验证过程演示:

在浏览器地址栏中访问:http://app.com,如果用户还未登陆则网站会自动重定向至:http://sso.com/passport,同时通过querystring传参数的方式将对应的appkey应用标识传递过来,运行截图如下:

url地址:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=

 输入正确的登陆账号和密码后,点击登陆按钮系统自动301重定向至应用会掉首页,毁掉成功后如下所示:

 由于在不同的域下进行sso授权登陆,所以采用querystring方式返回授权标识。同域网站下可采用cookie方式。由于301重定向请求是由浏览器发送的,所以在如果授权标识放入handers中的话,浏览器重定向的时候会丢失。重定向成功后,程序自动将授权标识写入到cookie中,点击其他页面地址时,url地址栏中将不再会看到授权标示信息。cookie设置如下:

登陆成功后的后续授权验证(访问其他需要授权访问的页面):

校验地址:http://sso.com/api/passport?sessionkey=xxxxxx&remark=xxxxxx

返回结果:true,false

客户端可以根据实际业务情况,选择提示用户授权已丢失,需要重新获得授权。默认自动重定向至sso登陆页面,即:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=seo@ljja.cn 同时登陆页面邮箱地址文本框会自定补全用户的登陆账号,用户只需输入登陆密码即可,授权成功后会话有效期自动延长一年时间。

sso数据库验证日志:

用户授权验证日志:

用户授权会话session:

数据库用户账号和应用信息:

应用授权登陆验证页面核心代码:

/// <summary>
  /// 公钥:appkey
  /// 私钥:appsecret
  /// 会话:sessionkey
  /// </summary>
  public class passportcontroller : controller
  {
    private readonly iappinfoservice _appinfoservice = new appinfoservice();
    private readonly iappuserservice _appuserservice = new appuserservice();
    private readonly iuserauthsessionservice _authsessionservice = new userauthsessionservice();
    private readonly iuserauthoperateservice _userauthoperateservice = new userauthoperateservice();

    private const string appinfo = "appinfo";
    private const string sessionkey = "sessionkey";
    private const string sessionusername = "sessionusername";

    //默认登录界面
    public actionresult index(string appkey = "", string username = "")
    {
      tempdata[appinfo] = _appinfoservice.get(appkey);

      var viewmodel = new passportloginrequest
      {
        appkey = appkey,
        username = username
      };

      return view(viewmodel);
    }

    //授权登录
    [httppost]
    public actionresult index(passportloginrequest model)
    {
      //获取应用信息
      var appinfo = _appinfoservice.get(model.appkey);
      if (appinfo == null)
      {
        //应用不存在
        return view(model);
      }

      tempdata[appinfo] = appinfo;

      if (modelstate.isvalid == false)
      {
        //实体验证失败
        return view(model);
      }

      //过滤字段无效字符
      model.trim();

      //获取用户信息
      var userinfo = _appuserservice.get(model.username);
      if (userinfo == null)
      {
        //用户不存在
        return view(model);
      }

      if (userinfo.userpwd != model.password.tomd5())
      {
        //密码不正确
        return view(model);
      }

      //获取当前未到期的session
      var currentsession = _authsessionservice.existsbyvalid(appinfo.appkey, userinfo.username);
      if (currentsession == null)
      {
        //构建session
        currentsession = new userauthsession
        {
          appkey = appinfo.appkey,
          createtime = datetime.now,
          invalidtime = datetime.now.addyears(1),
          ipaddress = request.userhostaddress,
          sessionkey = guid.newguid().tostring().tomd5(),
          username = userinfo.username
        };

        //创建session
        _authsessionservice.create(currentsession);
      }
      else
      {
        //延长有效期,默认一年
        _authsessionservice.extendvalid(currentsession.sessionkey);
      }

      //记录用户授权日志
      _userauthoperateservice.create(new userauthoperate
      {
        createtime = datetime.now,
        ipaddress = request.userhostaddress,
        remark = string.format("{0} 登录 {1} 授权成功", currentsession.username, appinfo.title),
        sessionkey = currentsession.sessionkey
      }); 104 
      var redirecturl = string.format("{0}?sessionkey={1}&sessionusername={2}",
        appinfo.returnurl, 
        currentsession.sessionkey, 
        userinfo.username);

      //跳转默认回调页面
      return redirect(redirecturl);
    }
  }
memcached会话标识验证核心代码:
public class passportcontroller : apicontroller
  {
    private readonly iuserauthsessionservice _authsessionservice = new userauthsessionservice();
    private readonly iuserauthoperateservice _userauthoperateservice = new userauthoperateservice();

    public bool get(string sessionkey = "", string remark = "")
    {
      if (_authsessionservice.getcache(sessionkey))
      {
        _userauthoperateservice.create(new userauthoperate
        {
          createtime = datetime.now,
          ipaddress = request.requesturi.host,
          remark = string.format("验证成功-{0}", remark),
          sessionkey = sessionkey
        });

        return true;
      }

      _userauthoperateservice.create(new userauthoperate
      {
        createtime = datetime.now,
        ipaddress = request.requesturi.host,
        remark = string.format("验证失败-{0}", remark),
        sessionkey = sessionkey
      });

      return false;
    }
  }

client授权验证filters attribute

public class ssoauthattribute : actionfilterattribute
  {
    public const string sessionkey = "sessionkey";
    public const string sessionusername = "sessionusername";

    public override void onactionexecuting(actionexecutingcontext filtercontext)
    {
      var cookiesessionkey = "";
      var cookiesessionusername = "";

      //sessionkey by querystring
      if (filtercontext.httpcontext.request.querystring[sessionkey] != null)
      {
        cookiesessionkey = filtercontext.httpcontext.request.querystring[sessionkey];
        filtercontext.httpcontext.response.cookies.add(new httpcookie(sessionkey, cookiesessionkey));
      }

      //sessionusername by querystring
      if (filtercontext.httpcontext.request.querystring[sessionusername] != null)
      {
        cookiesessionusername = filtercontext.httpcontext.request.querystring[sessionusername];
        filtercontext.httpcontext.response.cookies.add(new httpcookie(sessionusername, cookiesessionusername));
      }

      //从cookie读取sessionkey
      if (filtercontext.httpcontext.request.cookies[sessionkey] != null)
      {
        cookiesessionkey = filtercontext.httpcontext.request.cookies[sessionkey].value;
      }

      //从cookie读取sessionusername
      if (filtercontext.httpcontext.request.cookies[sessionusername] != null)
      {
        cookiesessionusername = filtercontext.httpcontext.request.cookies[sessionusername].value;
      }

      if (string.isnullorempty(cookiesessionkey) || string.isnullorempty(cookiesessionusername))
      {
        //直接登录
        filtercontext.result = ssologinresult(cookiesessionusername);
      }
      else
      {
        //验证
        if (checklogin(cookiesessionkey, filtercontext.httpcontext.request.rawurl) == false)
        {
          //会话丢失,跳转到登录页面
          filtercontext.result = ssologinresult(cookiesessionusername);
        }
      }

      base.onactionexecuting(filtercontext);
    }

    public static bool checklogin(string sessionkey, string remark = "")
    {
      var httpclient = new httpclient
      {
        baseaddress = new uri(configurationmanager.appsettings["ssopassport"])
      };

      var requesturi = string.format("api/passport?sessionkey={0}&remark={1}", sessionkey, remark);

      try
      {
        var resp = httpclient.getasync(requesturi).result;

        resp.ensuresuccessstatuscode();

        return resp.content.readasasync<bool>().result;
      }
      catch (exception ex)
      {
        throw ex;
      }
    }

    private static actionresult ssologinresult(string username)
    {
      return new redirectresult(string.format("{0}/passport?appkey={1}&username={2}",
          configurationmanager.appsettings["ssopassport"],
          configurationmanager.appsettings["ssoappkey"],
          username));
    }
  }

示例sso验证特性使用方法:

[ssoauth]
  public class homecontroller : controller
  {
    public actionresult index()
    {
      return view();
    }

    public actionresult about()
    {
      viewbag.message = "your application description page.";

      return view();
    }

    public actionresult contact()
    {
      viewbag.message = "your contact page.";

      return view();
    }
  }

总结:

从草稿示例代码中可以看到代码性能上还有很多优化的地方,还有sso应用授权登陆页面的用户账号不存在、密码错误等一系列的提示信息等。在业务代码运行基本正确的后期,可以考虑往更多的安全性层面优化,比如启用appsecret私钥签名验证,ip范围验证,固定会话请求攻击、sso授权登陆界面的验证码、会话缓存自动重建、sso服务器、缓存的水平扩展等。

源码地址:

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网