代码之家  ›  专栏  ›  技术社区  ›  Neeraj Kumar Gupta

Webapi 2.0访问令牌过期时如何实现刷新JWT令牌

  •  4
  • Neeraj Kumar Gupta  · 技术社区  · 6 年前

    我是web API实现方面的新手,我创建了一个web API服务来使用它 ASP.net web form 应用程序以及一些独立应用程序(C#控制台/Windows应用程序),使用 HttpClient 对象

    我已经在web api中实现了一个基本的JWT访问令牌身份验证,该身份验证技术在令牌未过期之前工作正常,当令牌过期时,web api不接受请求,因为令牌已过期!根据身份验证实现,这很好,但我想在web api中实现刷新令牌逻辑,以便令牌可以更新/引用,客户端应该能够使用web api资源。

    我在google上搜索了很多,但找不到刷新令牌逻辑的正确实现。如果有人有正确的方法来处理过期的访问令牌,请帮助我。

    1. 在ASP中。net web表单登录页我调用web API“TokenController”此控制器接受两个参数loginID和password,并返回存储在会话对象中的JWT令牌。

    2. 现在,每当我的客户端应用程序需要使用web api时,web api资源必须在请求标头中发送访问令牌,同时使用 httpclient .

    3. 但当令牌过期时,客户端无法使用web api资源,他必须再次登录并续订令牌!我不想这样,用户不应该提示再次登录,因为应用程序会话超时时间尚未过去。

    如何在不强制用户再次登录的情况下刷新令牌。

    如果我下面给出的JWT访问令牌实现逻辑不合适或不正确,请告诉我正确的方法。

    以下是代码。

    WebAPI

    AuthHandler。反恐精英

      public class AuthHandler : DelegatingHandler
        {
    
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                    CancellationToken cancellationToken)
        {
            HttpResponseMessage errorResponse = null;           
            try
            {
                IEnumerable<string> authHeaderValues;
                request.Headers.TryGetValues("Authorization", out authHeaderValues);
    
                if (authHeaderValues == null)
                    return base.SendAsync(request, cancellationToken);
    
                var requestToken = authHeaderValues.ElementAt(0);
    
                var token = "";
    
                if (requestToken.StartsWith("Bearer ", StringComparison.CurrentCultureIgnoreCase))
                {
                    token = requestToken.Substring("Bearer ".Length);
                }
    
                var secret = "w$e$#*az";
    
                ClaimsPrincipal cp = ValidateToken(token, secret, true);
    
    
                Thread.CurrentPrincipal = cp;
    
                if (HttpContext.Current != null)
                {
                    Thread.CurrentPrincipal = cp;
                    HttpContext.Current.User = cp;
                }
            }
            catch (SignatureVerificationException ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
            }
            catch (Exception ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
            }
    
    
            return errorResponse != null
                ? Task.FromResult(errorResponse)
                : base.SendAsync(request, cancellationToken);
        }
    
        private static ClaimsPrincipal ValidateToken(string token, string secret, bool checkExpiration)
        {
            var jsonSerializer = new JavaScriptSerializer();
            string payloadJson = string.Empty;
    
            try
            {
                payloadJson = JsonWebToken.Decode(token, secret);
            }
            catch (Exception)
            {
                throw new SignatureVerificationException("Unauthorized access!");
            }
    
            var payloadData = jsonSerializer.Deserialize<Dictionary<string, object>>(payloadJson);
    
    
            object exp;
            if (payloadData != null && (checkExpiration && payloadData.TryGetValue("exp", out exp)))
            {
                var validTo = AuthFactory.FromUnixTime(long.Parse(exp.ToString()));
                if (DateTime.Compare(validTo, DateTime.UtcNow) <= 0)
                {
                    throw new SignatureVerificationException("Token is expired!");
                }
            }
    
            var clmsIdentity = new ClaimsIdentity("Federation", ClaimTypes.Name, ClaimTypes.Role);
    
            var claims = new List<Claim>();
    
            if (payloadData != null)
                foreach (var pair in payloadData)
                {
                    var claimType = pair.Key;
    
                    var source = pair.Value as ArrayList;
    
                    if (source != null)
                    {
                        claims.AddRange(from object item in source
                                        select new Claim(claimType, item.ToString(), ClaimValueTypes.String));
    
                        continue;
                    }
    
                    switch (pair.Key.ToUpper())
                    {
                        case "USERNAME":
                            claims.Add(new Claim(ClaimTypes.Name, pair.Value.ToString(), ClaimValueTypes.String));
                            break;
                        case "EMAILID":
                            claims.Add(new Claim(ClaimTypes.Email, pair.Value.ToString(), ClaimValueTypes.Email));
                            break;
                        case "USERID":
                            claims.Add(new Claim(ClaimTypes.UserData, pair.Value.ToString(), ClaimValueTypes.Integer));
                            break;
                        default:
                            claims.Add(new Claim(claimType, pair.Value.ToString(), ClaimValueTypes.String));
                            break;
                    }
                }
    
            clmsIdentity.AddClaims(claims);
    
            ClaimsPrincipal cp = new ClaimsPrincipal(clmsIdentity);
    
            return cp;
        }
    
    
    }
    

    AuthFactory。反恐精英

    public static class AuthFactory
    {
    internal static DateTime FromUnixTime(double unixTime)
    {
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return epoch.AddSeconds(unixTime);
    }
    
    
    internal static string CreateToken(User user, string loginID, out double issuedAt, out double expiryAt)
    {
    
        var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        expiryAt = Math.Round((DateTime.UtcNow.AddMinutes(TokenLifeDuration) - unixEpoch).TotalSeconds);
        issuedAt = Math.Round((DateTime.UtcNow - unixEpoch).TotalSeconds);
    
        var payload = new Dictionary<string, object>
            {
                {enmUserIdentity.UserName.ToString(), user.Name},
                {enmUserIdentity.EmailID.ToString(), user.Email},
                {enmUserIdentity.UserID.ToString(), user.UserID},
                {enmUserIdentity.LoginID.ToString(), loginID}
                ,{"iat", issuedAt}
                ,{"exp", expiryAt}
            };
    
        var secret = "w$e$#*az";
    
        var token = JsonWebToken.Encode(payload, secret, JwtHashAlgorithm.HS256);
    
        return token;
    }
    
    public static int TokenLifeDuration
    {
        get
        {
            int tokenLifeDuration = 20; // in minuets
            return tokenLifeDuration;
        }
    }
    
    internal static string CreateMasterToken(int userID, string loginID)
    {
    
        var payload = new Dictionary<string, object>
            {
                {enmUserIdentity.LoginID.ToString(), loginID},
                {enmUserIdentity.UserID.ToString(), userID},
                {"instanceid", DateTime.Now.ToFileTime()}
            };
    
        var secret = "w$e$#*az";
    
        var token = JsonWebToken.Encode(payload, secret, JwtHashAlgorithm.HS256);
    
        return token;
    }
    

    }

    WebApiConfig。反恐精英

    public static class WebApiConfig
    {
    
        public static void Register(HttpConfiguration config)
        {
            var cors = new EnableCorsAttribute("*", "*", "*");
            config.EnableCors(cors);
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    
            config.Formatters.Remove(config.Formatters.XmlFormatter);
    
            config.MessageHandlers.Add(new AuthHandler());
        }
    }
    

    TokenController。反恐精英

    public class TokenController : ApiController
    {
        [AllowAnonymous]
        [Route("signin")]
        [HttpPost]
        public HttpResponseMessage Login(Login model)
        {
            HttpResponseMessage response = null;
            DataTable dtblLogin = null;
            double issuedAt;
            double expiryAt;
    
            if (ModelState.IsValid)
            {
                dtblLogin = LoginManager.GetUserLoginDetails(model.LoginID, model.Password, true);
    
                if (dtblLogin == null || dtblLogin.Rows.Count == 0)
                {
                    response = Request.CreateResponse(HttpStatusCode.NotFound);
                }
                else
                {
                    User loggedInUser = new User();
                    loggedInUser.UserID = Convert.ToInt32(dtblLogin.Rows[0]["UserID"]);
                    loggedInUser.Email = Convert.ToString(dtblLogin.Rows[0]["UserEmailID"]);
                    loggedInUser.Name = Convert.ToString(dtblLogin.Rows[0]["LastName"]) + " " + Convert.ToString(dtblLogin.Rows[0]["FirstName"]);
    
                    string token = AuthFactory.CreateToken(loggedInUser, model.LoginID, out issuedAt, out expiryAt);
                    loggedInUser.Token = token;
    
                    response = Request.CreateResponse(loggedInUser);
    
                }
            }
            else
            {
                response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
            return response;
        }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
    
    }
    

    PremiumCalculator控制器。反恐精英

    PremiumCalculatorController : ApiController
    {
        [HttpPost]
        public IHttpActionResult CalculatAnnualPremium(PremiumFactorInfo premiumFactDetails)
        {
          PremiumInfo result;
          result = AnnualPremium.GetPremium(premiumFactDetails);
          return Ok(result);
        }
    }
    

    Web表单应用程序

    登录名。aspx。反恐精英

    public class Login
    {
        protected void imgbtnLogin_Click(object sender, System.EventArgs s)
        {
    
        UserInfo loggedinUser = LoginManager.ValidateUser(txtUserID.text.trim(), txtPassword.text);
    
        if (loggedinUser != null)
        {
    
            byte[] password = LoginManager.EncryptPassword(txtPassword.text);
    
            APIToken tokenInfo = ApiLoginManager.Login(txtUserID.text.trim(), password);
    
            loggedinUser.AccessToken = tokenInfo.Token;
    
            Session.Add("LoggedInUser", loggedinUser);
    
            Response.Redirect("Home.aspx");
    
        }
        else
        {
            msg.Show("Logn ID or Password is invalid.");
        }
    
    
        }
    }
    

    ApiLoginManager。反恐精英

    public class ApiLoginManager
    {
        public UserDetails Login(string userName, byte[] password)
        {
            APIToken result = null;
            UserLogin objLoginInfo;
            string webAPIBaseURL = "http://localhost/polwebapiService/"
            try
            {
                using (var client = new HttpClient())
                {
                    result = new UserDetails();
                    client.BaseAddress = new Uri(webAPIBaseURL);
                    objLoginInfo = new UserLogin { LoginID = userName, Password = password };
    
                    var response = client.PostAsJsonAsync("api/token/Login", objLoginInfo);
    
                    if (response.Result.IsSuccessStatusCode)
                    {
                        string jsonResponce = response.Result.Content.ReadAsStringAsync().Result;
                        result = JsonConvert.DeserializeObject<APIToken>(jsonResponce);
                    }
    
                    response = null;
                }
    
                return result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
    
        }
    
    }
    

    年度PremiumCalculator。aspx。反恐精英

    public class AnnualPremiumCalculator
    {
        protected void imgbtnCalculatePremium_Click(object sender, System.EventArgs s)
        { 
           string token = ((UserInfo)Session["LoggedInUser"]).AccessToken;
           PremiumFactors premiumFacts = CollectUserInputPremiumFactors();
           PremiumInfo premiumDet = CalculatePremium(premiumFacts, token);
           txtAnnulPremium.text = premiumDet.Premium;
           //other details so on 
        }
    
        public PremiumInfo CalculatePremium(PremiumFactors premiumFacts, string accessToken)
        {
            PremiumInfo result = null;
            string webAPIBaseURL = "http://localhost/polwebapiService/";
            try
            {
                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri(webAPIBaseURL);
    
                    StringContent content = new StringContent(JsonConvert.SerializeObject(premiumFacts), Encoding.UTF8, "application/json");
    
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    
                    var response = client.PostAsync("api/calculators/PremiumCalculator", content);
    
                    if (response.Result.IsSuccessStatusCode)
                    {
                        string jsonResponce = response.Result.Content.ReadAsStringAsync().Result;
                        result = JsonConvert.DeserializeObject<PremiumInfo>(jsonResponce);
                    }
    
                    response = null;
    
                }
    
                return result;
            }
            finally
            {
    
            }
    
        }
    
    }
    

    上面是一个示例代码来说明这个问题,它可能有一些输入错误。

    2 回复  |  直到 6 年前
        1
  •  7
  •   Community arnoo    3 年前

    我有几点意见:

    1. 访问令牌将由客户端保存,而不是保存在服务器上的会话中。刷新令牌的计数相同。原因是,通常没有会话。智能客户端可以在没有会话的情况下处理令牌,MVC网站可以使用cookie,API不知道会话。这是不被禁止的,但您需要再次担心会话过期,并且当您重新启动服务器时,所有用户都必须再次登录。

    2. 如果要实现OAuth,请阅读 specification . 在那里,您将找到实现刷新令牌所需的一切。

    3. 在TokenController中处理登录。你应该检查一下 other conditions

    • grant\u type=密码
    • 内容类型必须为“应用程序/x-www-form-urlencoded”
    • 仅当通过安全线路(https)发送时,才应处理该请求。
    1. 获取access\u令牌时,仅当请求refresh\u令牌时,才应包括 refresh_token 在access\u令牌中。

    2. 您不需要刷新令牌 client applications (grant\u type=client\u凭据),因为它们使用clientid/secret来获取访问令牌。扩展TokenController以允许client\u凭据流。请注意:刷新令牌用于 用户 只有在可以保密的情况下才使用。刷新令牌功能非常强大,请小心处理。

    3. 为了 refresh an access token 您需要发送 到端点。在您的情况下,可以扩展TokenController以允许refresh\u token请求。您需要检查:

    • grant\u type=刷新\u令牌
    • 内容类型必须为“应用程序/x-www-form-urlencoded”
    1. 刷新令牌有几种方案,您也可以将其结合使用:
    • 将刷新令牌保存在数据库中。每次使用刷新令牌时,都可以将其从数据库中删除,然后保存新的刷新令牌,该刷新令牌也会在新的access\u令牌中返回。
    • 将刷新令牌设置为更长的生存期,并且在刷新访问令牌时不刷新它。在这种情况下,返回的access\u令牌不包括新的刷新令牌。这样,您需要在refresh\u令牌过期后再次登录。
    1. 请注意,永不过期且无法撤销的刷新令牌为用户提供了无限制的访问权限,因此请小心您的实现。

    2. 在我的回答中 here 您可以看到如何使用标识2处理刷新令牌。您可以考虑切换到标识2。

    我想我什么都提到了。如果我遗漏了什么或有什么不清楚的地方,请告诉我。

        2
  •  6
  •   Niko    6 年前

    这可以通过一个单独的持久刷新令牌来完成。一个很好的教程 http://www.c-sharpcorner.com/article/handle-refresh-token-using-asp-net-core-2-0-and-json-web-token/