.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是否與數據庫以前保存的一致,至此,徹底解決。

問題:

  • 咱們以前說過的,每次請求認證都會請求數據庫,下降性能。
  • 資源服務不能獨立,必須與用戶庫綁定。
相關文章
相關標籤/搜索