相信很多开发者都用过WebService来实现程序的面向服务,本文主要介绍WebService的身份识别实现方式,当然本文会提供一个不是很完善的例子,权当抱砖引玉了.
首先我们来介绍webservice下的两种验证方式,
一.通过集成windows身份验证
通过集成windows方式解决webservice的安全问题是一个很简洁,并且行之有效的解决方案,该方案的优点是比较安全,性能较好,当然因为与windows紧密的结合到了一起,缺点自然也很明显了,第一,不便于移植,第二,要进行相关的配置部署工作(当然我们也可以用代码来操作IIS,只不过比较麻烦,最近一直做自动化部署,所以一讲到配置马上就会联想到怎么去自动部署)
具体怎么做呢?
服务器端:配置IIS虚拟目录为集成windows身份验证
客户端:
Service1 wr = new Service1(); //web service实例??
wr.Credentials = new NetworkCredential("administrator","123"); //用户名密码??
lblTest.Text = wr.Add(2,2).ToString(); //调用Add的 web service方法?
二.使用 SoapHeader(SOAP 标头)自定义身份验证
SoapHeader 多数情况下用来传递用户身份验证信息,当然它的作用远不止如此,有待于在实际应用中发掘,体可以实现哪些东西大家有想法可以留言一起交流.
SoapHeader 使用步骤:
(1) 创建继承自 System.Web.WebServices.SoapHeader 的自定义 SoapHeader 类型。
(2) 在 WebService 中创建拥有 public 访问权限的自定义 SoapHeader 字段。
(3) 在需要使用 SoapHeader 的 WebMethod 上添加 SoapHeaderAttribute 访问特性。SoapHeaderAttribute 构造必须指定 memberName 参数,就是我们在第二步中申明的字段名称。
(4) 生成器会自动为客户端生成同名的自定义 SoapHeader 类型,只不过比起我们在 WebService 端创建的要复杂一些。同时还会为代理类型添加一个 soapheaderValue 属性。
下面展示一段SoapHeader的代码,多余的方法将会在后面用到
客户端
class
Program {
static
void
Main(
string
[] args) { Service1 ws
=
new
Service1(); ServiceCredential mycredential
=
new
ServiceCredential(); mycredential.User
=
"
gazi
"
; mycredential.Password
=
"
gazi
"
; ws.ServiceCredentialValue
=
mycredential;
string
mystr
=
ws.SayHello(); } }
服务器端
public
class
Service1 : System.Web.Services.WebService {
public
ServiceCredential myCredential; [WebMethod] [SoapHeader(
"
myCredential
"
,Direction
=
SoapHeaderDirection.In)]
public
string
SayHello() {
return
"
hello
"
; } }
public
class
ServiceCredential : SoapHeader {
public
string
User;
public
string
Password;
public
static
bool
ValideUser(
string
User,
string
Password) {
return
true
; }
public
static
void
CheckUser(Object sender,WebServiceAuthenticationEvent e) {
if
(ValideUser(e.User,e.Password)) {
return
; }
else
{ WebServiceAuthenticationModule module
=
sender
as
WebServiceAuthenticationModule; module.Result.AddRule(
"
验证错误
"
,
"
不能确认您的身份,请检查用户名和密码
"
); } } }
当我们拥有很多个类的时候,要添加一个或者删除一个验证方式(假设需要进行多种认证)是非常麻烦的,我们不可能跑到每个方法里面去加一个方法调用,这是灾难性的工作,当然我们也可以用AOP来实现,Aop的话需要额外增加很多代码或者直接引入第三方来做,但是我们可不可以有更简便的方法呢?
OK,答案就是使用HttpModule,我们集成IHttpModule写一个处理模块,那么它的原理是什么呢?具体进行了哪些操作呢?我们的思路如下:
- HTTP Module 分析 HTTP 消息以检查它们是不是 SOAP 消息。
- 如果 HTTP Module 检测到 SOAP 消息,它会读取 SOAP 标头。
- 如果 SOAP 消息的 SOAP 标头中有身份验证凭据,HTTP Module 将引发一个自定义 global.asax 事件。
下面来看看我们的Module代码
1
public
class
WebServiceAuthenticationModule : IHttpModule
2
{
3
private
static
WebServiceAuthenticationEventHandler
4
_eventHandler
=
null
;
5
///
<summary>
6
///
验证事件.绑定到此事件可进行对用户身份的识别
7
///
</summary>
8
public
static
event
WebServiceAuthenticationEventHandler Authenticate
9
{
10
add { _eventHandler
=
value; }
11
remove { _eventHandler
-=
value; }
12
}
13
public
Result Result
=
new
Result();
14
15
public
void
Dispose()
16
{
17
}
18
public
void
Init(HttpApplication app)
19
{
20
app.AuthenticateRequest
=
new
21
EventHandler(
this
.OnEnter);
22
Result.EndValid
=
new
23
EventHandler(
this
.OnCheckError);
24
}
25
26
///
<summary>
27
///
验证用户身份
28
///
</summary>
29
///
<param name="e"></param>
30
private
void
OnAuthenticate(WebServiceAuthenticationEvent e)
31
{
32
if
(_eventHandler
==
null
)
33
return
;
34
35
_eventHandler(
this
,e);
36
if
(e.User
!=
null
)
37
e.Context.User
=
e.Principal;
38
}
39
40
public
string
ModuleName
41
{
42
get
{
return
"
WebServiceAuthentication
"
; }
43
}
44
45
void
OnEnter(Object source,EventArgs eventArgs)
46
{
47
HttpApplication app
=
(HttpApplication)source;
48
HttpContext context
=
app.Context;
49
Stream HttpStream
=
context.Request.InputStream;
50
51
//
Save the current position of stream.
52
long
posStream
=
HttpStream.Position;
53
54
//
If the request contains an HTTP_SOAPACTION
55
//
header,look at this message.HTTP_SOAPACTION
56
if
(context.Request.ServerVariables[
"
HTTP_SOAPACTION
"
]
==
null
)
57
return
;
58
59
//
Load the body of the HTTP message
60
//
into an XML document.
61
XmlDocument dom
=
new
XmlDocument();
62
string
soapUser;
63
string
soapPassword;
64
65
try
66
{
67
dom.Load(HttpStream);
68
69
//
Reset the stream position.
70
HttpStream.Position
=
posStream;
71
72
//
Bind to the Authentication header.
73
soapUser
=
74
dom.GetElementsByTagName(
"
User
"
).Item(
0
).InnerText;
75
soapPassword
=
76
dom.GetElementsByTagName(
"
Password
"
).Item(
0
).InnerText;
77
}
78
catch
(Exception e)
79
{
80
//
Reset the position of stream.
81
HttpStream.Position
=
posStream;
82
83
//
Throw a SOAP exception.
84
XmlQualifiedName name
=
new
85
XmlQualifiedName(
"
Load
"
);
86
SoapException soapException
=
new
SoapException(
87
"
SOAP请求没有包含必须的身份识别信息
"
,name,e);
88
throw
soapException;
89
}
90
//
触发全局事件
91
OnAuthenticate(
new
WebServiceAuthenticationEvent
92
(context,soapUser,soapPassword));
93
Result.OnEndValid();
94
return
;
95
}
96
void
OnCheckError(Object sender,EventArgs e)
97
{
98
if
(Result.BrokenRules.Count
==
0
)
99
{
100
return
;
101
}
102
else
103
{
104
HttpApplication app
=
HttpContext.Current.ApplicationInstance;
105
app.CompleteRequest();
106
app.Context.Response.Write(Result.Error);
107
}
108
}
109
}
Authenticate事件是一个静态的变量,这样我们可以在程序的外部来订阅和取消订阅事件(非静态的public 事件在外部也是不能进行订阅和取消订阅事件的,这也是事件和委托的一个区别之一)
下面是我们的事件参数以及委托
1
public
delegate
void
WebServiceAuthenticationEventHandler(Object sender,WebServiceAuthenticationEvent e);
2
3
///
<summary>
4
///
封装的事件参数
5
///
</summary>
6
public
class
WebServiceAuthenticationEvent : EventArgs
7
{
8
private
IPrincipal _IPrincipalUser;
9
private
HttpContext _Context;
10
private
string
_User;
11
private
string
_Password;
12
13
public
WebServiceAuthenticationEvent(HttpContext context)
14
{
15
_Context
=
context;
16
}
17
18
public
WebServiceAuthenticationEvent(HttpContext context,
19
string
user,
string
password)
20
{
21
_Context
=
context;
22
_User
=
user;
23
_Password
=
password;
24
}
25
public
HttpContext Context
26
{
27
get
{
return
_Context; }
28
}
29
public
IPrincipal Principal
30
{
31
get
{
return
_IPrincipalUser; }
32
set
{ _IPrincipalUser
=
value; }
33
}
34
public
void
Authenticate()
35
{
36
GenericIdentity i
=
new
GenericIdentity(User);
37
this
.Principal
=
new
GenericPrincipal(i,
new
String[
0
]);
38
}
39
public
void
Authenticate(
string
[] roles)
40
{
41
GenericIdentity i
=
new
GenericIdentity(User);
42
this
.Principal
=
new
GenericPrincipal(i,roles);
43
}
44
public
string
User
45
{
46
get
{
return
_User; }
47
set
{ _User
=
value; }
48
}
49
public
string
Password
50
{
51
get
{
return
_Password; }
52
set
{ _Password
=
value; }
53
}
54
public
bool
HasCredentials
55
{
56
get
57
{
58
if
((_User
==
null
)
||
(_Password
==
null
))
59
return
false
;
60
return
true
;
61
}
62
}
63
}
我们在Global.asax的Application_Start方法里面把前面介绍的静态方法ServiceCredential.CheckUser订阅到我们Authenticate事件上,前面提到的增加和删除多种认证方式就是通过这种方法实现的.
protected void Application_Start(object sender, EventArgs e)
{
WebServiceAuthenticationModule.Authenticate += ServiceCredential.CheckUser;
}
我们在ServiceCredential.ValideUser方法设置了返回false,这是针对测试的一个配置,实际情况下我们可以和数据库结合起来写一个认证 运行上面讲解SoapHeader的那段代码,你会发现我们的认证已经有效了.关于文章中用到的Result类改天在用一篇文章记录一下,这是一个非常好的记录错误的方案.