欢迎访问 生活随笔!

尊龙游戏旗舰厅官网

当前位置: 尊龙游戏旗舰厅官网 > > 编程问答 >内容正文

编程问答

关于web servicewcfwebapi实现身份验证之webapi篇 -尊龙游戏旗舰厅官网

发布时间:2025/1/21 编程问答 6 豆豆
尊龙游戏旗舰厅官网 收集整理的这篇文章主要介绍了 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

之前先后总结并发表了关于web service、wcf身份验证相关文章,如下:

关于web service&wcf&webapi实现身份验证之web service篇、

关于web service&wcf&webapi实现身份验证之wcf篇(1)、关于web service&wcf&webapi实现身份验证之wcf篇(2)

今天再来总结关于如何实现webapi的身份验证,以完成该系列所有文章,webapi常见的实现方式有:form身份验证、集成windows验证、basic基础认证、digest摘要认证

 第一种:form身份验证(若在asp.net应用程序使用,则该验证方式不支持跨域,因为cookie无法跨域访问)

1.定义一个formauthenticationfilterattribute,该类继承自authorizationfilterattribute,并重写其onauthorization,在该方法中添加从请求头中获取有无登录的cookie,若有则表示登录成功,否则失败,代码如下:

using system; using system.collections.generic; using system.linq; using system.web; using system.web.http; using system.web.http.filters; using system.web.security; using system.net.http; using system.collections.objectmodel; using system.net.http.headers; using system.threading; using system.security.principal; using system.net; using system.text;namespace webapplication1.models {public class formauthenticationfilterattribute : authorizationfilterattribute{private const string unauthorizedmessage = "请求未授权,拒绝访问。";public override void onauthorization(system.web.http.controllers.httpactioncontext actioncontext){if (actioncontext.actiondescriptor.getcustomattributes().count > 0){base.onauthorization(actioncontext);return;}if (httpcontext.current.user != null && httpcontext.current.user.identity.isauthenticated){base.onauthorization(actioncontext);return;}var cookies = actioncontext.request.headers.getcookies();if (cookies == null || cookies.count < 1){actioncontext.response = new httpresponsemessage(httpstatuscode.unauthorized) { content = new stringcontent(unauthorizedmessage, encoding.utf8) };return;}formsauthenticationticket ticket = getticket(cookies);if (ticket == null){actioncontext.response = new httpresponsemessage(httpstatuscode.unauthorized) { content = new stringcontent(unauthorizedmessage, encoding.utf8) };return;}//这里可以对formsauthenticationticket对象进行进一步验证var principal = new genericprincipal(new formsidentity(ticket), null);httpcontext.current.user = principal;thread.currentprincipal = principal;base.onauthorization(actioncontext);}private formsauthenticationticket getticket(collection cookies){formsauthenticationticket ticket = null;foreach (var item in cookies){var cookie = item.cookies.singleordefault(c => c.name == formsauthentication.formscookiename);if (cookie != null){ticket = formsauthentication.decrypt(cookie.value);break;}}return ticket;}} }

2.在需要认证授权后才能访问的controller中类或action方法上添加上述授权过滤器formauthenticationfilterattribute,也可在global文件中将该类添加到全局过滤器中,同时定义一个登录action,用于登录入口,示例代码如下:

using system; using system.collections.generic; using system.linq; using system.net; using system.net.http; using system.web; using system.web.http; using system.web.security; using webapplication1.models;namespace webapplication1.controllers {[formauthenticationfilter]public class testcontroller : apicontroller{[allowanonymous][acceptverbs("get")][route("api/test/login")]public httpresponsemessage login(string uname, string pwd){if ("admin".equals(uname, stringcomparison.ordinalignorecase) && "api.admin".equals(pwd)){//创建票据formsauthenticationticket ticket = new formsauthenticationticket(1, uname, datetime.now, datetime.now.addminutes(30), false, string.empty);//加密票据string authticket = formsauthentication.encrypt(ticket);//存储为cookiehttpcookie cookie = new httpcookie(formsauthentication.formscookiename, authticket);cookie.path = formsauthentication.formscookiepath;httpcontext.current.response.appendcookie(cookie);//或者//formsauthentication.setauthcookie(uname, false, "/");return request.createresponse(httpstatuscode.ok, "登录成功!");}else{httpcontext.current.response.appendcookie(new httpcookie(formsauthentication.formscookiename) { expires = datetime.now.adddays(-10) });//测试用:当登录失败时,清除可能存在的身份验证cookiereturn request.createerrorresponse(httpstatuscode.notfound, "登录失败,无效的用户名或密码!");}}// get api/testpublic ienumerable getvalues(){return new string[] { "value1", "value2" };}// get api/test/5public string getvalue(int id){return "value";}} }

测试用法一:可直接在浏览器中访问需要授权的方法(即:login除外),如:http://localhost:11099/api/test/,响应结果如下:

请求头信息如下:

若成功调用login方法后(http://localhost:11099/api/test/login?uname=admin&pwd=api.admin),再调用上述方法,则可以获得正常的结果,如下图示:

看一下请求时附带的cookie,如下图示:

测试用法二:采用httpclient来调用api的相关方法,示例代码如下:

public async static void testloginapi(){httpclienthandler handler = new httpclienthandler();handler.usecookies = true;//因为采用form验证,所以需要使用cookie来记录身份登录信息httpclient client = new httpclient(handler);console.writeline("login>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");var response = await client.getasync("http://localhost:11099/api/test/login/?uname=admin&pwd=api.admin");var r = await response.content.readasasync();console.writeline("statuscode:{0}", response.statuscode);if (!response.issuccessstatuscode){console.writeline("msg:{1}", response.statuscode, r.message);return;}console.writeline("msg:{1}", response.statuscode, r);var getcookies = handler.cookiecontainer.getcookies(new uri("http://localhost:11099/"));console.writeline("获取到的cookie数量:" getcookies.count);console.writeline("获取到的cookie:");for (int i = 0; i < getcookies.count; i ){console.writeline(getcookies[i].name ":" getcookies[i].value);}console.writeline("getvalues>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");response = await client.getasync("http://localhost:11099/api/test/");var r2 = await response.content.readasasync>();foreach (string item in r2){console.writeline("getvalues - item value:{0}", item);}console.writeline("getvalue>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");response = await client.getasync("http://localhost:11099/api/test/8");var r3 = await response.content.readasasync();console.writeline("getvalue - item value:{0}", r3);}

结果如下图示:

 如果web api作为asp.net 或mvc的一部份使用,那么完全可以采用基于默认的form身份验证授权特性(authorize),或采用web.config中配置,这个很简单,就不作说明了,大家可以网上参考关于asp.net 或asp.net mvc的form身份验证。

第二种:集成windows验证

首先在web.config文件中,增加如下配置,以开启windows身份验证,配置如下:

然后在需要认证授权后才能访问的controller中类或action方法上添加authorize特性,controller与上文相同不再贴出,当然也可以在web.config中配置:

最后将web api寄宿到(或者说发布到)iis,且需要在iis中启用windows身份验证,如下图示:

这样就完成了该身份验证模式(理论上web服务、wcf若都以iis为宿主,都可以采用集成windows身份验证模式),测试方法很简单,第一种直接在浏览器中访问,第二种采用httpclient来调用web api,示例代码如下:

public async static void testloginapi2(){httpclienthandler handler = new httpclienthandler();handler.clientcertificateoptions = clientcertificateoption.manual;handler.credentials = new networkcredential("admin", "www.zuowenjun.cn");httpclient client = new httpclient(handler);var response = await client.getasync("http://localhost:8010/api/test/");var r2 = await response.content.readasasync>();foreach (string item in r2){console.writeline("getvalues - item value:{0}", item);}response = await client.getasync("http://localhost:8010/api/test/8");var r3 = await response.content.readasasync();console.writeline("getvalue - item value:{0}", r3);}

第三种:basic基础认证

1.定义一个继承自authorizationfilterattribute的httpbasicauthenticationfilter类,用于实现basic基础认证,实现代码如下:

using system; using system.net; using system.text; using system.web; using system.web.http.controllers; using system.web.http.filters; using system.net.http; using system.web.http; using system.security.principal; using system.threading; using system.net.http.headers;namespace webapplication1.models {public class httpbasicauthenticationfilter : authorizationfilterattribute{public override void onauthorization(system.web.http.controllers.httpactioncontext actioncontext){if (actioncontext.actiondescriptor.getcustomattributes().count > 0){base.onauthorization(actioncontext);return;}if (thread.currentprincipal != null && thread.currentprincipal.identity.isauthenticated){base.onauthorization(actioncontext);return;}string authparameter = null;var authvalue = actioncontext.request.headers.authorization;if (authvalue != null && authvalue.scheme == "basic"){authparameter = authvalue.parameter; //authparameter:获取请求中经过base64编码的(用户:密码)}if (string.isnullorempty(authparameter)){challenge(actioncontext);return;}authparameter = encoding.default.getstring(convert.frombase64string(authparameter));var authtoken = authparameter.split(':');if (authtoken.length < 2){challenge(actioncontext);return;}if (!validateuser(authtoken[0], authtoken[1])){challenge(actioncontext);return;}var principal = new genericprincipal(new genericidentity(authtoken[0]), null);thread.currentprincipal = principal;if (httpcontext.current != null){httpcontext.current.user = principal;}base.onauthorization(actioncontext);}private void challenge(httpactioncontext actioncontext){var host = actioncontext.request.requesturi.dnssafehost;actioncontext.response = actioncontext.request.createresponse(httpstatuscode.unauthorized, "请求未授权,拒绝访问。");//actioncontext.response.headers.add("www-authenticate", string.format("basic realm=\"{0}\"", host));//可以使用如下语句actioncontext.response.headers.wwwauthenticate.add(new authenticationheadervalue("basic", string.format("realm=\"{0}\"", host)));}protected virtual bool validateuser(string username, string password){if (username.equals("admin", stringcomparison.ordinalignorecase) && password.equals("api.admin")) //判断用户名及密码,实际可从数据库查询验证,可重写{return true;}return false;}} }

 2.在需要认证授权后才能访问的controller中类或action方法上添加上述定义的类httpbasicauthenticationfilter,也可在global文件中将该类添加到全局过滤器中,即可

测试方法很简单,第一种直接在浏览器中访问(同上),第二种采用httpclient来调用web api,示例代码如下:

public async static void testloginapi3(){httpclient client = new httpclient();client.defaultrequestheaders.authorization = createbasicheader("admin", "api.admin");var response = await client.getasync("http://localhost:11099/api/test/");var r2 = await response.content.readasasync>();foreach (string item in r2){console.writeline("getvalues - item value:{0}", item);}response = await client.getasync("http://localhost:11099/api/test/8");var r3 = await response.content.readasasync();console.writeline("getvalue - item value:{0}", r3);}public static authenticationheadervalue createbasicheader(string username, string password){return new authenticationheadervalue("basic",convert.tobase64string(system.text.asciiencoding.ascii.getbytes(string.format("{0}:{1}", username, password))));}

实现basic基础认证,除了通过继承自authorizationfilterattribute来实现自定义的验证授权过滤器外,还可以通过继承自delegatinghandler来实现自定义的消息处理管道类,具体的实现方式可参见园子里的这篇文章:

http://www.cnblogs.com/createmyself/p/4857799.html

 第四种:digest摘要认证

 1.定义一个继承自delegatinghandler的httpdigestauthenticationhandler类,用于实现在消息管道中实现digest摘要认证,同时定义该类所需关联或依赖的其它类,源代码如下:

using system; using system.collections.concurrent; using system.net; using system.net.http; using system.net.http.headers; using system.security.cryptography; using system.security.principal; using system.text; using system.threading; using system.threading.tasks; using system.web;namespace webapplication1.models {public class httpdigestauthenticationhandler : delegatinghandler{protected async override task sendasync(httprequestmessage request, cancellationtoken cancellationtoken){try{httprequestheaders headers = request.headers;if (headers.authorization != null){header header = new header(request.headers.authorization.parameter, request.method.method);if (nonce.isvalid(header.nonce, header.nouncecounter)){string password = "www.zuowenjun.cn";//默认值//根据用户名获取正确的密码,实际情况应该从数据库查询if (header.username.equals("admin", stringcomparison.ordinalignorecase)){password = "api.admin";//这里模拟获取到的正确的密码}#region 计算正确的可授权的hash值string ha1 = string.format("{0}:{1}:{2}", header.username, header.realm, password).tomd5hash();string ha2 = string.format("{0}:{1}", header.method, header.uri).tomd5hash();string computedresponse = string.format("{0}:{1}:{2}:{3}:{4}:{5}",ha1, header.nonce, header.nouncecounter, header.cnonce, "auth", ha2).tomd5hash();#endregionif (string.compareordinal(header.response, computedresponse) == 0) //比较请求的hash值与正确的可授权的hash值是否相同,相则则表示验证通过,否则失败{// digest computed matches the value sent by client in the response field.// looks like an authentic client! create a principal.// var claims = new list//{// new claim(claimtypes.name, header.username),// new claim(claimtypes.authenticationmethod, authenticationmethods.password)//};// claimsprincipal principal = new claimsprincipal(new[] { new claimsidentity(claims, "digest") });// thread.currentprincipal = principal;// if (httpcontext.current != null)// httpcontext.current.user = principal;var principal = new genericprincipal(new genericidentity(header.username), null);thread.currentprincipal = principal;if (httpcontext.current != null){httpcontext.current.user = principal;}}}}httpresponsemessage response = await base.sendasync(request, cancellationtoken);if (response.statuscode == httpstatuscode.unauthorized){response.headers.wwwauthenticate.add(new authenticationheadervalue("digest", header.getunauthorizedresponseheader(request).tostring()));}return response;}catch (exception){var response = request.createresponse(httpstatuscode.unauthorized);response.headers.wwwauthenticate.add(new authenticationheadervalue("digest", header.getunauthorizedresponseheader(request).tostring()));return response;}}}public class header{public header() { }public header(string header, string method){string keyvaluepairs = header.replace("\"", string.empty);foreach (string keyvaluepair in keyvaluepairs.split(',')){int index = keyvaluepair.indexof("=", system.stringcomparison.ordinal);string key = keyvaluepair.substring(0, index).trim();string value = keyvaluepair.substring(index 1).trim();switch (key){case "username": this.username = value; break;case "realm": this.realm = value; break;case "nonce": this.nonce = value; break;case "uri": this.uri = value; break;case "nc": this.nouncecounter = value; break;case "cnonce": this.cnonce = value; break;case "response": this.response = value; break;case "method": this.method = value; break;}}if (string.isnullorempty(this.method))this.method = method;}public string cnonce { get; private set; }public string nonce { get; private set; }public string realm { get; private set; }public string username { get; private set; }public string uri { get; private set; }public string response { get; private set; }public string method { get; private set; }public string nouncecounter { get; private set; }// this property is used by the handler to generate a// nonce and get it ready to be packaged in the// www-authenticate header, as part of 401 responsepublic static header getunauthorizedresponseheader(httprequestmessage request){var host = request.requesturi.dnssafehost;return new header(){realm = host,nonce = webapplication1.models.nonce.generate()};}public override string tostring(){stringbuilder header = new stringbuilder();header.appendformat("realm=\"{0}\"", realm);header.appendformat(",nonce=\"{0}\"", nonce);header.appendformat(",qop=\"{0}\"", "auth");return header.tostring();}}public class nonce{private static concurrentdictionary>nonces = new concurrentdictionary>();public static string generate(){byte[] bytes = new byte[16];using (var rngprovider = new rngcryptoserviceprovider()){rngprovider.getbytes(bytes);}string nonce = bytes.tomd5hash();nonces.tryadd(nonce, new tuple(0, datetime.now.addminutes(10)));return nonce;}public static bool isvalid(string nonce, string noncecount){tuple cachednonce = null;//nonces.trygetvalue(nonce, out cachednonce);nonces.tryremove(nonce, out cachednonce);//每个nonce只允许使用一次if (cachednonce != null) // nonce is found{// nonce count is greater than the one in recordif (int32.parse(noncecount) > cachednonce.item1){// nonce has not expired yetif (cachednonce.item2 > datetime.now){// update the dictionary to reflect the nonce count just received in this request//nonces[nonce] = new tuple(int32.parse(noncecount), cachednonce.item2);// every thing looks ok - server nonce is fresh and nonce count seems to be // incremented. does not look like replay.return true;}}}return false;}} }

 

using system.linq; using system.security.cryptography; using system.text;namespace webapplication1.models {public static class hashhelper{public static string tomd5hash(this byte[] bytes){stringbuilder hash = new stringbuilder();md5 md5 = md5.create();md5.computehash(bytes).tolist().foreach(b => hash.appendformat("{0:x2}", b));return hash.tostring();}public static string tomd5hash(this string inputstring){return encoding.utf8.getbytes(inputstring).tomd5hash();}}}

2.将上述自定义的httpdigestauthenticationhandler类添加到全局消息处理管道中,代码如下:

public static class webapiconfig{public static void register(httpconfiguration config){config.maphttpattributeroutes();config.routes.maphttproute(name: "defaultapi",routetemplate: "api/{controller}/{id}",defaults: new { id = routeparameter.optional });config.messagehandlers.add(new httpdigestauthenticationhandler());//添加到消息处理管道中}}

3.在需要认证授权后才能访问的controller中类或action方法上添加authorize特性即可。

测试方法很简单,第一种直接在浏览器中访问(同上),第二种采用httpclient来调用web api,示例代码如下:

public async static void testloginapi4(){httpclienthandler handler = new httpclienthandler();handler.clientcertificateoptions = clientcertificateoption.manual;handler.credentials = new networkcredential("admin", "api.admin");httpclient client = new httpclient(handler);var response = await client.getasync("http://localhost:11099/api/test/");var r2 = await response.content.readasasync>();foreach (string item in r2){console.writeline("getvalues - item value:{0}", item);}response = await client.getasync("http://localhost:11099/api/test/8");var r3 = await response.content.readasasync();console.writeline("getvalue - item value:{0}", r3);}

该实现方法,参考了该篇文章:http://zrj-software.iteye.com/blog/2163487

实现digest摘要认证,除了上述通过继承自delegatinghandler来实现自定义的消息处理管道类外,也可以通过继承自authorizationfilterattribute来实现自定义的验证授权过滤器,basic基础认证与digest摘要认证流程基本相同,区别在于:basic是将密码直接base64编码(明文),而digest是用md5进行加密后传输,所以两者实现认证方式上,也基本相同。

最后说明一下,web service、wcf、web api实现身份验证的方法有很多,每种方法都有他所适用的场景,我这个系列文章仅是列举一些常见的实见身份验证的方法,一是给自己复习并备忘,二是给大家以参考,文中可能有不足之处,若发现问题,可以在下面评论指出,谢谢!

总结

以上是尊龙游戏旗舰厅官网为你收集整理的的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得尊龙游戏旗舰厅官网网站内容还不错,欢迎将尊龙游戏旗舰厅官网推荐给好友。

网站地图