.Net Identity OAuth 2.0 SecurityStamp 使用

起源:

近期帮别人作项目,涉及到OAuth认证,服务端主动使token失效,要使对应用户不能再继续访问,只能从新登录,或者从新受权。web

场景:

这种场景OAuth2.0是支持的,好比用户修改了密码,那全部以前保留在各个客户端的token均失效,要求用户从新提供凭证。算法

缘由:

在以前的项目中,一旦受权服务AuthorizationServer发放了token,全部的资源服务只会经过统一的加密解密算法,解析出token中的信息包括用户身份等,就直接使用了。这样由于不和数据库或外部存储介质交互,因此效率会比较高。如下是在全部资源服务、包括认证服务Web.config中的配置,machineKey必须相同。sql

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <machineKey decryptionKey="B7E121C5839A624E" validation="SHA1" validationKey="C2B8DF31AB96asd428066DFDA1A479542825F3B48865C4E47AF6A026F2" />
  </system.web>

这样效率是提升了,可是每次验证不与后端(尤指存储)交互,因此token一旦发出,除非客户端主动清除或着超出有效时间,不然都有效,有必定的失窃风险。数据库

分析:

.Net在IdentityServer3.0在用户表中使用SecurityStamp字段,做为安全标识,去识别是否失效。默认状况是没有开启验证的,不然就像以前说的每次都会和数据库交互,下降性能。关于SecurityStamp的说明,你们能够看一下这篇文章:后端

What is ASP.NET Identity's IUserSecurityStampStore<TUser> interface?promise

    This is meant to represent the current snapshot of your user's credentials. So if nothing changes, the stamp will stay the same. But if the user's password is changed, or a login is removed (unlink your google/fb account), the stamp will change. This is needed for things like automatically signing users/rejecting old cookies when this occurs, which is a feature that's coming in 2.0. 安全

    Edit: Updated for 2.0.0. So the primary purpose of the SecurityStamp is to enable sign out everywhere. The basic idea is that whenever something security related is changed on the user, like a password, it is a good idea to automatically invalidate any existing sign in cookies, so if your password/account was previously compromised, the attacker no longer has access. cookie

    In 2.0.0 we added the following configuration to hook the OnValidateIdentity method in the CookieMiddleware to look at the SecurityStamp and reject cookies when it has changed. It also automatically refreshes the user's claims from the database every refreshInterval if the stamp is unchanged (which takes care of things like changing roles etc)app

文章中给出的代码以下:ide

app.UseCookieAuthentication(new CookieAuthenticationOptions {
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});

虽然这篇文章大体讲明白了,这是个什么东西和怎么用,可是仔细想一想,仍是有问题,由于咱们是使用Bearer方式,这里只是Cookie,因此还差点什么。在资源服务中的配置以下:

namespace ResourceServer
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions());
        }
    }
}

因此咱们应该更改的是OAuthBearerAuthenticationOptions这个对象。有找到文章参考以下:

Using bearer tokens (ASP.NET Identity 2.0) with WCF Data Services

总结:

在资源服务中,引入Microsoft.AspNet.Identity.Owin包,在Startup中,更改以下代码:

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
            {
                Provider = new OAuthBearerAuthenticationProvider()
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });
        }
    }

 查看SecurityStampValidator类的源码:

public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser, TKey>(
            TimeSpan validateInterval, Func<TManager, TUser, Task<ClaimsIdentity>> regenerateIdentityCallback,
            Func<ClaimsIdentity, TKey> getUserIdCallback)
            where TManager : UserManager<TUser, TKey>
            where TUser : class, IUser<TKey>
            where TKey : IEquatable<TKey>
        {
            .....
               var user = await manager.FindByIdAsync(userId).WithCurrentCulture(); var reject = true; // Refresh the identity if the stamp matches, otherwise reject if (user != null && manager.SupportsUserSecurityStamp) { var securityStamp = context.Identity.FindFirstValue(Constants.DefaultSecurityStampClaimType); if (securityStamp == await manager.GetSecurityStampAsync(userId).WithCurrentCulture()) { reject = false; // Regenerate fresh claims if possible and resign in if (regenerateIdentityCallback != null) { var identity = await regenerateIdentityCallback.Invoke(manager, user).WithCurrentCulture(); if (identity != null) { // Fix for regression where this value is not updated // Setting it to null so that it is refreshed by the cookie middleware context.Properties.IssuedUtc = null; context.Properties.ExpiresUtc = null; context.OwinContext.Authentication.SignIn(context.Properties, identity); } } } }           ..... }; }

它会本身判断token中解析出来的securityStamp是否与数据库以前保存的一致,至此,彻底解决。

问题:

  • 咱们以前说过的,每次请求认证都会请求数据库,下降性能。
  • 资源服务不能独立,必须与用户库绑定。
相关文章
相关标签/搜索