在 mono 下使用微软的 OWIN 认证中间件
在 mono 下使用微软的 OWIN 认证中间件
使用 Microsoft.Owin.Security 中间件作为 OWIN 应用的标准验证在 IIS 下面工作良好, 不过最近在将 WebAPI 应用迁移到 Linux + Mono 的环境时, 发现这个中间件不能运行, 在启动时会抛出下面的异常:
这个异常是说无法加载类型 Microsoft.Owin.Security.DataProtection.DpapiDataProtector
, 通过 ILSpy 分析 Microsoft.Owin.Security.dll 发现, Microsoft.Owin.Security.DataProtection.DpapiDataProtector
使用 System.Security.Cryptography.DpapiDataProtector
实现, 而 System.Security.Cryptography.DpapiDataProtector
使用了 win32 函数实现, 因此,不能直接在非 windows 环境下运行。
不过, Microsoft.Owin.Security 中预留了扩展接口 IDataProtectionProvider
, 可以实现自定义的 IDataProtector
, Mono 内置了 AesManaged
类, 可以用来实现自定义的 IDataProtector
, 示例代码如下:
public class AesDataProtector : IDataProtector {
private readonly byte[] key;
public AesDataProtector(string key) {
using (var sha1 = new SHA256Managed()) {
this.key = sha1.ComputeHash(Encoding.UTF8.GetBytes(key));
}
}
public byte[] Protect(byte[] userData) {
byte[] dataHash;
using (var sha = new SHA256Managed()) {
dataHash = sha.ComputeHash(userData);
}
using (AesManaged aesAlg = new AesManaged()) {
aesAlg.Key = key;
aesAlg.GenerateIV();
using (var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
using (var msEncrypt = new MemoryStream()) {
msEncrypt.Write(aesAlg.IV, 0, 16);
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (var bwEncrypt = new BinaryWriter(csEncrypt)) {
bwEncrypt.Write(dataHash);
bwEncrypt.Write(userData.Length);
bwEncrypt.Write(userData);
}
var protectedData = msEncrypt.ToArray();
return protectedData;
}
}
}
public byte[] Unprotect(byte[] protectedData) {
using (AesManaged aesAlg = new AesManaged()) {
aesAlg.Key = key;
using (var msDecrypt = new MemoryStream(protectedData)) {
byte[] iv = new byte[16];
msDecrypt.Read(iv, 0, 16);
aesAlg.IV = iv;
using (var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var brDecrypt = new BinaryReader(csDecrypt)) {
var signature = brDecrypt.ReadBytes(32);
var len = brDecrypt.ReadInt32();
var data = brDecrypt.ReadBytes(len);
byte[] dataHash;
using (var sha = new SHA256Managed()) {
dataHash = sha.ComputeHash(data);
}
if (!dataHash.SequenceEqual(signature)) {
throw new SecurityException("Signature does not match the computed hash");
}
return data;
}
}
}
}
}
再来实现一个 IDataProtectionProvider
, 提供 AesDataProtector
实例, 代码如下:
public class AesDataProtectionProvider : IDataProtectionProvider {
private string appName;
public AesDataProtectionProvider() : this(Guid.NewGuid().ToString()) {
}
public AesDataProtectionProvider(string appName) {
if (appName == null) {
throw new ArgumentNullException("appName");
}
this.appName = appName;
}
public IDataProtector Create(params string[] purposes) {
return new AesDataProtector(appName + ":" + string.Join(",", purposes));
}
}
为了方便使用, 对 Owin.IAppBuilder
做一个扩展方法 UseAesDataProtectionProvider
, 代码如下:
public static void UseAesDataProtectionProvider(this IAppBuilder app) {
const string hostAppNameKey = "host.AppName";
if (app.Properties.ContainsKey(hostAppNameKey)) {
var appName = app.Properties[hostAppNameKey].ToString();
app.SetDataProtectionProvider(new AesDataProtectionProvider(appName));
}
else {
app.SetDataProtectionProvider(new AesDataProtectionProvider());
}
}
有了上面的扩展方法, 使用自己实现的 AesDataProtectionProvider
就非常简单了, 只要在 UseCookieAuthentication
之前加上一句 UseAesDataProtectionProvider
即可, 下面是示例代码:
void Configure(IAppBuilder app) {
// handle static file
app.UseStaticFile(new StaticFileMiddlewareOptions {
RootDirectory = @"../Website",
DefaultFile = "index.html",
MimeTypeProvider = new MimeTypeProvider(),
EnableETag = true,
ETagProvider = new LastWriteTimeETagProvider()
});
// use aes data protection provider;
app.UseAesDataProtectionProvider();
// cookie auth;
app.UseCookieAuthentication(new CookieAuthenticationOptions{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
// web-api
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
本文的全部源代码已经上传到至 github , 也做了一个 nuget 包方便大家使用。
- 算法模板——计算几何1(图形面积)
- 算法模板——线段树6(二维线段树:区域加法+区域求和)(求助phile)
- 【LeetCode 242】 关关的刷题日记36 Valid Anagram
- javascript闭包
- 【LeetCode 438】关关的刷题日记37 Find All Anagrams in a String
- 还在手动给css加前缀?no!几种自动处理css前缀的方法简介
- 算法模板——线段树5(区间开根+区间求和)
- Spring基础篇——通过Java注解和XML配置装配bean
- Java多线程高并发学习笔记(二)——深入理解ReentrantLock与Condition
- 算法模板——线段树1(区间加法+区间求和)
- 【LeetCode 205】关关的刷题日记38 Isomorphic Strings
- JavaScript基础2---控制权DOM操作
- 算法模板——线段树3(区间覆盖值+区间求和)
- 算法模板——线段树4(区间加+区间乘+区间覆盖值+区间求和)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 读源码——cglib动态代理
- 每日一题 | 字符串转换问题
- Git同时上传到github和码云(国内知名的男性交友网站)
- 读源码——Guava-Cache
- LeetCode 94 | 构造出所有二叉搜索树
- 每日一题 | 最大考试分数问题
- 计算广告——收入分解
- 浅谈mybatis中的占位符
- Python | 详解Python中的协程,为什么说它的底层是生成器?
- 初识Mybatis中的动态sql
- Raw use of parameterized class 'Future'
- javaweb遇到的报错问题以及解决方案(持续更新)
- Spark Java UDAF 输入struct嵌套结构
- 深入理解Java内存模型
- Mybatis高级查询(一):resultMap与resultType