WCF安全之基于自定義聲明授權(quán)策略
背景
我們認(rèn)為WCF可以使用自定的義的用戶名密碼方式限制對(duì)服務(wù)的訪問(wèn)和加密,有可能存在這樣一種情況,比如一個(gè)協(xié)定中存在多個(gè)操作,即一個(gè)服務(wù)契約中包含多個(gè)操作契約,如果我們還希望在同一用戶訪問(wèn)當(dāng)前服務(wù)契約的時(shí)候,更進(jìn)一步,可以讓我們做到對(duì)不同的用戶授予不同的操作契約的訪問(wèn),直白一點(diǎn),比如有兩個(gè)用戶admin、admin2,admin可以訪問(wèn)服務(wù)契約中的兩個(gè)操作契約,但是admin2只能訪問(wèn)其中一個(gè),本示例將實(shí)現(xiàn)這種需求,在以下的示例中,服務(wù):IUserData 中包含三個(gè)操作契約,我們將對(duì)admin、admin2 這兩個(gè)用戶授予不同的操作契約的訪問(wèn)權(quán)限,在客戶使用不同的用戶調(diào)用服務(wù)后,服務(wù)器將打印當(dāng)前的請(qǐng)求服務(wù)的用戶、請(qǐng)求的資源、服務(wù)器對(duì)聲明的檢查、檢查結(jié)果等數(shù)據(jù)。
開(kāi)始
首先我們建立一個(gè)自定義的基于服務(wù)授權(quán)訪問(wèn)檢查的管理器,CustomServiceAuthorizationManager,首先看代碼:
- public class CustomServiceAuthorizationManager : ServiceAuthorizationManager
- {
- protected override bool CheckAccessCore(OperationContext operationContext)
- {
- string action = operationContext.RequestContext.RequestMessage.Headers.Action;
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("/--分割線--嘎嘎-------------------------------------------/");
- Console.ForegroundColor = ConsoleColor.White;
- Console.WriteLine("請(qǐng)求的資源,URI:{0}", action);
- foreach (ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
- {
- if (cs.Issuer == ClaimSet.System)//如果此聲明是應(yīng)用程序頒發(fā)的。
- {
- foreach (Claim claim in cs.FindClaims("net.tcp://UserDataService.IUserData/", Rights.PossessProperty))
- {
- Console.WriteLine("服務(wù)器聲明檢查,URI:{0}", action);
- if (claim.Resource.ToString() == action)
- {
- Console.WriteLine("通過(guò),URI:{0}", action);
- return true;
- }
- }
- }
- }
- Console.WriteLine("不通過(guò),URI:{0}", action);
- return false;
- }
1、在上面的代碼中,我們首先建立了一個(gè)繼承自 System.ServiceModel.ServiceAuthorizationManager,并重寫(xiě)了其CheckAccessCore的檢查方法,或者有朋友會(huì)問(wèn),為什么不直接使用ServiceAuthorizationManager類呢?是的,沒(méi)錯(cuò),ServiceAuthorizationManager是提供自定義授權(quán)訪問(wèn)檢查的類,但是,ServiceAuthorizationManager類本身并不對(duì)任何聲明進(jìn)行評(píng)估,換句話說(shuō),此類不執(zhí)行任何基于自定義聲明授權(quán)的檢查,在默認(rèn)情況下,所有服務(wù)都是可以訪問(wèn)的,顯示,這不符合我們本次示例的業(yè)務(wù)需求。
2、ServiceAuthorizationManager類共有三個(gè)可供重寫(xiě)的方法,分別為:
(1)bool CheckAccess(OperationContext operationContext)
(2)bool CheckAccess(OperationContext operationContext, ref Message message)
(3)bool CheckAccessCore(OperationContext operationContext)
但是,實(shí)際上,前兩個(gè)方法都是通過(guò)調(diào)用bool CheckAccessCore(OperationContext operationContext) 來(lái)實(shí)現(xiàn)對(duì)自定義授權(quán)聲明的評(píng)估,所以,我們應(yīng)該直接重寫(xiě)CheckAccessCore而不是前兩個(gè)方法。
3、在重寫(xiě)的CheckAccessCore方法內(nèi),首先通過(guò)當(dāng)前操作的上下文對(duì)象獲得了當(dāng)前客戶端所請(qǐng)求的資源定位符Action,接著,馬上調(diào)用自定義授權(quán)聲明頒發(fā)管理器,把當(dāng)前用戶的授權(quán)策略聲明集添加到服務(wù)安全上下文,并檢查此授權(quán)策略聲明集是否為當(dāng)前應(yīng)用程序所頒發(fā),如果是當(dāng)前應(yīng)用程序所頒發(fā),則查找當(dāng)前授權(quán)策略聲明中一個(gè)名為:“net.tcp://UserDataService.IUserData/”的聲明,最后,服務(wù)器再檢查如果用戶所請(qǐng)求的資源存在于此聲明中,剛授權(quán)通過(guò),否則服務(wù)將拒絕客戶端所請(qǐng)求的資源。
自定義授權(quán)策略聲明集管理器
1、為了完成此次基于自定義授權(quán)策略聲明的服務(wù)器授權(quán)檢查, 我們需要構(gòu)造自已的授權(quán)策略聲明集管理器,代碼如下:
- CustomAuthorizationPolicy
- public class CustomAuthorizationPolicy : IAuthorizationPolicy
- {
- string id = string.Empty;
- public CustomAuthorizationPolicy()
- {
- id = new Guid().ToString();//每個(gè)聲明集都是一個(gè)唯一的
- }
- /// <summary>
- /// 評(píng)估用戶是否符合基于此授權(quán)策略的聲明
- /// </summary>
- /// <param name="evaluationContext"></param>
- /// <param name="state"></param>
- /// <returns></returns>
- public bool Evaluate(EvaluationContext evaluationContext, ref object state)
- {
- bool flag = false;
- bool r_state = false;
- if (state == null) {tate = r_state;} else { r_state = Convert.ToBoolean(state);}
- if (!r_state)
- {
- IList<Claim> claims = new List<Claim>();//實(shí)體聲明集
- foreach (ClaimSet cs in evaluationContext.ClaimSets)
- {
- foreach (Claim claim in cs.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
- {
- Console.WriteLine("用戶:{0}", claim.Resource);
- foreach (string str in GetOprationList(claim.Resource.ToString()))
- {
- claims.Add(new Claim("net.tcp://UserDataService.IUserData/", str, Rights.PossessProperty));
- Console.WriteLine("授權(quán)的資源:{0}", str);
- }
- }
- }
- evaluationContext.AddClaimSet(this, new DefaultClaimSet(Issuer, claims));r_state = true;flag = true;
- }
- else{flag = true;}
- return flag;
- }
- /// <summary>
- /// 賦予用戶聲明權(quán)限
- /// </summary>
- /// <param name="username"></param>
- /// <returns></returns>
- private static IEnumerable<string> GetOprationList(string username)
- {
- IList<string> lists = new List<string>();
- if (username == "admin")
- {
- lists.Add("net.tcp://UserService.IUserData/GetData");
- lists.Add("net.tcp://UserService.IUserData/GetUserExtensionXElement");
- }
- else if (username == "admin2")
- {
- lists.Add("net.tcp://UserService.IUserData/GetData");
- }
- return lists;
- }
- #region IAuthorizationComponent 成員/屬性實(shí)現(xiàn)
- public ClaimSet Issuer
- {
- get { return ClaimSet.System; }
- }
- public string Id
- {
- get { return id; }
- }
- #endregion
- }
2、首先,我們建立了一個(gè)類CustomAuthorizationPolicy繼承自IAuthorizationPolicy接口,IAuthorizationPolicy中定義了一組用于對(duì)用戶進(jìn)行授權(quán)的規(guī)則,規(guī)則相當(dāng)簡(jiǎn)單,除了兩個(gè)屬性和一個(gè)授權(quán)規(guī)則檢查的方法,再?zèng)]有其它。
3、在類的一開(kāi)始,我們給每個(gè)即將進(jìn)行授權(quán)的客戶定義了一個(gè)聲明集,并給了一個(gè)唯一的標(biāo)識(shí)(GUID),
4、實(shí)現(xiàn)了bool Evaluate(EvaluationContext evaluationContext, ref object state),在 Evaluate中,我們通過(guò)當(dāng)前經(jīng)過(guò)評(píng)估的授權(quán)策略的結(jié)果上下文對(duì)象獲取了一個(gè)ClaimSets對(duì)象,ClaimSets包含了一組經(jīng)過(guò)評(píng)估的獲取與授權(quán)策略關(guān)聯(lián)的聲明集,實(shí)際上,包含了客戶端調(diào)用服務(wù)前添加的用戶名,這個(gè)用戶名是經(jīng)過(guò)了上一章的自定義用戶名密碼訪問(wèn)服務(wù)的驗(yàn)證程序驗(yàn)證后的用戶名,所以,我們可基于此用戶名對(duì)其權(quán)限進(jìn)行分配,即給他一個(gè)可訪問(wèn)服務(wù)操作的聲明。
5、添加聲明,在上面的代碼中,有一個(gè)方法:IEnumerable<string> GetOprationList(string username),在第4點(diǎn)中, bool Evaluate方法傳入一個(gè)用戶名,然后,GetOprationList方法將對(duì)這個(gè)用戶進(jìn)行檢查并給其添加預(yù)先定義的聲明,當(dāng)然,這個(gè)檢查你可以在持久化介質(zhì)中做,比如數(shù)據(jù)庫(kù),你大可將你的所有服務(wù)操作都做成基于角色的訪問(wèn)的聲明,現(xiàn)在這里只是一個(gè)示例,所以沒(méi)有必要做得很靈活,當(dāng)然,我也不推薦你那樣做,除非真有必要,我覺(jué)得效率上會(huì)有很大的問(wèn)題。
6、不論這個(gè)用戶的權(quán)限如何,最后, Evaluate方法都會(huì)在將一個(gè)默認(rèn)的聲明添加到當(dāng)前當(dāng)前經(jīng)過(guò)評(píng)估的授權(quán)策略的結(jié)果上下文對(duì)象中,
這一句:evaluationContext.AddClaimSet(this, new DefaultClaimSet(Issuer, claims));
7、然后,再由前面定義的服務(wù)授權(quán)策略管理器對(duì)這個(gè)聲明和用戶所請(qǐng)求的資源進(jìn)行檢查,以決定是否對(duì)該用戶所請(qǐng)求的資源授予其訪問(wèn)權(quán)限。
配置授權(quán)策略管理器和授權(quán)聲明集管理器
1、在上面的代碼完成以后,我們還需要在服務(wù)器配置文件中進(jìn)行相應(yīng)的設(shè)置,好讓我們的代碼正常工作,配置比較簡(jiǎn)單,如下:
- <behaviors>
- <serviceBehaviors>
- <serviceCredentials>
- <serviceCertificate findValue="192168168151service"
- x509FindType="FindBySubjectName"
- storeLocation="LocalMachine"
- storeName="My"/>
- <userNameAuthentication customUserNamePasswordValidatorType="UserDataServcie.CustomUserPassword,UserDataServcie" userNamePasswordValidationMode="Custom"/>
- </serviceCredentials>
- <serviceAuthorization serviceAuthorizationManagerType="UserDataServcie.CustomServiceAuthorizationManager,UserDataServcie">
- <authorizationPolicies>
- <add policyType="UserDataServcie.CustomAuthorizationPolicy,UserDataServcie"/>
- </authorizationPolicies>
- </serviceAuthorization>
- </behavior>
2、其中紅色部分是對(duì) 授權(quán)策略管理器和授權(quán)聲明集的配置,相當(dāng)?shù)暮?jiǎn)單,現(xiàn)在讓我們來(lái)測(cè)試一下,客戶端代碼如下:
- UserDataClient client = new UserDataClient();
- //模擬admin用戶調(diào)用
- client.ClientCredentials.UserName.UserName = "admin";
- client.ClientCredentials.UserName.Password = "admin";
- Console.WriteLine("正在開(kāi)始調(diào)用GetData方法");
- string msg = client.GetData(100);
- Console.WriteLine("調(diào)用結(jié)果:{0}", msg);
- Console.WriteLine("正在開(kāi)始調(diào)用GetUserExtensionXElement方法");
- XElement xe = client.GetUserExtensionXElement(null);
- Console.WriteLine("調(diào)用成功,開(kāi)始打印消息.");
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine("==================================");
- Console.WriteLine(xe.Value);
- client.Close();
- //模擬admin2用戶調(diào)用
- client = new UserDataClient();
- Console.WriteLine("-----------------------------------------------------------------");
- client.ClientCredentials.UserName.UserName = "admin2";
- client.ClientCredentials.UserName.Password = "admin";
- Console.WriteLine("正在開(kāi)始調(diào)用GetData方法");
- string msg2 = client.GetData(200);
- Console.WriteLine("調(diào)用結(jié)果:{0}", msg2);
- Console.WriteLine("正在開(kāi)始調(diào)用GetUserExtensionXElement方法");
- XElement xe2 = client.GetUserExtensionXElement(null);
3、為了讓大家對(duì)服務(wù)器授權(quán)檢查是否成功有一個(gè)具體的認(rèn)識(shí),默認(rèn)情況下,如果授權(quán)不通過(guò),服務(wù)器將拋出一個(gè)SecurityAccessDeniedException異常,告訴客戶端,此調(diào)用未經(jīng)服務(wù)器授權(quán)。我們也捕獲一下,并打印到控制臺(tái):
- XElement xele = null;
- try
- {
- xele = base.Channel.GetUserExtensionXElement(xe);
- }
- catch (SecurityAccessDeniedException ex)
- {
- Console.WriteLine(ex.Message);
- }
結(jié)果
1、客戶端調(diào)用
2、服務(wù)器對(duì)客戶端進(jìn)行聲明檢查
3、從上面的結(jié)果可以看出:
我們對(duì)admin用戶授予了對(duì)服務(wù)的兩個(gè)操作的聲明,所以兩個(gè)方法的服務(wù)調(diào)用都通過(guò),
但是admin2用戶只授予了對(duì)服務(wù)的一個(gè)的聲明,所以當(dāng)?shù)谄湔{(diào)用第二個(gè)操作的時(shí)候,授權(quán)未通過(guò)。
從這里可以看出,WCF對(duì)于安全配置提供了非常靈活的方式,讓我們可以隨心所欲的對(duì)服務(wù)訪問(wèn)進(jìn)行授權(quán),但是,并不是所有的安全策略用上,服務(wù)才是安全的,還是那句話,有多大量,吃多大碗飯,別浪費(fèi)了,呵呵~~
博文作者:梁規(guī)曉博客(http://www.cnblogs.com/viter/)!
【編輯推薦】