實現sso系統的主要難點:
1:不能直接訪問數據庫,有安全隱患,並且還容易亂套。
2:多個系統須要進行單點登陸,邏輯須要嚴謹,能支持N多系統、而不僅是少數幾個系統。
3:代碼不能過於複雜,須要簡潔,靈活支持本地部署,單點部署,集羣部署,相同的代碼能夠經過部署配置靈活實現服務段(sso)、本地段(子網站)功能。
4:多系統的權限也能夠靈活判斷,不能訪問數據庫,須要進行遠程服務調用,並且還須要對外部系統能提供調用接口。
5:須要有必定的安全性,能防止注入攻擊等等。
6:權限定義須要很是靈活,能夠定義10個權限,也能夠定義100個權限,能夠根據url進行權限判斷,同時能夠是n多個業務系統。
7:須要穩定,2周內就搞定,1周後交付測試,而不是須要研究2個月才能搞定,或者研究2年。
單點登陸常見需求:
跨應用、跨域、跨機器的單點登陸。
一、流程:
a) 用戶直接訪問門戶url,登陸成功後跳轉到默認應用的url。
程序裏須要引用2個Dll,通用權限管理系統組件的,這2個dll主要實現權限判斷,登陸功能等。
下面是登陸頁面的位置
這裏是MVC默認的登陸頁面,
上圖是須要登陸的功能定義部分。
在 MVC 里加上 [NeedAuthorize] 就能夠默認要求登陸了。不登陸不容許訪問了。
b) 用戶直接訪問應用url,若未登陸,跳轉到登陸頁面的url,完成登陸後,跳轉回應用url。
上圖是跳轉的處理,若沒指定須要跳轉的頁面,那就默認回到首頁,如有指定的 returnUrl,就跳轉回指定的頁面裏。
c) 用戶已經登陸,用戶直接訪問應用的URL,若該用戶無權限,跳轉到指定的url
沒有訪問權限的用戶會被重定向到沒權限訪問的頁面
系統中的注意事項,先看下圖
NeedUrlAuthorizedAttribute 是須要進行url驗證用的屬性,在須要控制的Controller上加上,例如
判斷是否有url權限的調用方法以下:
[NeedAuthorize] 須要進行登陸(這個能夠省略)
[NeedUrlAuthorized] 須要進行Url驗證 (首先會要求須要登陸)
[HttpPost]
public ActionResult User(Friend.Models.User model)
{
}
PermissionWebService 是進行權限驗證中心遠程進行權限判斷的WebService,是引用了
http://www.sso.com/PermissionService.asmx 這個WebService。
須要在配置文件裏進行
配置
SystemCode, 主要是來識別,是哪一個子系統的權限?由於當前可能10多個網站,那就是有10多個子系統,那就每一個子系統須要有一個惟一識別的編號。
那如何設置某個子系統的用的url權限?看下圖(用戶權限配置參考,子系統設置菜單、菜單編號、菜單url設置):
若沒登陸,就會提示先登陸
登陸後,若沒權限訪問這個頁面,就會提示沒權限訪問。
能夠點網頁下面的 有情鏈接進行測試,不用在url里人工輸入。
d) 用戶已經登陸,用戶直接訪問用戶有權限的應用url,經過。
與上面的需求一致,有權限的天然能經過調用。
有URL訪問權限的,能夠顯示頁面效果以下
E) 跨域的單點登陸、權限判斷須要注意的配置部分。
須要在其餘須要接入的 其餘應用裏須要加上這3個配置,認證主服務器上不要進行這個配置,須要刪除掉這幾行配置信息才能夠。
二、驗證碼的生成,封裝成一個插件,方便各類增強版本的驗證須要。
這個是一個通用的嚴正碼設置與驗證碼校驗的類,能夠按本身的須要進行修改。
複製代碼
//-----------------------------------------------------------------
// All Rights Reserved , Copyright (C) 2013 , Hairihan TECH, Ltd.
//-----------------------------------------------------------------
using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
namespace DotNet.Business
{
using DotNet.Utilities;
/// <summary>
/// BaseUserManager
/// 用戶管理
///
/// 修改紀錄
///
/// 2013.08.17 版本:1.0 JiRiGaLa 用戶登陸後才設置驗證碼、獲取驗證碼等。
///
/// <author>
/// <name>JiRiGaLa</name>
/// <date>2011.10.17</date>
/// </author>
/// </summary>
public partial class BaseUserManager : BaseManager
{
#region public int SetVerificationCode(string userId, string verificationCode) 設置驗證碼
/// <summary>
/// 設置驗證碼
/// </summary>
/// <param name="userId">用戶主鍵</param>
/// <param name="verificationCode">驗證碼</param>
/// <returns>影響行數</returns>
public int SetVerificationCode(string userId, string verificationCode)
{
int result = 0;
if (string.IsNullOrEmpty(userId) && this.UserInfo != null)
{
userId = this.UserInfo.Id;
}
string sqlQuery = string.Empty;
sqlQuery = " UPDATE " + BaseUserLogOnEntity.TableName
+ " SET " + BaseUserLogOnEntity.FieldVerificationCode + " = " + DbHelper.GetParameter("VerificationCode")
+ " WHERE " + BaseUserLogOnEntity.FieldId + " = " + DbHelper.GetParameter("UserId");
List<IDbDataParameter> dbParameters = new List<IDbDataParameter>();
dbParameters.Add(DbHelper.MakeParameter("VerificationCode", verificationCode));
dbParameters.Add(DbHelper.MakeParameter("UserId", userId));
result = DbHelper.ExecuteNonQuery(sqlQuery, dbParameters.ToArray());
return result;
}
#endregion
#region public bool Verify(string userId, string verificationCode)
/// <summary>
/// 驗證,驗證碼是否正確
/// </summary>
/// <param name="userId">用戶主鍵</param>
/// <param name="verificationCode">驗證碼</param>
/// <returns></returns>
public bool Verify(string userId, string verificationCode)
{
bool result = false;
string sqlQuery = string.Empty;
// 最後一次登陸時間
sqlQuery = " SELECT COUNT(1)"
+ " FROM " + BaseUserLogOnEntity.TableName
+ " WHERE " + BaseUserLogOnEntity.FieldVerificationCode + " = " + DbHelper.GetParameter("VerificationCode")
+ " AND " + BaseUserLogOnEntity.FieldId + " = " + DbHelper.GetParameter("UserId");
List<IDbDataParameter> dbParameters = new List<IDbDataParameter>();
dbParameters.Add(DbHelper.MakeParameter("VerificationCode", verificationCode));
dbParameters.Add(DbHelper.MakeParameter("UserId", userId));
object exist = DbHelper.ExecuteScalar(sqlQuery, dbParameters.ToArray());
if (exist != null)
{
if (BaseSystemInfo.OnLineLimit <= int.Parse(exist.ToString()))
{
result = true;
}
}
return result;
}
#endregion
}
}
複製代碼
三、提供遠程訪問接口:用戶信息的訪問;權限信息的訪問。訪問方式能夠是Webservice的方式,封裝成一個訪問類,方便別人調用。
PermissionService.asmx 權限的WebService中有方法能夠獲取用戶的權限,權限主要注意
1):要判斷哪一個子系統的權限?
2):每一個權限都有一個不重複的編號來識別的。
複製代碼
//-----------------------------------------------------------------------
// <copyright file="FriendFansManager.Auto.cs" company="Hairihan">
// Copyright (c) 2013 , All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using DotNet.Business;
namespace Friend
{
/// <remarks>
/// PermissionWebService
/// 權限檢查的WebService
///
/// 修改紀錄
///
/// 2013.08.17 版本:1.0 JiRiGaLa 更新審覈意見。
///
/// 版本:1.0
///
/// <author>
/// <name>JiRiGaLa</name>
/// <date>2013.08.17</date>
/// </author>
/// </remarks>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// 若要容許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的註釋。
// [System.Web.Script.Services.ScriptService]
public class PermissionService : System.Web.Services.WebService
{
// 若是當前用戶沒登陸後臺的權限
DotNet.Business.PermissionService permissionService = new DotNet.Business.PermissionService();
// 若是當前用戶登陸
// DotNet.Utilities.BaseUserInfo userInfo = Utilities.CheckCookie(HttpContext.Current.Request);
public PermissionService()
{
/*
if (userInfo != null && !string.IsNullOrEmpty(userInfo.Code))
{
if (!string.IsNullOrEmpty(permissionItemCode))
{
bool permissionAdmin = permissionService.IsAuthorizedByUser(userInfo, userInfo.Id, permissionItemCode, string.Empty);
if (!permissionAdmin)
{
throw new Exception("沒有權限訪問。");
}
}
}
else
{
throw new Exception("沒有權限訪問。");
}
*/
}
#region public bool IsUserInRole(string systemCode, string userId, string roleCode)
/// <summary>
/// 用戶是否在某個角色裏
/// </summary>
/// <param name="systemCode">子系統編號</param>
/// <param name="userId">用戶主鍵</param>
/// <param name="roleCode">角色編號</param>
/// <returns>是否在某個角色裏</returns>
[WebMethod]
public bool IsUserInRole(string systemCode, string userId, string roleCode)
{
// 須要建立個用戶對象
DotNet.Utilities.BaseUserInfo userInfo = new DotNet.Utilities.BaseUserInfo();
// 若沒指定是哪一個子系統,默認就是基礎系統
if (string.IsNullOrEmpty(systemCode))
{
systemCode = "Base";
}
userInfo.SystemCode = systemCode;
// 判斷用戶是否在指定的角色裏?
BaseUserManager userManager = new BaseUserManager(userInfo);
return userManager.IsInRoleByCode(userId, roleCode);
}
#endregion
#region public bool IsAuthorized(string systemCode, string userId, string permissionItemCode)
/// <summary>
/// 對某個模塊、操做是否有權限?
/// </summary>
/// <param name="systemCode">子系統編號</param>
/// <param name="userId">用戶主鍵</param>
/// <param name="permissionItemCode">操做權限編號、模塊編號</param>
/// <returns>是否擁有操做權限</returns>
[WebMethod]
public bool IsAuthorized(string systemCode, string userId, string permissionItemCode)
{
bool returnValue = false;
// 須要建立個用戶對象
DotNet.Utilities.BaseUserInfo userInfo = new DotNet.Utilities.BaseUserInfo();
// 若沒指定是哪一個子系統,默認就是基礎系統
if (string.IsNullOrEmpty(systemCode))
{
systemCode = "Base";
}
userInfo.SystemCode = systemCode;
// 實時從數據庫判斷權限的調用方法
returnValue = permissionService.IsAuthorizedByUser(userInfo, userId, permissionItemCode, string.Empty);
return returnValue;
}
#endregion
#region public bool IsUrlAuthorized(string systemCode, string userId, string url)
/// <summary>
/// 對某個模塊、操做是否有權限?
/// </summary>
/// <param name="systemCode">子系統編號</param>
/// <param name="userId">用戶主鍵</param>
/// <param name="url">按網址受權</param>
/// <returns>是否擁有操做權限</returns>
[WebMethod]
public bool IsUrlAuthorized(string systemCode, string userId, string url)
{
bool returnValue = false;
// 須要建立個用戶對象
DotNet.Utilities.BaseUserInfo userInfo = new DotNet.Utilities.BaseUserInfo();
// 若沒指定是哪一個子系統,默認就是基礎系統
if (string.IsNullOrEmpty(systemCode))
{
systemCode = "Base";
}
userInfo.SystemCode = systemCode;
// 實時從數據庫判斷權限的調用方法
returnValue = permissionService.IsUrlAuthorizedByUser(userInfo, userId, url);
return returnValue;
}
#endregion
#region public string[] GetUserPermissions(string systemCode, string userId)
/// <summary>
/// 獲取用戶的全部權限列表
/// </summary>
/// <param name="systemCode">子系統編號</param>
/// <param name="userId">用戶主鍵</param>
/// <returns>權限編號數組</returns>
[WebMethod]
public string[] GetUserPermissions(string systemCode, string userId)
{
List<string> permissions = new List<string>();
// 須要建立個用戶對象
DotNet.Utilities.BaseUserInfo userInfo = new DotNet.Utilities.BaseUserInfo();
// 若沒指定是哪一個子系統,默認就是基礎系統
if (string.IsNullOrEmpty(systemCode))
{
systemCode = "Base";
}
userInfo.SystemCode = systemCode;
// 獲取用戶的全部權限列表
List<BaseModuleEntity> entityList = permissionService.GetModuleListByUser(userInfo, userId);
foreach (var entity in entityList)
{
// 權限編號
// entity.Code;
permissions.Add(entity.Code);
// 能訪問的url列表
// entity.NavigateUrl;
// entity.FullName;
}
return permissions.ToArray();
}
#endregion
}
}
複製代碼
A: SSO服務器端配置說明:
1: 附加數據庫,把sql2008數據庫配置好,附加DotNet.CommonV3.9\DotNet.DataBase\SQL2008\UserCenter39
2: 配置 DotNet.Common\Friend, MVC的單點登陸程序,配置數據庫鏈接Web.config中的UserCenterDbConnection的數據庫鏈接。
3: 刪除單點登陸的SSO,SSOVerify,SSOPermissionService項目,從Web.config中。
進行以上3個步驟,mvc 的 SSO 服務器端就配置好了,在iis裏設置好MVC網站就能夠了。
B: SSO 客戶端的配置說明:
1:添加2個dll的引用,DotNet.Business、 DotNet.Utilities,dll在 DotNet.Common\Friend\External 目錄下。
2:Controller 須要加 [NeedAuthorize] 進行單點登陸控制, [NeedUrlAuthorized]進行ul 權限限制。
3:Web.config 中加 SSO,SSOVerify,SSOPermissionService 的配置。
4:添加 PermissionWebService 引用,就是須要引用 上面裏的單點登陸 WebService。
5:Global.asax 中須要寫一下 Application_Start() 中的代碼複製過去。
目前通用權限管理系統組件徹底知足以上需求,方便快速開發各類 .Net 應用軟件。
主要須要整理的部分以下列表中的問題
01:MVC 單點登陸需求
02:MVC 數據庫鏈接的配置。
03:MVC 用戶名密碼登陸,有錯誤時須要有錯誤提示信息。
04:MVC 裏保存密碼的方式,Cokies 保存測試。
05:MVC cookies 保存的時間長度設置,是否啓用cookies。
06:MVC 如有登陸自動跳轉地址的方式。
07:MVC 用OpenId(Key)登陸的方式,登陸跳轉的優化。
08:MVC 多系統支持單點登陸的配置注意事項。
09:MVC 權限判斷的例子。
10:MVC URL 權限判斷的例子。
11:MVC 退出功能的深刻優化,能退出子系統也能夠退出主系統。sql