1. 程式人生 > >開箱即用 - jwt 無狀態分布式授權

開箱即用 - jwt 無狀態分布式授權

token .get invalid ndk var access convert vid sys

基於JWT(Json Web Token)的授權方式

JWT 是JSON風格輕量級的授權和身份認證規範,可實現無狀態、分布式的Web應用授權;

技術分享

從客戶端請求服務器獲取token, 用該token 去訪問實現了jwt認證的web服務器。 token 可保存自定義信息,如用戶基本信息, web服務器用key去解析token,就獲取到請求用戶的信息了;
很方便解決跨域授權的問題,因為跨域無法共享cookie,.net平臺集成的 FormAuthentication 認證系統是基於Session保存授權信息,拿不到cookie就無法認證,用jwt完美解決了。
很多時候,web服務器和授權服務器是同一個項目,所以也可以用以下架構:

技術分享

實現JWT授權

1.vs2015 新建一個WebApi,安裝下面的庫,可用nuget 或 命令安裝:

install-package Thinktecture.IdentityModel.Core
install-package Microsoft.Owin.Security.Jwt

2.把Startup.Auth.cs 下的 ConfigureAuth 方法清空掉,改為:


    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            var issuer = ConfigurationManager.AppSettings["issuer"];
            var secret = TextEncodings.Base64Url.Decode(Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(ConfigurationManager.AppSettings["secret"])));

            //用jwt進行身份認證
            app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { "Any" },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]{
        new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
                }
            });


            app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
            {
                //生產環境設為false
                AllowInsecureHttp = true,
                //請求token的路徑
                TokenEndpointPath = new PathString("/oauth2/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
                //請求獲取token時,驗證username, password
                Provider = new CustomOAuthProvider(),
                //定義token信息格式 
                AccessTokenFormat = new CustomJwtFormat(issuer, secret),
            });

        }
    }

3.ConfigureAuth中的 AccessTokenFormat = new CustomJwtFormat(issuer, secret)是自定義token 保存的信息格式, CustomJwtFormat.cs 類代碼


    /// <summary> 
    /// 自定義 jwt token 的格式 
    /// </summary>
    public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
    {
        private readonly byte[] _secret;
        private readonly string _issuer;

        public CustomJwtFormat(string issuer, byte[] secret)
        {
            _issuer = issuer;
            _secret = secret;
        }

        public string Protect(AuthenticationTicket data)
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            var signingKey = new HmacSigningCredentials(_secret);
            var issued = data.Properties.IssuedUtc;
            var expires = data.Properties.ExpiresUtc;

            return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, "Any", data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey));
        }

        public AuthenticationTicket Unprotect(string protectedText)
        {
            throw new NotImplementedException();
        }
    }

4.ConfigureAuth中的 Provider = new CustomOAuthProvider() 是自定義驗證username, password 的,可以用它來實現訪問數據庫的驗證業務邏輯,CustomOAuthProvider.cs類代碼

    /// <summary>
    /// 自定義 jwt oauth 的授權驗證
    /// </summary>
    public class CustomOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var username = context.UserName;
            var password = context.Password;
            string userid;
            if (!CheckCredential(username, password, out userid))
            {
                context.SetError("invalid_grant", "The user name or password is incorrect");
                context.Rejected();
                return Task.FromResult<object>(null);
            }
            var ticket = new AuthenticationTicket(SetClaimsIdentity(context, userid, username), new AuthenticationProperties());
            context.Validated(ticket);

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }

        private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, string userid, string usercode)
        {
            var identity = new ClaimsIdentity("JWT");
            identity.AddClaim(new Claim("userid", userid));
            identity.AddClaim(new Claim("username", usercode));
            return identity;
        }

        private static bool CheckCredential(string usernme, string password, out string userid)
        {
            var success = false;
            // 用戶名和密碼驗證
            if (usernme == "admin" && password == "admin")
            {
                userid = "1";
                success = true;
            }
            else
            {
                userid = "";
            }
            return success;
        }
    }

5.Web.config 添加 issue 和 secret

  <appSettings>
    <add key="issuer" value="test"/>
    <!--32個字符的secret-->
    <add key="secret" value="12345678123456781234567812345678"/>
  </appSettings>

使用

強烈建議用 chrome 的 postman 插件來調試

  1. 獲取token
    技術分享

  2. 用token請求數據
    技術分享

header 要添加 Authorization , 值為: Bearer [token], 獲取到的 token 替換 [token], 如

Authorization   Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiIxIiwidXNlcmNvZGUiOiIxIiwiaXNzIjoidGVzdCIsImF1ZCI6IkFueSIsImV4cCI6MTQ4NzI0MTQ5MCwibmJmIjoxNDg0NjQ5NDkwfQ.RaWlJC3OF0RNz4mLtuW4uQtRKDHF8RXwZwzIcbZoNOo

JWT缺點

  1. 一旦拿到token, 可用它訪問服務器,直到過期,中間服務器無法控制它,如是它失效(有解決方案: 在 token 中保存信息,可添加額外的驗證,如加一個 flag, 把數據庫對應的flag失效,來控制token有效性)。
  2. token 的過期時間設置很關鍵,一般把它設到淩晨少人訪問時失效,以免用戶使用過程中失效而丟失數據。
  3. token保存的信息有限,且都是字符串。

Demo源碼

開箱即用 - jwt 無狀態分布式授權