[ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证
一、iauthenticationfilter接口
所有的authenticationfilter类型均实现了iauthenticationfilter接口,该接口定义在命名空间“system.web.m.filters”下(其他四种过滤器接口都定义在“system.web.mvc”命名空间下)。如下面的代码片断所示,onauthentication和onauthenticationchallenge这两个方法被定义在此接口中,前者用于对请求实施认证,后者则负责将相应的认证质询发送给请求者。
1: public interface iauthenticationfilter
2: {
3: void onauthentication(authenticationcontext filtercontext);
4: void onauthenticationchallenge(authenticationchallengecontext filtercontext);
5: }
定义在iauthenticationfilter接口的两个方法都将一个上下文对象作为其唯一参数。onauthentication方法的这个参数类型为authenticationcontext,如下面的代码片断所示,它是controllercontext的子类。authenticationcontext的actiondescriptor返回的自然是用于描述目标action方法的actiondescriptor对象。借助于principal属性,我们可以获取或设置代表当前用户的principal对象。如果我们在执行onauthentication方法的过程中设置了authenticationcontext的result属性,提供的actionresult将直接用于响应当前请求。
1: public class actionexecutingcontext : controllercontext
2: {
3: public actionexecutingcontext();
4: public actionexecutingcontext(controllercontext controllercontext, actiondescriptor actiondescriptor,idictionary<string, object> actionparameters);
5:
6: public virtual actiondescriptor actiondescriptor { get; set; }
7: public virtual idictionary<string, object> actionparameters { get; set; }
8: public actionresult result { get; set; }
9: }
onauthenticationchallenge方法的参数类型为authenticationchallengecontext。如下面的代码片断所示,它依然是controllercontext的子类。它同样具有一个用于描述目标action方法的actiondescriptor属性,其result属性代表的actionresult对象将用于响应当前请求。
1: public class actionexecutedcontext : controllercontext
2: {
3: public actionexecutedcontext();
4: public actionexecutedcontext(controllercontext controllercontext, actiondescriptor actiondescriptor, bool canceled, exception exception);
5:
6: public virtual actiondescriptor actiondescriptor { get; set; }
7: public virtual bool canceled { get; set; }
8: public virtual exception exception { get; set; }
9: public bool exceptionhandled { get; set; }
10: public actionresult result { get; set; }
11: }
二、authenticationfilter的执行流程
我们知道身份认证总是对请求处理的第一个步骤,因为只有确定了请求者的真实身份,安全才能得到保障,所以authenticationfilter是最先被执行的一类过滤器。所有过滤器的执行都是actioninvoker来驱动的,asp.net mvc在默认情况下采用的actioninvoker是一个asynccontrolleractioninvoker对象,后者类型派生于controlleractioninvoker。controlleractioninvoker针对authenticationfilter的执行体现在如下两个方法(invokeauthenticationfilters和invokeauthenticationfilterschallenge)上。
1: public class controlleractioninvoker : iactioninvoker
2: {
3: //其他成员
4: protected virtual authenticationcontext invokeauthenticationfilters(controllercontext controllercontext,ilist<iauthenticationfilter> filters, actiondescriptor actiondescriptor);
5: protected virtual authenticationchallengecontext invokeauthenticationfilterschallenge(controllercontext controllercontext, ilist<iauthenticationfilter> filters, actiondescriptor actiondescriptor, actionresult result);
6: }
如果多个authenticationfilter同时被应用到目标action方法上,controlleractioninvoker会根据对应filter的order/scope属性对它们进行排序。随后controlleractioninvoker会根据当前controllercontext、描述目标action方法的actiondescriptor对象以及原始的principal(对应于当前httpcontext的user属性)创建一个authenticationcontext对象,并以此作为参数以此调用每个authenticationfilter对象的onauthentication对象实施认证。
在目标action方法被执行之后,通过本书第11章“view的呈现”我们知道最终执行的结果会被封装为一个actionresult对象。controlleractioninvoker会利用当前controllercontext、描述目标action方法的actiondescriptor对象和这个actionresult创建一个authenticationchallengecontext对象,并将其作为参数依次调用每个authenticationfilter的onauthenticationchallenge方法。这个authenticationchallengecontext对象的result属性最终返回的actionresult对象将被用来对请求予以响应。
右图基本反映了整个“authenticationfilter链”的执行流程,但是如果在执行某个authenticationfilter对象的onauthenticatio方法时对作为参数的authenticationcontext对象的result属性作了相应的设置,针对整个“authenticationfilter链”的执行将会立即中止,指定的这个actionresult对象将用于响应当前请求。如果在执行过程中对authenticationcontext对象的principal属性作了相应的设置,该属性值将会作为当前httpcontext和当前线程的principal。
三、实例演示:通过自定义authenticationfilter实现basic认证
在asp.net mvc的应用接口中,我们找不到iauthenticationfilter接口的实现者。为了让大家对这个在asp.net mvc 5才引入的过滤器具有更加深刻的认识,我们接下来会通过一个实例来演示如何通过自定义的authenticationfilter实现针对basic方案的认证。不过在这之前,我们有必要对basic这种基本的认证方法作一个基本的了解。basic和digest是两种典型的http认证方案。对于前者,虽然客户端提供的认证凭证(用户名+密码)仅仅是被base64编码而没有被,但是我们可以通过采用https传输利用ssl来解决机密性的问题,所以basic认证也不失为一种不错的认证方案。左图体现了basic认证的基本流程,可以看出这也是一种典型的采用“质询-应答”模式的认证方案,整个流程包含如下两个基本步骤。
客户端向服务端发送一个http请求,服务端返回一个状态为“401, unauthorized”的响应。该响应具有一个“www-authenticate”的报头标明采用的是basic认证方案。basic认证是在一个“领域(realm)”限定的上下文中进行的,该报头还可以执行认证的领域,左图所示的www-authenticate报头值为:basic realm="localhost"。
· 客户端向服务端发送一个携带基于用户名/密码的认证凭证的请求。认证凭证的格式为“{username}:{password}”,并采用base64编码(编码的目的不是为了保护提供的密码)。这样一个经过编码的认证凭证被存放在请求报头authorization中,相应的认证方案类型(basic)依然需要在该报头中指定,左图所示的authorization报头值为:basic ycdfaysss==。服务端接收到请求之后,从authorization报头中提取凭证并对其进行解码,最后采用提取的用户名和密码实施认证。认证成功之后,该请求会得到正常的处理,并回复一个正常的响应。
在正式介绍如果定义这个实现basic认证的authenticationfilter之前,我们不妨先来看看使用了这个自定义authenticationfilter会产生怎样的效果。我们在一个asp.net mvc应用中定义了如下一个homecontroller,定义其中的默认action方法index会输出以三种形式体现的“当前用户名”。homecontroller类型上应用的authenticateattribute特性正是我们自定义的authenticationfilter。
1: [authenticate]
2: public class homecontroller : controller
3: {
4: public void index()
5: {
6: response.write(string.format("controller.user: {0}<br/>", this.user.identity.name));
7: response.write(string.format("httpcontext.user: {0}<br/>", this.controllercontext.httpcontext.user.identity.name));
8: response.write(string.format("thread.currentprincipal: {0}", thread.currentprincipal.identity.name));
9: }
10: }
由于默认提供对basic认证的支持,所以当我们运行该程序后如下图所示的登录对话框会自动弹出,当我们输入正确的用户名和密码(用户名和密码直接维护在authenticateattribute上)后,当前登录用户名会呈现在浏览器上。
这个用于实现basic认证的authenticateattribute定义如下,简单起见我们将帐号采用的用户名和密码保存在一个静态字段中。具体的认证实现在实现的onauthentication方法中,我们在该方法中调用isauthenticated判断请是否经过认证,并在认证成功的情况下得到代表请求用户的principal对象,然对作为参数的authenticationcontext对象的principal属性进行赋值。对于没有经过认证的请求,我们会调用另一个方法processunauthenticatedrequest对其进行处理。
1: public class authenticateattribute:filterattribute,iauthenticationfilter
2: {
3: public const string authorizationheadername ="authorization";
4: public const string wwwauthenticationheadername ="www-authenticate";
5: public const string basicauthenticationscheme ="basic";
6: private static dictionary<string, string> useraccounters;
7:
8: static authenticateattribute()
9: {
10: useraccounters = new dictionary<string, string>(stringcomparer.ordinalignorecase);
11:
12: useraccounters.add("foo", "password");
13: useraccounters.add("bar", "password");
14: useraccounters.add("baz", "password");
15: }
16:
17: public void onauthentication(authenticationcontext filtercontext)
18: {
19: iprincipal user;
20: if (this.isauthenticated(filtercontext, out user))
21: {
22: filtercontext.principal = user;
23: }
24: else
25: {
26: this.processunauthenticatedrequest(filtercontext);
27: }
28: }
29:
30: protected virtual authenticationheadervalue getauthenticationheadervalue(authenticationcontext filtercontext)
31: {
32: string rawvalue = filtercontext.requestcontext.httpcontext.request.headers[authorizationheadername];
33: if (string.isnullorempty(rawvalue))
34: {
35: return null;
36: }
37: string[] split = rawvalue.split(' ');
38: if (split.length != 2)
39: {
40: return null;
41: }
42: return new authenticationheadervalue(split[0], split[1]);
43: }
44:
45: protected virtual bool isauthenticated(authenticationcontext filtercontext, out iprincipal user)
46: {
47: user = filtercontext.principal;
48: if (null != user & user.identity.isauthenticated)
49: {
50: return true;
51: }
52:
53: authenticationheadervalue token = this.getauthenticationheadervalue(filtercontext);
54: if (null != token && token.scheme == basicauthenticationscheme)
55: {
56: string credential = encoding.default.getstring(convert.frombase64string(token.parameter));
57: string[] split = credential.split(':');
58: if (split.length == 2)
59: {
60: string username = split[0];
61: string password;
62: if (useraccounters.trygetvalue(username, out password))
63: {
64: if (password == split[1])
65: {
66: genericidentity identity = new genericidentity(username);
67: user = new genericprincipal(identity, new string[0]);
68: return true;
69: }
70: }
71: }
72: }
73: return false;
74: }
75:
76: protected virtual void processunauthenticatedrequest(authenticationcontext filtercontext)
77: {
78: string parameter = string.format("realm=\"{0}\"", filtercontext.requestcontext.httpcontext.request.url.dnssafehost);
79: authenticationheadervalue challenge = new authenticationheadervalue(basicauthenticationscheme, parameter);
80: filtercontext.httpcontext.response.headers[wwwauthenticationheadername] = challenge.tostring();
81: filtercontext.result = new httpunauthorizedresult();
82: }
83:
84: public void onauthenticationchallenge(authenticationchallengecontext filtercontext) {}
85: }
在对请求实施认证的isauthenticated方法中,我们会试图从请求的authorization报头中提取安全凭证,并按照basic凭证的格式解析出用户名和密码。只有在用户名和密码匹配的情况下,我们认为请求通过认证,并根据解析出来的用户名创建一个genericprincipal对象作为输出参数user的值。如果请求并为通过认证(它可以是一个匿名请求,或者提供的用户名与密码不匹配),方法processunauthenticatedrequest会被调用。在此情况下,它会对响应的www-authenticate报头进行相应的设置,并创建一个httpunauthorizedresult对象作为authenticationcontext对象的result属性,那么客户端最终会接收到一个状态为“401, unauthorized”的响应。
上一篇: NoSQL开篇之为什么要使用NoSQL
下一篇: sql注入之手工注入示例详解
推荐阅读
-
asp.net MVC利用自定义ModelBinder过滤关键字的方法(附demo源码下载)
-
asp.net MVC利用自定义ModelBinder过滤关键字的方法(附demo源码下载)
-
Asp.net MVC利用knockoutjs实现登陆并记录用户的内外网IP及所在城市(推荐)
-
asp.net core MVC之实现基于token的认证
-
[ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证
-
ASP.NET MVC中利用AuthorizeAttribute实现访问身份是否合法以及Cookie过期问题的处理
-
Asp.net MVC利用knockoutjs实现登陆并记录用户的内外网IP及所在城市(推荐)
-
Asp.net Core中实现自定义身份认证的示例代码
-
Asp.net MVC利用knockoutjs实现登陆并记录用户的内外网IP及所在城市
-
利用Razor在ASP.NET MVC中的实现,自定义视图引擎框架(2)