网页抓取解密(ASP.NETCore过渡阶段缓存的问题后.net)

优采云 发布时间: 2021-10-09 20:02

  网页抓取解密(ASP.NETCore过渡阶段缓存的问题后.net)

  在解决了core中访问memcached缓存的问题后,我们开始向.net core迈进——将更多站点迁移到core。在迁移涉及获取用户登录信息的站点时,我们遇到了一个问题——如何在core和传统之间共享保存用户登录信息的cookie?

  对于cookies的加解密,传统上采用对称加解密算法,而core采用基于公钥和私钥的非对称加解密算法。因此,core 无法解密传统生成的 cookie,也无法解密传统的 core 生成的 cookie。针对这个问题,.net社区有人提供了解决方案——将传统的加解密算法改为core(详见这里),但这需要修改传统站点的所有涉及获取用户登录信息的代码. 有些奢侈品不是万不得已,我们不想用它,我们必须另辟蹊径。

  我们先把问题简化一下。根据我们迁移到 ASP.NET Core 过渡阶段的实际场景,用户登录操作是在传统站点上完成的。我们只需要解密核心站点上的cookie即可获取用户登录信息,无需加密。需要。由于core自身无法解密,可以让传统解密帮忙,core会通过web api将接收到的cookie发送给传统解密。简化后,问题变成-如何在核心接收传统cookie?如何拦截对cookies的核心解密操作?如何从 web api 中的 cookie 解密用户身份验证信息(FormsAuthenticationTicket)?在core中如何将FormsAuthenticationTicket转换成自己的认证信息(AuthenticationTicket)?让我们一一解决这些问题。

  问题一:如何在core中接收传统的cookies?

  这个问题很容易解决。只需将 Startup.cs 中 CookieAuthenticationOptions 的 CookieName 和 CookieDomain 设置为与传统相同即可。

  var cookieOptions = new CookieAuthenticationOptions

{

CookieName = ".CnblogsCookie",

CookieDomain = ".cnblogs.com",

};

app.UseCookieAuthentication(cookieOptions);

  问题二:如何拦截对cookies的核心解密操作?

  这个问题很棘手。我们通过阅读Microsoft.AspNetCore.Authentication.Cookies的源代码,在CookieAuthenticationHandler.cs中找到了TicketDataFormat:

  private async Task ReadCookieTicket()

{

var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);

//...

var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());

//...

return AuthenticateResult.Success(ticket);

}

  TicketDataFormat 的类型为 ISecureDataFormat 接口,解密 cookie 是调用该接口的 Unprotect 方法:

  public interface ISecureDataFormat

{

string Protect(TData data);

string Protect(TData data, string purpose);

TData Unprotect(string protectedText);

TData Unprotect(string protectedText, string purpose);

}

  而TicketDataFormat是CookieAuthenticationOptions的一个属性,我们可以直接修改这个属性值,使用我们自己的ISecureDataFormat接口实现(默认实现是SecureDataFormat),在Unprotect()方法的实现中读取protectedText参数值(这个应该是接收到的cookie 值)来达到拦截的目的,我们来试试。

  定义一个 FormsAuthTicketDataFormat 类来实现 ISecureDataFormat 接口:

  public class FormsAuthTicketDataFormat : ISecureDataFormat

{

//...

public AuthenticationTicket Unprotect(string protectedText, string purpose)

{

Console.WriteLine($"{nameof(Unprotect)}(\"{protectedText}\", \"{purpose}\")");

throw new NotImplementedException();

}

}

  在 Startup.cs 中应用 FormsAuthTicketDataFormat:

  var cookieOptions = new CookieAuthenticationOptions

{

//...

TicketDataFormat = new FormsAuthTicketDataFormat()

};

app.UseCookieAuthentication(cookieOptions);

  经过测试验证,接收到的cookie值确实是传统生成的cookie值。

  现在可以在 Unprotect() 方法中读取 cookie 值,然后可以通过 web api 将其发送到传统解密,因此我们继续下一个问题。

  问题三:如何从web api中的cookie中解密用户认证信息(FormsAuthenticationTicket)?

  这个问题也很容易解决,只要调用FormsAuthentication.Decrypt()方法解密,将FormsAuthenticationTicket的Name、IssueDate、Expiration值返回给core即可。

  public IHttpActionResult GetTicket(string cookie)

