Nancy框架
- 一、创建第一个 Nancy 应用
- 二、探索 Nancy 的 module
- 1. 模块能够在全局被发现
- 2. 使用模块为路由创建一个根
- 三、定义路由
- 1. 方法
- 2. 模式
- 3. 模式的优先级
- 4. 动作
- 5. 条件
- 6. 路由片段约束
- 6.1 自定义约束
- 例子
- 6.1 自定义约束
- 7. 选择去调用路由的秘诀
- 8. 疯狂的路由
- 四、自定义路由
- 五、异步
- 1. 语法
- 2 语法例子
- 六、查看 DynamicDictionary
- 七、module 的 before/after 钩子
- 1. 在路由被调用前拦截请求
- 2. After 拦截器
- 八、Application 的 Before,After 和 OnError 管道
- 1.Before 拦截
- 2. After 拦截
- 3. 错误拦截器
- 4. 构建自己的钩子
- 九、模型绑定
- 1. 屏蔽不想要的信息
- 2. 绑定配置
- 3. 反序列化 rich request body payloads(负载)
- 4. 模型绑定 Checkbox
- 5. 绑定到 list
- 5.1 绑定 arrary 到单独的对象
- 5.2 绑定到对象的 list
- 5.3 HTML form 中的 List 分隔符
- 十、Bootstrapper
- 1. 简单的修改 bootstrapper
- 2. 找到合适的 bootstrapper
- 3. 使用自动注册
- 十一、视图引擎
- 1. 在路由中渲染视图
- 2. 从模型中解析视图的名称
- 十二、超简单视图引擎
- 1. 标准变量替换
- 2. 循环
- 3. 条件
- 4. 隐式条件
- 5. HTML 编码
- 6. 部分 Patials
- 7. Master 页和 section
- 8. 防止伪造 token
- 9. 路径扩展
- 10. 扩展 SSVE
- 十二、Razor 引擎
- 1. 安装 Razor
- 2. 配置 Razor
- 十三、实现自己的视图引擎需要注意的地方
- 十四、视图位置约定
- 1. 查看默认约定
- 1.1 根约定
- 1.2 视图文件夹约定
- 1.3 视图和模块路径约定
- 1.4 模块路径约定
- 1.5 模块名称约定
- 1.6 视图模块名称约定
- 2. 从模型类型推断是退名
- 3. 自定义约定
- 3. 使用 IConventions 定义自己的约定
- 1. 查看默认约定
- 十五、本地化
- 十六、测试应用
- 十七、根路径
- 1. 改变跟路径
- 2. 上传文件
- 十八、管理静态内容
- 十九、诊断
- 1. 配置到 dashboard 的访问
- 2. 去除诊断
- 3. 有哪些工具呢?
- 3.1 信息
- 3.2 配置
- 3.3 请求跟踪
- 3.4 交互式的诊断
- (1)IDiagnosticsProvider 接口
- (2)可诊断的对象
- (3)提供描述给方法
- (4)自定义模板输出
- (5)创建诊断提供者
- 二十、添加自己的 favicon
- 1. 替换默认的 FavIcon
- 2. 使用内嵌 icon
- 3. 移除 ICON
- 二十一、添加自定义的错误页面
- 二十二、加密帮助方法
- 1. IEncryptionProvider 接口
- 2. IHmacProvider 接口
- 3. IKeyGenerator 接口
- 4. 加密配置类型 CryptographyConfiguration
- 二十三、Content negotiation(内容协商)
- 1. Response Processor
- 1.1 匹配优先级
- 1.2 默认响应处理器
- 2. 控制协商
- 3. 支持文件扩展名
- 5. 使用 IConventions 来定义自己的约定
- 6. 自动协商头
- 7. 更多信息
- 1. Response Processor
- 二十四、使用转换器来扩展序列化
- 二十五、授权
- 1. 了解用户
- 2. 保护你的资源
- 3. 创造你自己的安全扩展
- 4. 实现自己的验证 provider
- 5. 无状态认证
- 5.1 配置并开启无状态认证
- 5.2 简单配置
- 6. Form 认证
- 6.1 User mapper
- 6.2 修改应用,处理 form 认证
- 6.3 启用 form 认证
- 6.4 关于加密,还有一些话
- 6.5 更多
- 7. 令牌认证
- 7.1 认识 Nancy 的令牌认证
- 7.2 基本原理
- 7.3 使用
- 7.3.1 Nancy 配置
- 7.3.2 客户端配置
- 8. 幕后的工作
一、创建第一个 Nancy 应用
- 安装 Nancy 项目模板
- 创建
Nancy Empty Web Application with ASP.NET Hosting
- 添加
Nancy module
, 它是一个标准 C# 类,通过添加下面几行代码定义了 web 应用的路由处理方法。 - 编译并运行。
public class HelloModule : NancyModule
{
public HelloModule()
{
Get["/"] = parameters => "Hello World";
}
}
二、探索 Nancy 的 module
Module 继承自NancyModule
类。Module 是必不可少的. 它不仅定义了路由,还提供了许多其他信息,比如请求、上下文、构造响应的辅助方法、视图渲染等等。
1. 模块能够在全局被发现
可以在任意地方定义 module,比如外部的 dll 等,这为代码的复用带来很大的方便。不用担心效率问题,扫描 module 只在程序启动时发生。
2. 使用模块为路由创建一个根
类似命名空间的概念,在创建构造方法时传给 base 一个名称。
public class ResourceModule : NancyModule
{
public ResourceModule() : base("/products")
{
// would capture routes to /products/list sent as a GET request
Get["/list"] = parameters => {
return "The list of products";
};
}
}
三、定义路由
路由是在 module 的构造方法中定义的。为了定义一个路由,你需要声明方法
+模式
+动作
+(可选)条件
比如:
public class ProductsModule : NancyModule
{
public ProductsModule()
{
Get["/products/{id}"] = _ =>
{
//do something
};
}
}
或者异步
public class ProductsModule : NancyModule
{
public ProductsModule()
{
Get["/products/{id}", runAsync: true] = async (_, token) =>
{
//do something long and tedious
};
}
}
1. 方法
支持 HTTP 常见方法:DELETE
, GET
, HEAD
, OPTIONS
, POST
, PUT
, PATCH
2. 模式
模式能够自定义,Nancy 提供了一些常用的:
- 字面量 -
/some/literal/segments
- 捕获片段 -
/{name}
,获取 URL 的片段,并传给路由的 Action - 捕获可选片段 -
/{name?}
,添加了一个问号,片段就是可选的了 - 捕获可选 / 默认片段 -
/{name?default}
- 正则片段 -
/(?
,使用命名捕获组来捕获片段,如果不需要捕获,使用非捕获组,比如[\d]{1,2}) (?:regex-goes-here)
- 贪心片段 -
/{name*}
,从 / 处开始捕获 - 贪心正则捕获 -
^(?
[a-z]{3, 10}(?:/{1})(? [a-z]{5, 10}))$ - 多个捕获片段 -
/{file}.{extension}
或者/{file}.ext
3. 模式的优先级
4. 动作
动作时一个 lambda 表达式Func
,输入时DynamicDictionary
,详见此处.
响应可以使任意的 model,最终的结果会被 Content Negotiation 处理。但是如果返回值是Response
类型,则原样返回。
Response
对象有几个隐形转换操作:
int
变为 Http 的状态HttpStatusCode
枚举值string
直接是相应的 bodyAction
则写道 response stream 中
5. 条件
路由条件用来过滤(比如登录非登录)。使用Func
的 lambda 表达式定义.
Post["/login", (ctx) => ctx.Request.Form.remember] = _ =>
{
return "Handling code when remember is true!";
}
Post["/login", (ctx) => !ctx.Request.Form.remember] = _ =>
{
return "Handling code when remember is false!";
}
6. 路由片段约束
Get["/intConstraint/{value:int}"] = _ => "Value " + _.value + " is an integer.";
只有为 int 的才会匹配。
约束:
int
decimal
guid
bool
alpha
datetime
datetime(format)
min(minimum)
max(maximum)
range(minimum, maximum)
minlength(length)
maxlength(length)
length(minimum, maximum)
6.1 自定义约束
实现IRouteSegmentConstraint
接口,或者继承自
RouteSegmentConstraintBase
- Base class for a named constraint.ParameterizedRouteSegmentConstraintBase
- Base class for a named constraint that accepts arguments.
例子
一个 email 约束
public class EmailRouteSegmentConstraint : RouteSegmentConstraintBase
{
public override string Name
{
get { return "email"; }
}
protected override bool TryMatch(string constraint, string segment, out string matchedValue)
{
if (segment.Contains("@"))
{
matchedValue = segment;
return true;
}
matchedValue = null;
return false;
}
}
用法
Get["/profile/{value:email}"] = _ => "Value " + _.value + " is an e-mail address.";
7. 选择去调用路由的秘诀
一个请求有时符合多个模式,此时记住:
- module 的顺序在启动时不定
- 同一 module 中的路由是按顺序来的
- 多个匹配中,得分最高的匹配
- 得分相同的匹配按照启动时的顺序匹配
8. 疯狂的路由
一些可能的用法:
// would capture routes like /hello/nancy sent as a GET request
Get["/hello/{name}"] = parameters => {
return "Hello " + parameters.name;
};
// would capture routes like /favoriteNumber/1234, but not /favoriteNumber/asdf as a GET request
Get["/favoriteNumber/{value:int}"] = parameters => {
return "So your favorite number is " + parameters.value + "?";
};
// would capture routes like /products/1034 sent as a DELETE request
Delete[@"/products/(?[\d]{1,7})"] = parameters => {
return 200;
};
// would capture routes like /users/192/add/moderator sent as a POST request
Post["/users/{id}/add/{category}"] = parameters => {
return HttpStatusCode.OK;
};
四、自定义路由
http://www.philliphaydon.com/2013/04/nancyfx-implementing-your-own-routing/
五、异步
1. 语法
Before/After 管道、主路由委托都可以使用 async. 语法绝大部分与同步代码一致,但需要注意下面的变化:
- before/after 钩子接受两个参数,context 和 cancellation token(取消令牌),而不仅仅是 context
- 路由定义有一个附加的 bool 参数,并且委托接受两个参数,一个捕获的参数,另一个 cancellation token.
2 语法例子
public MainModule()
{
Before += async (ctx, ct) =>
{
this.AddToLog("Before Hook Delay\n");
await Task.Delay(5000);
return null;
};
After += async (ctx, ct) =>
{
this.AddToLog("After Hook Delay\n");
await Task.Delay(5000);
this.AddToLog("After Hook Complete\n");
ctx.Response = this.GetLog();
};
Get["/", true] = async (x, ct) =>
{
this.AddToLog("Delay 1\n");
await Task.Delay(1000);
this.AddToLog("Delay 2\n");
await Task.Delay(1000);
this.AddToLog("Executing async http client\n");
var client = new HttpClient();
var res = await client.GetAsync("http://nancyfx.org");
var content = await res.Content.ReadAsStringAsync();
this.AddToLog("Response: " + content.Split('\n')[0] + "\n");
return (Response)this.GetLog();
};
}
六、查看 DynamicDictionary
DynamicDictionary
类似字典,但功能更多. 从请求中获取的值都保存到它里面。可以使用属性或者 index 来使用捕获的值。
Get["/hello/{name}"] = parameters => {
return "Hello " + parameters.name;
};
Get["/goodbye/{name}"] = parameters => {
return "Goodbye " + parameters["name"];
};
存储的值可以显示或者隐式的转换为基础类型或者特殊属性. 使用HasValue
决定是否被赋值。值已经实现了IEquatable<>
和IConvertible
接口。
七、module 的 before/after 钩子
除了为特定的路由定义处理程序, module 还可以拦截匹配某个路由的请求, 请求前后都能做到。重要的是要理解, 只有传入的请求匹配模块的路由之一,这些拦截器才会被调用。
1. 在路由被调用前拦截请求
Before 拦截器能让你修改请求,甚至可以通过返回一个 response 来放弃请求。
Before += ctx => {
return ;
};
定义 Before 拦截器的语法与定义路由有些不同。因为它是定义在 module 上,被所有路由调用,所以不需要匹配模式。
传给拦截器的是当前请求的 NancyContext 实例。
最后的不同就是拦截器的返回值,如果返回null
,拦截器将主动权转给路由;如果返回Response
对象,则路由不起作用。
2. After 拦截器
与定义 Before 烂机器相同,但是没有返回值。
After += ctx => {
// Modify ctx.Response
};
Before 拦截器可以修改 Request,相应的,After 拦截器可以修改 Response。
八、Application 的 Before,After 和 OnError 管道
应用管道能在所有的路由上执行,是全局性的。
1.Before 拦截
应用级的Before
钩子通过Func
函数定义:
pipelines.BeforeRequest += (ctx) => {
return ;
};
异步版本的:
pipelines.BeforeRequest += async (ctx, token) => {
return ;
};
2. After 拦截
After 拦截器通过 `Action 定义:
pipelines.AfterRequest += (ctx) => {
// Modify ctx.Response
};
3. 错误拦截器
OnError
拦截器用来拦截路由发生的错误。通过它可以获取NancyContext
和发生的异常。
OnError
拦截器通过Func
函数定义:
pipelines.OnError += (ctx, ex) => {
return null;
};
System.AggregateExceptions 在 OnError 管道中的注意事项:
路由是通过许多嵌套的 Task(System.Threading.Tasks.Task
) 来执行的。如果那个任务出现了问题,异常会被包装到System.AggregateException
。System.AggregateException
可以持有任意个异常。
如果只有一个异常,Nancy 会解包异常并且交给OnError
管道。如果发生多个异常,Nancy 会使用System.AggregateException
,以避免吞异常。
4. 构建自己的钩子
在 Bootstrapper 中创建系统级的钩子. 可以在ApplicationStartup
或者RequestStartup
方法中定义它们。这是因为也许你需要在钩子中使用容器中的一些东西。两个方法的不同之处在于范围不同。
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
}
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
}
通过使用pipelines
中适当的属性来创建钩子。它允许你获取BeforeRequest
, AfterRequest
和OnError
属性。
九、模型绑定
发送数据给 Nancy 可以有多种方法,比如 Query String, 路由捕获参数、请求体 request body。手工处理这些不同的方法也可以,但是还有一种方法就是统一处理,绑定到model
。
Nancy 只用一行代码就能处理上述的所有情况,并且能接受JSON
和XML
形式的请求。
也可以扩展 Nancy 的模型绑定。
Nancy 的模型绑定在NancyModule
中被定义为一个单独的扩展方法。该扩展在Nancy.ModelBinding
命名空间里,并且添加了 Bind() 和 BindTo() 方法
Foo f = this.Bind();
var f = this.Bind();
var f = this.BindTo(instance);
上面 3 个有着相同的功能,他们提供了做同一事物的不同方法。前两个使用 Bind() 重载来创建Foo
类型的实例,并且绑定;BindTo() 则绑定到现有实例。
1. 屏蔽不想要的信息
var f = this.Bind(f => f.id, f => f.creator, f => f.createddate);
或者
var f = this.Bind("id", "creator", "createddate");
当绑定到到 arrary, list 或者 ienumerable 时,屏蔽的是序列中的元素。
2. 绑定配置
使用BindingConfig
实例来修改 model binder 的默认行为。
下面是BindingConfig
提供的一些配置项:
属性 | 描述 | 默认 |
---|---|---|
BodyOnly | 是否只绑定 request body。这种情况下,request 和 context 参数都不会被绑定。如果没有 body 并且没有选项,那么绑定就不会放生 | false |
IgnoreErrors | 是否忽略绑定错误并且继续下一个属性 | false |
Overwrite | 丙丁是否可以覆盖没有默认值的属性 | true |
不准 Overwrite 还有一个快捷方法:BindingConfig.NoOverwrite
3. 反序列化 rich request body payloads(负载)
有时你像在请求中发送结构化的数据,比如JSON
或者XML
,并且绑定到模型。模型绑定器支持这种反序列化。
Nancy 支持两种反序列化:JSON 和 XML。绑定器根据 Http 的Content-type
头来决定使用哪一种反序列化。
默认使用 JSON 反序列化来处理application/json
, text/json
和application/vnd....+json
。同样的使用 XML 反序列化来处理application/xml
, text/xml
和application/vnd....+xml
对于其他模型绑定器,你可以使用自己的反序列化,并且 Nancy 会自动检测他们,任何用户定义的绑定器的优先级都高于内建的。
注意:如果你使用 Nancy.Json.JsonSetting.MaxJsonLength Exceeded 错误,那是因为你的 payloads 太高了,在 Bootstrapper 中更改限制:ApplicationStartup
中设置Nancy.Json.JsonSettings.MaxJsonLength=int.MaxValue
4. 模型绑定 Checkbox
要绑定复选框到 bool 值,确定设置value=true
:
public class LoginModel
{
public bool RememberMe { get; set; }
}
5. 绑定到 list
5.1 绑定 arrary 到单独的对象
如果有一个 form:
而且有一个类:
public class Posts
{
public string[] Tags { get; set; }
public int[] Ints { get; set; }
}
使用一个简单的语句:
var listOfPosts = this.Bind();
5.2 绑定到对象的 list
可以使用this.Bind
来绑定对象列表:>();
public class User
{
public string Name { get; set; }
public int Commits { get; set; }
}
5.3 HTML form 中的 List 分隔符
两种分隔符
- 下划线 (
Name_1
,Name_2
等) - 括号 (
Name[1]
,Name[2]
等)
十、Bootstrapper
bootstrapper 负责自动发现模型、自定义模型绑定、依赖等等。可以被替换掉。
1. 简单的修改 bootstrapper
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
// your customization goes here
}
}
2. 找到合适的 bootstrapper
应用启动时,它会寻找自定义的 bootstrap,如果没有找到,则使用DefaultNancyBootstrap
。每个应用只能有一个 bootstrapper. 如果有多个,则 Nancy 寻找最底层的 bootstrapper。
3. 使用自动注册
注入自己的依赖到 NancyModule 中
public class Home : NancyModule
{
public Home(IMessageService service)
{
//If there is only one implementation of IMessageService in the application,
// TinyIoC will resolve the dependency on its own and inject it in the module.
}
}
十一、视图引擎
视图引擎就是输入 “模板” 和“模型”,输出 HTML(大部分情况下)到浏览器。
Nancy 默认使用 SuperSimpleViewEngine
。它支持一些必要的功能:layout 布局、partials 部分、models 模型、conditions 条件和 iterations 循环。你可以使用这个而不无需其他依赖。它支持.html
和.sshtml
文件。
@Master['MasterPage']
@Section['Content']
This content from the index page
Partials
Login box below rendered via a partial view with no model.
@Partial['login'];
Box below is rendered via a partial with a sub-model passed in.
The submodel is a list which the partial iterates over with Each
@Partial['user', Model.Users];
Encoding
Model output can also be encoded:
@!Model.NaughtyStuff
@EndSection
除此之外,Nancy 还支持 Razor, Spark, NDjango 和 dotLiquid 引擎。通过添加引用,Nancy 会自动的根据文件后缀名调用对应的引擎。
1. 在路由中渲染视图
Get["/products"] = parameters => {
return View["products.html", someModel];
};
模板说明:
- 视图文件名: “products.html”
- 如果没有后缀,而且有多个同名模板,则会收到
AmbigiousViewsException
错误。 - 一个相对于跟的路径 (比如:
products/products.html
)
更多参见视图位置约定
2. 从模型中解析视图的名称
如果值传递给 View 一个模型,Nancy 会用模型名(去掉”Model” 后缀)作为视图名。
Get["/products"] = parameters => {
return View[new ProductsModel()];
};
如果找不到,就会报 406 Not Acceptable.
十二、超简单视图引擎
SSVE 基于正则,支持sshtml
, html
, html
文件后缀。
模型可以是标准类型,或者ExpandoObjects
(或者实现了IDynamicMetaObjectProvider
实现了IDictionary
的对象)。
所有的命令都可以有分号,但不是必须的。[.Parameters]
这样的参数可以使任意层级的,比如This.Property.That.Property
。
注意:所有引号都是_单引号_.
1. 标准变量替换
如果变量不能替换,则使用[Err!]
替换。
语法:
@Model[.Parameters]
例子:
Hello @Model.Name, your age is @Model.User.Age
2. 循环
循环不能嵌套
语法:
@Each[.Parameters]
[@Current[.Parameters]]
@EndEach
@Each
表示循环;@Current
表示当前变量,使用方法同@Model
。
例子:
@Each.Users
Hello @Current.Name!
@EndEach
3. 条件
参数必须是 bool,或能隐式转化。嵌套的 @If @IfNot 不支持。
语法:
@If[Not].Parameters
[contents]
@EndIf
例子:
@IfNot.HasUsers
No users found!
@EndIf
4. 隐式条件
如果 module 实现了ICollection
,那你就能使用隐式转换。使用Has
前缀。
语法:
Has[CollectionPropertyName]
例子:
@If.HasUsers
Users found!
@EndIf
5. HTML 编码
@Model
和@Current
都可以有一个!
,用来编码 HTML:
语法:
@!Model[.Parameter]
@!Current[.Parameter]
例子:
@!Model.Test
@Each
@!Current.Test
@EndEach
6. 部分 Patials
语法:
@Partial[''[, Model.Property]]
例子:
// Renders the partial view with the same model as the parent
@Partial['subview.sshtml'];
// Renders the partial view using the User as the model
@Partial['subview.sshtml', Model.User];
7. Master 页和 section
可以声明 master 页和节。不必为每个节提供内容。Master 能用@Module
,并且扩展名可以省略。
可以多次使用@Section
语法
@Master['']
@Section['']
@EndSection
例子:
// master.sshtml
@Section['Content'];
// index.sshtml
@Master['master.sshtml']
@Section['Content']
This is content on the index page
@EndSection
8. 防止伪造 token
防止 CSRF
语法:
@AntiForgeryToken
例子:
@AntiForgeryToken
9. 路径扩展
扩展相对路径为整体路径。
语法:
@Path['']
例子:
@Path['~/relative/url/image.png']
10. 扩展 SSVE
十二、Razor 引擎
这个 Razor 引擎跟 ASP.NET MVC 的有点不一样。
注意,Nancy 仍然绑定模型到@Model
,而不是 ASP.NET 中的@model
1. 安装 Razor
只需要添加Nancy.ViewEngines.Razor.dll
(使用 nuget 安装Nancy.ViewEngines.Razor
)。然后试图模板以cshtml
或vbhtml
结尾即可。
2. 配置 Razor
十三、实现自己的视图引擎需要注意的地方
十四、视图位置约定
1. 查看默认约定
视图位置的约定通过Func
方法以及下面的一些默认约定来定义。
1.1 根约定
(viewName, model, viewLocationContext) => {
return viewName;
}
这个约定会在根目录里寻找视图。但是如果视图包含一个相对路径,视图名称执行对应于根路径的路径。比如,视图admin/index
会在admin/index
目下寻找视图。
1.2 视图文件夹约定
(viewName, model, viewLocationContext) => {
return string.Concat("views/", viewName);
}
很简单,视图admin/index
会在views/admin/index
下查找对应的视图。
1.3 视图和模块路径约定
(viewName, model, viewLocationContext) => {
return string.Concat("views/", viewLocationContext.ModulePath, "/", viewName);
}
对于模块 products 的视图admin/index
,会在views/products/admin/index
中查找视图。
1.4 模块路径约定
(viewName, model, viewLocationContext) => {
return string.Concat(viewLocationContext.ModulePath, "/", viewName);
}
这个约定会在与模块名相同的文件夹中查找视图。
1.5 模块名称约定
(viewName, model, viewLocationContext) => {
return string.Concat(viewLocationContext.ModuleName, "/", viewName);
}
查找以模块名为前缀的对应视图。
1.6 视图模块名称约定
(viewName, model, viewLocationContext) => {
return string.Concat("views/", viewLocationContext.ModuleName, "/", viewName);
}
查找 views 文件夹下以模块名为前缀的对应视图。
2. 从模型类型推断是退名
如果没有提供视图名而只提供了视图,那么:
Customer
类型的模型 ->Customer
视图名CustomerModel
类型的模型 ->Customer
视图名
3. 自定义约定
自定义一个 bootstrapper,然后添加约定到Conventions.ViewLocationConventions
集合。
比如:
public class CustomConventionsBootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
{
this.Conventions.ViewLocationConventions.Add((viewName, model, context) =>
{
return string.Concat("custom/", viewName);
});
}
}
比如这个会查找 custom 文件夹下的视图名称。
ViewLocationConventions
是一个标准的列表,可以进行修改。
3. 使用 IConventions 定义自己的约定
你也可以实现IConvention
接口,并在Initialise
方法中添加约定到ViewLocationConventions
属性中。
Nancy 会定位所有接口的实现,并且执行约定,这些发生在他们被传递给 bootstrapper 的ConfigureConventions
方法之前。
十五、本地化
Nancy 内建了本地化。有一系列的约定描述了如何决定当前文化,还有一些根据文化选择视图的约定。
所以,对于de-DE
的文化他会寻找Home-de-DE
的视图。
不仅如此,还会有 rese 文件,比如Text.resx
, Text.de-DE.resx
(可以被重写).
Razor 本地化的例子
十六、测试应用
使用 NuGet 来安装Nancy.Testing
。
测试应当与主应用分开。
为了测试路由,使用 helper 类Browser
。使用 bootstrap 实例化 Browser。
[Fact]
public void Should_return_status_ok_when_route_exists()
{
// Given
var bootstrapper = new DefaultNancyBootstrapper();
var browser = new Browser(bootstrapper);
// When
var result = browser.Get("/", with => {
with.HttpRequest();
});
// Then
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
十七、根路径
Nancy 通过IRootPathProvider
接口的唯一方法GetRootPath
来确定根路径。
1. 改变跟路径
改变根路径需要做两件事:
首先,自定义一个类实现IRootPathProvider
:
public class CustomRootPathProvider : IRootPathProvider
{
public string GetRootPath()
{
return "What ever path you want to use as your application root";
}
}
注意,根路径是绝对路径。
其次,在自定义的 Bootstrapper 中重写RootPathProvider
属性。
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override IRootPathProvider RootPathProvider
{
get { return new CustomRootPathProvider(); }
}
}
2. 上传文件
在 Nancy 中要上传文件,你需要接受上传文件的 content stream, 在磁盘上创建文件,并将 stream 写入到磁盘。
var uploadDirectory = Path.Combine(pathProvider.GetRootPath(), "Content", "uploads");
if (!Directory.Exists(uploadDirectory))
{
Directory.CreateDirectory(uploadDirectory);
}
foreach (var file in Request.Files)
{
var filename = Path.Combine(uploadDirectory, file.Name);
using (FileStream fileStream = new FileStream(filename, FileMode.Create))
{
file.Value.CopyTo(fileStream);
}
}
上例中的pathProvider
是在模块的构造函数中传递进来的,通过它的GetRootPath()
来获取跟路径。
public HomeModule(IRootPathProvider pathProvider)
十八、管理静态内容
简而言之:把东西都放到/Content
文件夹内,仅此而已
十九、诊断
Nancy 自带诊断功能:http://
1. 配置到 dashboard 的访问
添加密码:
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override DiagnosticsConfiguration DiagnosticsConfiguration
{
get { return new DiagnosticsConfiguration { Password = @"A2\6mVtH/XRT\p,B"}; }
}
}
2. 去除诊断
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoc.TinyIoCContainer container, IPipelines pipelines)
{
DiagnosticsHook.Disable(pipelines);
}
}
3. 有哪些工具呢?
Information
, Interactive Diagnostics
, Request Tracing
, Configuration
3.1 信息
3.2 配置
Nancy 中StaticConfiguration
可以用来配置程序的行为,配置页面提供了配置方法。
注意,系统重启后配置页面的内容失效。
要想永久保存配置,请在 bootstrapper 的ApplicationStartup
中设置。
3.3 请求跟踪
请求跟踪因为性能原因默认关闭,可以再Configuration
页开启,也可以这样:
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override void ApplicationStartup(TinyIoC.TinyIoCContainer container, IPipelines pipelines)
{
StaticConfiguration.EnableRequestTracing = true;
}
}
跟踪日志可以通过NancyContext
中得到。和容易添加自己的内容:
public class HomeModule : NancyModule
{
public HomeModule()
{
Get["/"] = parameters => {
this.Context.Trace.TraceLog.WriteLog(s => s.AppendLine("Root path was called"));
return HttpStatusCode.Ok;
};
}
}
WriteLog
方法是用一个接受StringBuilder
的函数是为了调试关闭时直接不调用函数,从而避免性能损耗。
3.4 交互式的诊断
只要实现了IDiagnosticsProvider
接口,Nancy 诊断会自动发现它,并且把它暴露给交互工具。
(1)IDiagnosticsProvider 接口
///
/// Defines the functionality a diagnostics provider.
///
public interface IDiagnosticsProvider
{
///
/// Gets the name of the provider.
///
/// A containing the name of the provider.
string Name { get; }
///
/// Gets the description of the provider.
///
/// A containing the description of the provider.
string Description { get; }
///
/// Gets the object that contains the interactive diagnostics methods.
///
/// An instance of the interactive diagnostics object.
object DiagnosticObject { get; }
}
(2)可诊断的对象
任何公共方法都会暴露给交互诊断面板。方法可以是能被 JSON 序列化的任意类型。类型的返回值会被返回成 JSON Report Format
(3)提供描述给方法
两种方法:
1、使用 attribute: Nancy.Diagnostics.DescriptionAttribute
2、使用 property:使用与方法同名但添加了Description
后缀的属性,比如NameOfYourMethodDescription
描述了NameOfYourMethod
方法。
(4)自定义模板输出
(5)创建诊断提供者
二十、添加自己的 favicon
1. 替换默认的 FavIcon
在应用中防止一个_favicon_的文件,名称以.icon
或.png
结尾即可。
2. 使用内嵌 icon
在 Bootstrapper 中重写FavIcon
属性:
public class Bootstrapper : DefaultNancyBootstrapper
{
private byte[] favicon;
protected override byte[] FavIcon
{
get { return this.favicon?? (this.favicon= LoadFavIcon()); }
}
private byte[] LoadFavIcon()
{
//TODO: remember to replace 'AssemblyName' with the prefix of the resource
using (var resourceStream = GetType().Assembly.GetManifestResourceStream("AssemblyName.favicon.ico"))
{
var tempFavicon = new byte[resourceStream.Length];
resourceStream.Read(tempFavicon, 0, (int)resourceStream.Length);
return tempFavicon;
}
}
}
3. 移除 ICON
设置 Bootstrapper 的FavIcon
属性为null
。
二十一、添加自定义的错误页面
第一篇:http://mike-ward.net/blog/post/00824/custom-error-pages-in-nancyfx
第二篇:https://blog.tommyparnell.com/custom-error-pages-in-nancy/
二十二、加密帮助方法
命名空间:Nancy.Cryptography
1. IEncryptionProvider 接口
///
/// Provides symmetrical encryption support
///
public interface IEncryptionProvider
{
///
/// Encrypt and base64 encode the string
///
/// Data to encrypt
/// Encrypted string
string Encrypt(string data);
///
/// Decrypt string
///
/// Data to decrypt
/// Decrypted string
string Decrypt(string data);
}
Nancy 提供了两个默认实现
NoEncryptionProvider
: 没有加密,仅仅是 base64RijndaelEncryptionProvider
: 使用 Rijndael 算法,使用 256 位的 key 和 128 为的初始向量,加密 base64 字符串。
2. IHmacProvider 接口
用来签名,防止篡改。
///
/// Creates Hash-based Message Authentication Codes (HMACs)
///
public interface IHmacProvider
{
///
/// Gets the length of the HMAC signature in bytes
///
int HmacLength { get; }
///
/// Create a hmac from the given data
///
/// Data to create hmac from
/// Hmac bytes
byte[] GenerateHmac(string data);
///
/// Create a hmac from the given data
///
/// Data to create hmac from
/// Hmac bytes
byte[] GenerateHmac(byte[] data);
}
Nancy 也提供了一个默认实现:DefaultHmacProvider
,使用IKeyGenerator
来产生一个 key 来用 SHA-256 来进行 hash。
3. IKeyGenerator 接口
用来产生 key 来加密和数字签名。
///
/// Provides key byte generation
///
public interface IKeyGenerator
{
///
/// Generate a sequence of bytes
///
/// Number of bytes to return
/// Array bytes
byte[] GetBytes(int count);
}
Nancy 提供了两个默认实现。
-
RandomKeyGenerator
使用RNGCryptoServiceProvider
产生了一个随机定长的 key -
PassphraseKeyGenerator
使用密码、静态盐以及可选循环数字,以及Rfc2898DeriveBytes
来产生一个 key
注意,如果使用PassphraseKeyGenerator
,它的初始化应当在应用启动时使用,因为它太慢了。这意味着盐是静态的,因此密码一定要足够长和复杂。
4. 加密配置类型 CryptographyConfiguration
这是一个存储IEncryptionProvider
和IHmacProvider
的简便方法。它有两个静态属性:
Default
使用RijndaelEncryptionProvider
和DefaultHmacProvider
,两个都使用RandomKeyGenerator
。NoEncryption
使用NoEncryption
和DefaultHmacProvider
,两个也都使用RandomKeyGenerator
.
可以单独使用CryptographyConfiguration
,也可以在 bootstrapper 中配置一个:
///
/// Gets the cryptography configuration
///
protected virtual CryptographyConfiguration CryptographyConfiguration
{
get { return CryptographyConfiguration.Default; }
}
二十三、Content negotiation(内容协商)
当返回不是Response
类型时,使用 response processor 来根据请求的Accept
来处理。
1. Response Processor
public interface IResponseProcessor
{
///
/// Gets a set of mappings that map a given extension (such as .json)
/// to a media range that can be sent to the client in a vary header.
///
IEnumerable> ExtensionMappings { get; }
///
/// Determines whether the the processor can handle a given content type and model.
///
ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context);
///
/// Process the response.
///
Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context);
}
Response Processor 是自发现的,也可以在 Bootstrap 中配置。
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
var processors = new[]
{
typeof(SomeProcessor),
typeof(AnotherProcessor)
};
return NancyInternalConfiguration.WithOverrides(x => x.ResponseProcessors = processors);
}
}
}
1.1 匹配优先级
当相应准备转化请求媒体的格式时,Nancy 会查询所有的 processor 的CanProcess
方法,并且会聚合ProcessorMatch
的返回值。
ProcessorMatch
类型确保每个 processor 让 Nancy 知道它们对媒体类型的支持程度。
public class ProcessorMatch
{
///
/// Gets or sets the match result based on the content type
///
public MatchResult RequestedContentTypeResult { get; set; }
///
/// Gets or sets the match result based on the model
///
public MatchResult ModelResult { get; set; }
}
MatchResult
枚举了匹配程度:
public enum MatchResult
{
///
/// No match, nothing to see here, move along
///
NoMatch,
///
/// Will accept anything
///
DontCare,
///
/// Matched, but in a non-specific way such as a wildcard match or fallback
///
NonExactMatch,
///
/// Exact specific match
///
ExactMatch
}
所有的ProcessorMatch
会按照 Match 程度降序排列,最匹配的被执行。如果有两个匹配程度相同,Nancy 会选择其中一个。
1.2 默认响应处理器
Nancy 提供了一些默认响应处理器
JsonProcessor
- 当请求类型为application/json
或者application/vnd.foobar+json
时,转化返回值为 json;ViewProcessor
- 当请求类型为text/html
时,使用返回值作为 model,返回视图。视图使用视图位置约定;XmlProcessor
- 当请求为application/xml
或者为application/vnd.foobar+xml
时,返回 xml。
2. 控制协商
Nancy.Responses.Negotiation
命名空间中的Negotiator
用来控制协商。Negotiator
有一个属性:NegotiationContext
. NegotiationContext
可以用来控制响应的协商。
但是一般不会直接使用Negotiator
和NegotiationContext
,因为NancyModule
包含了一个帮助方法Negotiate
,用来更好的创造Negotiator
实例。
在路由中使用Negotiator
的例子:
Get["/"] = parameters => {
return Negotiate
.WithModel(new RatPack {FirstName = "Nancy "})
.WithMediaRangeModel("text/html", new RatPack {FirstName = "Nancy fancy pants"})
.WithView("negotiatedview")
.WithHeader("X-Custom", "SomeValue");
};
Negotiator
包含了用来配置返回Negotiator
实例的一些方法。
WithHeader
- 添加一个 Http 头;WithHeaders
- 添加一个 Http 的头集合;WithView
- 使用视图;WithModel
- 使用模型;WithMediaRangeModel
- 使用特定的媒体类型和模型,如果失败了,就使用WithModel
指定的模型;WithFullNegotiation
- 设置允许媒体类型为*/*
的帮助方法;WithAllowedMediaRange
- 指定允许的媒体范围。默认是”/“, 但是一旦指定一个特定的内容类型,通配符就会被移走。WithStatusCode
- 状态码
3. 支持文件扩展名
Nancy 支持基于扩展名来设置协商的处理,此时传递正常的可接受的头。
例子:
Get["/ratpack"] = parameters => {
return new RatPack {FirstName = "Nancy "});
};
它既可以通过/ratpack
和设置的application/json
头来调用,也可以使用/ratpack.json
并且不设置application/json
来调用,两个结果一样。
内部 Nancy 是通过检测扩展名,并查询可用的响应处理器的ExtensionMappings
属性来查看是否有支持的扩展。如果有,就调用并且设置对应的头信息,但是如果有更优先的处理器,则用更优先的处理器,除非更优先的处理器失败了,才会使用扩展。
约定的格式:
Func<
IEnumerable>,
NancyContext,
IEnumerable>>
这个函数接受NancyContext
和当前头,并且期望你返回修改后的可接受头列表。
默认情况下,Nancy 在Nancy.Conventions.BuiltInAcceptHeaderCoercions class
中提供了如下约定,其中加 * 的表示是默认默认被转换的:
BoostHtml
(*) - 如果 text/html 的优先级低于其他内容类型,则提高优先级;CoerceBlankAcceptHeader
(*) - 如果没有指定请求头,就分配一个默认的;CoerceStupidBrowsers
- 对于老浏览器,替换请求头,即使它们说是请求 xml 还是返回 html。
更改哪一个强制起作用时在 bootstrapper 中的ConfigureConventions
来设置的:
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureConventions(NancyConventions nancyConventions)
{
base.ConfigureConventions(nancyConventions);
this.Conventions.AcceptHeaderCoercionConventions.Add((acceptHeaders, ctx) => {
// Modify the acceptHeaders by adding, removing or updating the current
// values.
return acceptHeaders;
});
}
}
当然你也可以继承你自己的 bootstrapper。
5. 使用 IConventions 来定义自己的约定
可以通过实现IConventions
接口来创造一个类,并在它的Initialise
方法中添加自己的约定到传递进来的参数的AcceptHeaderCoercionConventions
属性中。
在所有的接口被传递给 bootstrapper 的ConfigureConventions
的方法之前,Nancy 会定位所有的接口实现,并且激发这些约定。
6. 自动协商头
Nancy 会自动添加链接和各种各样的头到协商响应中。链接头链接。连接头会连接到根据文件扩展来的其他代表中。
7. 更多信息
- Nancy and Content Negotiation
- Revisting Content Negotiation and APIs part 1
- Revisting Content Negotiation and APIs part 2
- Revisting Content Negotiation and APIs part 3
二十四、使用转换器来扩展序列化
二十五、授权
Nancy 中的验证使用扩展点:比如应用管道、模块管道、NancyContext
和其他的一些扩展方法。所以你可以写自己的验证来替换默认提供的验证。
Nancy 提供了以下几种验证,通过 Nuget 安装:
- 表单 (
Nancy.Authentication.Forms
) - 基本 (
Nancy.Authentication.Basic
) - 无状态 (
Nancy.Authentication.Stateless
)
1. 了解用户
Nancy 中用户使用IUserIdentity
接口代表,它提供了一些用户的基本信息:
public interface IUserIdentity
{
///
/// Gets or sets the name of the current user.
///
string UserName { get; set; }
///
/// Gets or set the claims of the current user.
///
IEnumerable Claims { get; set; }
}
你应当提供基于自己应用需求的类来实现自己的用户接口。
要获得当前用户,只需要获取NancyContext
的CurrentUser
属性。返回null
值表明当前请求未认证,其他的则表示已认证。
context 在 Nancy 的大部分地方都能获取,所以不必担心能否获取当前请求的用户身份。
2. 保护你的资源
可以在模块级和应用级来保护资源,方法是检测NancyContext.CurrentUser
属性不为 null。
这个任务可以通过在模块管道的Before
中实现。这个钩子允许我们终结当前请求的执行,返回其它资源,比如当未验证用户视图访问安全资源时:
public class SecureModule : NancyModule
{
public SecureModule()
{
Before += ctx => {
return (this.Context.CurrentUser == null) ? new HtmlResponse(HttpStatusCode.Unauthorized) : null;
};
// Your routes here
}
}
在每个模块上添加安全代码违反了 DRY 原则,更是一个无聊的任务。使用扩展方法!
Nancy 有一些扩展方法包装了这些任务,彻底的减少了要写的代码量。
下面是一些可用的扩展方法:
RequiresAuthentication
- 确保验证用户是可用的,或者返回HttpStatusCode.Unauthorized
. 对于认证的用户,CurrentUser
不能为null
,而且UserName
不能为空;RequiresClaims
- 用户必须满足声明列表中所有的条件才能获取资源;RequiresAnyClaim
- 见上一条,但是只需满足任意一条;RequiresValidatedClaims
- 通过自定义函数,来全部自我掌控验证流程,函数格式Func
;, bool> RequiresHttps
- 只允许 https 访问;
这些都是NancyModule
类的扩展方法,要使用它们需要添加Nancy.Security
命名空间。
使用扩展方法,前面的例子可以这样写:
public class SecureModule : NancyModule
{
public SecureModule()
{
this.RequiresAuthentication();
}
// Your routes here
}
当然还可以这样写:
public class SecureModule : NancyModule
{
public SecureModule()
{
this.RequiresHttps();
this.RequiresAuthentication();
this.RequiresClaims(new [] { "Admin" });
}
// Your routes here
}
用户必须通过 https,被授权,而且拥有 Admin claim 才能访问上面的路由。
3. 创造你自己的安全扩展
为了创造自己的安全扩展,你只需要添加扩展方法到NancyModule
,并且绑定到Before
管道,并检查证书。
比如,下面说明了RequiresAuthentication
如何工作的:
public static class ModuleSecurity
{
public static void RequiresAuthentication(this NancyModule module)
{
module.Before.AddItemToEndOfPipeline(RequiresAuthentication);
}
private static Response RequiresAuthentication(NancyContext context)
{
Response response = null;
if ((context.CurrentUser == null) ||
String.IsNullOrWhiteSpace(context.CurrentUser.UserName))
{
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
}
return response;
}
}
4. 实现自己的验证 provider
实际的验证 provider 实现根据不同的需求变化很大,但是基本模式如下:
- 应用管道的
Before
钩子用来检查请求的证书(比如 cookie, headers 等等)。如果发现证书,则验证用户并授权给NancyContext
的CurrentUser
属性。 - 模块管道的
Before
钩子用来确认当前的请求是被认证的用户执行,如果不是,则拒绝并返回HttpStatusCode.Unauthorized
- 应用管道的
After
钩子用来检查请求是否因为认证失败而被丢弃,比如检查HttpStatusCode.Unauthorized
(401) 状态码。如果检测到了就帮助用户去认证,比如重定向到 login 表单或者使用 header 的帮助通知客户端。
5. 无状态认证
无状态认证就是在每个请求中进行检查,根据请求的一些信息,来决定是否应该被确认为一个已认证的请求。
比如你检查请求来确认查询字符串的参数是否传递了 api key,或者是否包含某些 head, 有或者请求是否来自某些特定的 ip。
使用无状态认证需要做下面几件事:
- 安装
Nancy.Authentication.Stateless
包 - 配置并开启无状态认证
- 保护资源
5.1 配置并开启无状态认证
在 bootstrapper 中添加:
StatelessAuthentication.Enable(pipelines, statelessAuthConfiguration);
被传递到StatelessAuthentication.Enable
方法中的statelessAuthConfiguration
变量,是一个StatelessAuthenticationConfiguration
类型的实例,它能够让你自定义无状态认证提供者的行为。
定义StatelessAuthenticationConfiguration
类型实例的时候,需要有一个Func
类型的参数。这个函数用来检查请求或者 context 中的其他相关内容,并且在请求未通过验证时返回null
,否则返回合适的 IUserIdentity
.
5.2 简单配置
var configuration =
new StatelessAuthenticationConfiguration(ctx =>
{
if (!ctx.Request.Query.apikey.HasValue)
{
return null;
}
// This would where you authenticated the request. IUserApiMapper is
// not a Nancy type.
var userValidator =
container.Resolve();
return userValidator.GetUserFromAccessToken(ctx.Request.Query.apikey);
});
6. Form 认证
详细例子见 Nancy 解决方案中Nancy.Demo.Authentication.Forms
例子
为了开启 form 认证,需要完成:
- 安装
Nancy.Authentication.Forms
包 - 实现
IUserMapper
- 实现路由来处理 login 和 logout
- 配置并开启 Form 认证
6.1 User mapper
User mapper 用来负责从标示符 identifier 映射到用户。标示符是一个令牌,被存储在认证 cookie 中,用来代表执行请求的用户身份,避免每次请求时输入证书。
使用 GUID 来做标示符,如果用 username 来做标示符容易被嗅探并攻击。GUID 还很难读取,而且每个 GUID 都不一样,增加了嗅探的难度。
注意,需要知道标示符对每个用户来说都是永久的并且是唯一的。
IUserMapper
接口的定义:
public interface IUserMapper
{
///
/// Get the real username from an identifier
///
/// User identifier
/// The current NancyFx context
/// Matching populated IUserIdentity object, or empty
IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context);
}
6.2 修改应用,处理 form 认证
有了IUserMapper
后,下一步就是在不需要认证的地方添加 login 和 logout 了。
下面是一个模块的基础框架。请注意资源的路径和模块的名称可以使任意的:
public class LoginModule : NancyModule
{
public LoginModule()
{
Get["/login"] = parameters => {
// Called when the user visits the login page or is redirected here because
// an attempt was made to access a restricted resource. It should return
// the view that contains the login form
};
Get["/logout"] = parameters => {
// Called when the user clicks the sign out button in the application. Should
// perform one of the Logout actions (see below)
};
Post["/login"] = parameters => {
// Called when the user submits the contents of the login form. Should
// validate the user based on the posted form data, and perform one of the
// Login actions (see below)
};
}
}
Nancy.Authentication.Forms
命名空间中有一些扩展方法可供使用:
LoginAndRedirect
- 登录用户并重定向用户到他们来时的 url。或者也可以提供一个预留的 url,用来在没有重定向 url 时使用。如果使用 form 提交,注意使用 action=””,因为它会保留 returnUrl 原封不动。LoginWithoutRedirect
- 登录用户,并且返回响应和状态码 200(ok)Login
会调用当前请求的IsAjaxRequest
的扩展方法,并且如果不是 Ajax 调用,则执行LoginAndRedirect
方法,否则执行LoginWithoutRedirect
方法LogoutAndRedirect
- 登出用户,并提供重定向LogoutWithoutRedirect
- 登出用户并返回状态码为 200(OK) 的响应Logout
会调用当前请求的IsAjaxRequest
方法,如果不是 ajax 请求,则执行LogoutAndRedirect
,否则执行LogoutWithoutRedirect
注意 1: Nancy.Extensions.RequestExtensions
中的IsAjaxRequest
扩展方法会检查X-Requested-With
头,并且在其包含值XMLHttpRequest
时返回 true
注意 2: 请确认路径的定义 login 和 logout 的页面没有要求使用登录。
6.3 启用 form 认证
在 bootstrapper 中添加:
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
既可以在ApplicationStartup
中又可以在RequestStartup
中添加。到底在何处加,取决于IUserMapper
,即 user mapper 到底是有应用级的生命周期还是请求级的生命周期。
传递给FormsAuthentication.Enable
方法的formsAuthConfiguration
变量是FormsAuthenticationConfiguration
类型,它能让你自定义 form 认证提供者的行为。
比如,下面是一个基本的认证配置:
var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
RedirectUrl = "~/login",
UserMapper = container.Resolve(),
};
下面是一些配置项:
RedirectingQuerystringKey
:默认名是returnUrl
RedirectingUrl
:未认证的用户应当被重定向的 url,一般是登录页面~/login
UserMapper
:IUserMapper
在认证时应该被使用RequiresSSL
: SSLDisableRedirect
: 遇到未认证时,是否重定向到登陆页CryptographyConfiguration
:CryptographyConfiguration.Default
与 form 认证 cookie 配合使用。CryptographyConfiguration.Default
是默认的。
6.4 关于加密,还有一些话
默认使用RandomKeyGenerator
,这意味着每次程序启动时会产生一个新的秘钥,那么应用重启回到这认证 cookie 失效,在多台机器负载均衡时也会出现这种问题,别怕,看看加密配置
下面是一个例子:
var cryptographyConfiguration = new CryptographyConfiguration(
new RijndaelEncryptionProvider(new PassphraseKeyGenerator("SuperSecretPass", new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })),
new DefaultHmacProvider(new PassphraseKeyGenerator("UberSuperSecure", new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })));
var config =
new FormsAuthenticationConfiguration()
{
CryptographyConfiguration = cryptographyConfiguration,
RedirectUrl = "/login",
UserMapper = container.Resolve(),
};
6.5 更多
-
Forms authentication with nancyfx
-
Multiple forms authentication sections
7. 令牌认证
详细例子在 Nancy 解决方案中的Nancy.Demo.Authentication.Token
中。
7.1 认识 Nancy 的令牌认证
Nancy 令牌认证工程是为了多种客户端 (iOS, Android, Angular SPA 等等) 能与统一后台 Nancy 应用而创建的。
7.2 基本原理
令牌认证与授权在下面这些需求下应运而生:
- 没有 cookie(不适所有的客户端都是浏览器)
- 避免一旦用户被认证 / 授权后,从后端数据存储中取回用户和权限信息
- 允许客户端应用在第一次授权后保存令牌,以便为后续请求使用
- 通过单向加密算法确保令牌没有被篡改,阻止嗅探冒充令牌攻击
- 使用有期限的可配置的 key 来进行令牌生成
- 使用 server 端的文件系统来存储私钥,这样即使应用重启也能恢复。注意:可以使用内存存储作为测试。
7.3 使用
7.3.1 Nancy 配置
令牌认证可以像 form 认证那样:
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
TokenAuthentication.Enable(pipelines, new TokenAuthenticationConfiguration(container.Resolve()));
}
}
令牌从IUserIdentity
和NancyContext
中,通过实现ITokenizer
接口产生。默认实现是Tokenizer
,它提供了一些可配置的方法。默认情况下,它产生一个令牌包含下面部分:
- 用户名
- Pipe separated list of user claims
- UTC 当前时间
- 客户端的”User-Agent” 头(必须)
建议配置 Tokenizer,使用其他附加能代表用户唯一设备的信息。
下面举例说明了如何初始化用户认证,并且返回生成的令牌给客户端:
public class AuthModule : NancyModule
{
public AuthModule(ITokenizer tokenizer)
: base("/auth")
{
Post["/"] = x =>
{
var userName = (string)this.Request.Form.UserName;
var password = (string)this.Request.Form.Password;
var userIdentity = UserDatabase.ValidateUser(userName, password);
if (userIdentity == null)
{
return HttpStatusCode.Unauthorized;
}
var token = tokenizer.Tokenize(userIdentity, Context);
return new
{
Token = token,
};
};
Get["/validation"] = _ =>
{
this.RequiresAuthentication();
return "Yay! You are authenticated!";
};
Get["/admin"] = _ =>
{
this.RequiresClaims(new[] { "admin" });
return "Yay! You are authorized!";
};
}
}
7.3.2 客户端配置
一旦你的客户端接收到了 token,那么你必须使用 token 来设置 HTTP 头:
Authorization: Token {your-token-goes-here}
8. 幕后的工作
https://github.com/NancyFx/Nancy/commit/9ae0a5494bc335c3d940d730ae5d5f18c1018836
原文地址 liulixiang1988.github.io