关于WEB Service&WCF&WebApi实现身份验证之WebApi篇

简介:

之前先后总结并发表了关于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,若有则表示登录成功,否则失败,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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<AllowAnonymousAttribute>().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<CookieHeaderValue> 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,用于登录入口,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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);
                 //存储为cookie
                 HttpCookie 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) }); //测试用:当登录失败时,清除可能存在的身份验证Cookie
                 return  Request.CreateErrorResponse(HttpStatusCode.NotFound,  "登录失败,无效的用户名或密码!" );
             }
 
         }
 
         // GET api/test
         public  IEnumerable< string > GetValues()
         {
             return  new  string [] {  "value1" "value2"  };
         }
 
         // GET api/test/5
         public  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的相关方法,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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<dynamic>();
     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<IEnumerable< string >>();
     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< string >();
     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身份验证,配置如下:

1
2
< authentication  mode="Windows">
</ authentication >

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

1
2
3
< authorization >
   < deny  users="?"/>
</ authorization >

最后将WEB API寄宿到(或者说发布到)IIS,且需要在IIS中启用WINDOWS身份验证,如下图示:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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<IEnumerable< string >>();
     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< string >();
     Console.WriteLine( "GetValue - Item Value:{0}" , r3);
}

第三种:Basic基础认证

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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<AllowAnonymousAttribute>().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,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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<IEnumerable< string >>();
     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< string >();
     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摘要认证,同时定义该类所需关联或依赖的其它类,源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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<HttpResponseMessage> 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();
                         #endregion
 
                         if  (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<Claim>
                             //{
                             //                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 response
         public  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< string , Tuple< int , DateTime>>
         nonces =  new  ConcurrentDictionary< string , Tuple< int , DateTime>>();
 
         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< int , DateTime>(0, DateTime.Now.AddMinutes(10)));
 
             return  nonce;
         }
 
         public  static  bool  IsValid( string  nonce,  string  nonceCount)
         {
             Tuple< int , DateTime> 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 record
                 if  (Int32.Parse(nonceCount) > cachedNonce.Item1)
                 {
                     // nonce has not expired yet
                     if  (cachedNonce.Item2 > DateTime.Now)
                     {
                         // update the dictionary to reflect the nonce count just received in this request
                         //nonces[nonce] = new Tuple<int, DateTime>(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 ;
         }
     }
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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类添加到全局消息处理管道中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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<IEnumerable< string >>();
     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< string >();
     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实现身份验证的方法有很多,每种方法都有他所适用的场景,我这个系列文章仅是列举一些常见的实见身份验证的方法,一是给自己复习并备忘,二是给大家以参考,文中可能有不足之处,若发现问题,可以在下面评论指出,谢谢!


本文转自 梦在旅途 博客园博客,原文链接: http://www.cnblogs.com/zuowj/p/5123943.html ,如需转载请自行联系原作者

相关文章
|
9月前
phpstorm插件应用:Test RESTful WEB Service 控制台接口调试工具
phpstorm插件应用:Test RESTful WEB Service 控制台接口调试工具
117 0
|
1月前
|
存储 缓存 算法
关于 Service Worker 和 Web 应用对应关系的讨论
关于 Service Worker 和 Web 应用对应关系的讨论
12 0
|
2月前
|
Java API Apache
Apache CXF生成WebService的客户端
Apache CXF生成WebService的客户端
|
6月前
|
JSON 安全 API
使用 ABAP sproxy 事务码生成的 Proxy 消费 Web Service
使用 ABAP sproxy 事务码生成的 Proxy 消费 Web Service
54 0
|
2月前
|
XML 网络架构 数据格式
Ruby 教程 之 Ruby Web Service 应用 - SOAP4R 2
Ruby Web Service 应用 - SOAP4R
24 5
|
2月前
|
XML Linux 网络架构
Ruby 教程 之 Ruby Web Service 应用 - SOAP4R 1
Ruby Web Service 应用 - SOAP4R
23 3
|
8月前
|
XML Java API
Java Web Service Get请求使用指南
Java Web Service Get请求使用指南 在当今互联网时代,Web Service已经成为了现代软件开发中不可或缺的一部分。而Java作为一种广泛使用的编程语言,自然也提供了丰富的工具和库来支持Web Service的开发。本文将为大家介绍如何使用Java编程语言进行Web Service的Get请求。
86 0
|
4月前
|
Java 数据库连接 Apache
SpringBoot整合CXF实现WebService
SpringBoot整合CXF实现WebService
125 0
|
7月前
ABAP Web Service 调用的一个例子
ABAP Web Service 调用的一个例子
29 0
|
8月前
|
存储 JSON 安全
探索JSON Web Token(JWT):现代身份验证和授权的利器
在现代Web应用中,用户身份验证和授权是保护数据和资源安全的重要环节。JSON Web Token(JWT)作为一种轻量级的身份验证和授权机制,为我们提供了一种安全且高效的方式来传递信息。本文将深入探讨JWT的基本概念、结构,以及如何在应用中实现安全的身份验证和授权。
119 0