{

var formsAuthTicket = FormsAuthentication.Decrypt(cookie);

return Ok(new

{

formsAuthTicket.Name,

formsAuthTicket.IssueDate,

formsAuthTicket.Expiration

});

}

  核心通过调用web api对cookie进行解密,得到Name、IssueDate、Expiration三个值,所以FormsAuthTicketDataFormat.Unprotect()的实现代码变成了这样:

  public AuthenticationTicket Unprotect(string protectedText, string purpose)

{

var formsAuthTicket = GetFormsAuthTicket(protectedText);

var name = formsAuthTicket.Name;

DateTime issueDate = formsAuthTicket.IssueDate;

DateTime expiration = formsAuthTicket.Expiration;

throw new NotImplementedException();

}

  接下来,解决最后一个问题。

  问题4:在core中如何将FormsAuthenticationTicket转换成自己的认证信息(AuthenticationTicket)?

  由于Unprotect()方法的返回参数的类型是AuthenticationTicket,我们就不用换地方继续折腾这个方法了。现在我们有了 FormsAuthenticationTicket 的 Name、IssueDate 和 Expiration 三个值,我们需要根据它们创建一个有效的 AuthenticationTicket。

  AuthenticationTicket 的构造函数有 3 个参数。第一个参数的类型是ClaimsPrincipal,与用户名相关联;第二个参数的类型是AuthenticationProperties,里面存放了cookie的生成时间和过期时间,第三个参数authenticationScheme设置为对应的值(这里设置为空字符串),代码如下:

  public AuthenticationTicket Unprotect(string protectedText, string purpose)

{

//Get FormsAuthenticationTicket from asp.net web api

var formsAuthTicket = GetFormsAuthTicket(protectedText);

var name = formsAuthTicket.Name;

DateTime issueDate = formsAuthTicket.IssueDate;

DateTime expiration = formsAuthTicket.Expiration;

//Create AuthenticationTicket

var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic");

var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(claimsIdentity);

var authProperties = new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties

{

IssuedUtc = issueDate,

ExpiresUtc = expiration

};

var ticket = new AuthenticationTicket(claimsPrincipal, authProperties, _authenticationScheme);

return ticket;

}

  解决这4个问题后,你就大功告成了!在核心mvc控制器中可以显示当前登录用户名,如以下代码:

  public IActionResult Index()

{

return Content(User.Identity.Name);

}

  完整的相关实现代码如下:

  启动文件

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

{

var cookieOptions = new CookieAuthenticationOptions

{

AutomaticAuthenticate = true,

AutomaticChallenge = true,

CookieHttpOnly = true,

CookieName = ".CnblogsCookie",

CookieDomain = ".cnblogs.com",

LoginPath = "/account/signin",

TicketDataFormat = new FormsAuthTicketDataFormat("")

};

app.UseCookieAuthentication(cookieOptions);

//...

}

  FormAuthTicketDataFormat.cs

  public class FormsAuthTicketDataFormat : ISecureDataFormat

{

private string _authenticationScheme;

public FormsAuthTicketDataFormat(string authenticationScheme)

{

_authenticationScheme = authenticationScheme;

}

public AuthenticationTicket Unprotect(string protectedText, string purpose)

{

//Get FormsAuthenticationTicket from asp.net web api

var formsAuthTicket = GetFormsAuthTicket(protectedText);

var name = formsAuthTicket.Name;

DateTime issueDate = formsAuthTicket.IssueDate;

DateTime expiration = formsAuthTicket.Expiration;

//Create AuthenticationTicket

var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic");

var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(claimsIdentity);

var authProperties = new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties

{

IssuedUtc = issueDate,

ExpiresUtc = expiration

};

var ticket = new AuthenticationTicket(claimsPrincipal, authProperties, _authenticationScheme);

return ticket;

}

public string Protect(AuthenticationTicket data)

{

throw new NotImplementedException();

}

public string Protect(AuthenticationTicket data, string purpose)

{

throw new NotImplementedException();

}

public AuthenticationTicket Unprotect(string protectedText)

{

throw new NotImplementedException();

}

private FormsAuthTicketDto GetFormsAuthTicket(string cookie)

{

return new UserService().DecryptCookie(cookie).Result;

}

}

  遗留问题:目前不适用于 [Authorize] 标签。

  更新:

  其余问题已解决,将在

  var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) });

  到

  var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic");

  即,将 authenticationType 的值设置为“Basic”。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线