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

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

程序员文章站 2022-04-10 08:37:00
实验环境配置 host文件配置如下: 127.0.0.1 app.com 127.0.0.1 sso.com iis配置如下: 应用程序池采用.net fra...

实验环境配置

host文件配置如下:

127.0.0.1 app.com
127.0.0.1 sso.com

iis配置如下:

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

应用程序池采用.net framework 4.0

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

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

app.com网站配置如下:

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

 sso.com网站配置如下:

memcached缓存:

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

 数据库配置:

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

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

授权验证过程演示:

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

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

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

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

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

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

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

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

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

返回结果:true,false

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

sso数据库验证日志:

用户授权验证日志:

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

用户授权会话session:

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

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

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

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

/// <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服务器、缓存的水平扩展等。

源码地址:http://xiazai.jb51.net/201701/yuanma/smartsso_jb51.rar

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