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

2024-03-26

我对 Web API 实现相当陌生,我创建了一个 Web API 服务来使用它ASP.net web form应用程序以及一些独立应用程序(C#控制台/Windows应用程序)使用HttpClient object.

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

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

以下是我在 ASP.NET 应用程序中使用 Web api 所遵循的步骤。

  1. 在 ASP.net Web 表单登录页面中,我将 Web API 称为“TokenController”,该控制器采用两个参数登录 ID 和密码,并返回我存储在会话对象中的 JWT 令牌。

  2. 现在,每当我的客户端应用程序也需要使用 Web api 资源时,都必须在请求标头中发送访问令牌,同时使用以下命令调用 Web apihttpclient.

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

如何刷新令牌而不强制用户再次登录。

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

以下是代码。

WebAPI

AuthHandler.cs

  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.cs

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.cs

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.cs

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);
    }

}

PremiumCalculatorController.cs

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

网络表格申请

登录.aspx.cs

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.cs

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;
        }

    }

}

年费计算器.aspx.cs

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
        {

        }

    }

}

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


我有几点评论:

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

  2. 如果您想实施 OAuth,请阅读规格 https://www.rfc-editor.org/rfc/rfc6749。在那里您将找到实现刷新令牌所需的一切。

  3. 在 TokenController 中您处理登录。你应该检查一下其他条件 https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2以及。

  • 授权类型 = 密码
  • 内容类型必须是“application/x-www-form-urlencoded”
  • 仅当通过安全线路 (https) 发送请求时,才应处理该请求。
  1. 当获取access_token并且仅当请求refresh_token时,您应该包含刷新令牌 https://www.rfc-editor.org/rfc/rfc6749#section-4.3.3在 access_token 中。

  2. 您不需要刷新令牌客户端应用程序 https://www.rfc-editor.org/rfc/rfc6749#section-4.4.2(grant_type = client_credentials),因为它们使用 clientid/secret 来获取访问令牌。扩展 TokenController 以允许 client_credentials 流。请注意:刷新令牌用于users仅当可以保密时才应使用。刷新令牌非常强大,因此请小心处理。

  3. 为了刷新访问令牌 https://www.rfc-editor.org/rfc/rfc6749#section-6你需要发送刷新令牌到终点。在您的情况下,您可以扩展 TokenController 以允许刷新令牌请求。您需要检查:

  • grant_type = 刷新令牌
  • 内容类型必须是“application/x-www-form-urlencoded”
  1. 刷新令牌有多种场景,您也可以组合使用:
  • 将刷新令牌保存在数据库中。每次使用刷新令牌时,您都可以将其从数据库中删除,然后保存新的刷新令牌,该令牌也会在新的 access_token 中返回。
  • 将刷新令牌设置为更长的生存期,并且在刷新访问令牌时不刷新它。在这种情况下,返回的 access_token 不包含新的刷新令牌。这样您就需要在refresh_token过期后重新登录。
  1. 请注意,永不过期且无法撤销的刷新令牌为用户提供了无限制的访问权限,因此请谨慎实施。

  2. 在我的回答中here https://stackoverflow.com/questions/31747827/updating-roles-when-granting-refresh-token-in-web-api-2/44833586#44833586您可以看到如何使用身份 2 处理刷新令牌。您可以考虑切换到身份 2。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Webapi 2.0如何在访问令牌过期时实现刷新JWT令牌 的相关文章

  • 如何从 httpclient 调用中获取内容正文?

    我一直在试图弄清楚如何读取 httpclient 调用的内容 但我似乎无法理解 我得到的响应状态是 200 但我不知道如何获取返回的实际 Json 这就是我所需要的 以下是我的代码 async Task
  • 如果 DirectoryInfo.GetFiles().Length 超过 Int32.MaxValue 怎么办?

    由另一个question https stackoverflow com questions 3766540 error on maximum number of files 3767265 3767265关于文件夹中的最大文件数 我注意到
  • 使用 CustomUrlHelper 覆盖 UrlHelper - ASP.NET CORE 2.0

    有没有办法强制我的 ASP NET Core 2 0 应用程序使用我在各处编写的自定义 UrlHelper 我有一个具有自定义逻辑的类 public class CustomUrlHelper UrlHelper 我希望它能在任何地方使用
  • 如何在任何给定时刻找到我的网站 (IIS 7/asp.net) 的访问者/用户数量?

    我需要显示有多少用户正在浏览我的网站 该网站运行在iis7上 我们使用的是asp net 3 5 活跃会话的数量是一个好的方法吗 该数字不需要非常准确 不需要历史记录 我只想知道现在有多少用户 在线 并将其显示在页面本身上 您可以为此使用
  • 如何从代码隐藏中向我的 div 添加点击事件?

    如何从代码隐藏中向我的 div 添加点击事件 当我点击 div 时 会出现一个消息框 其中显示 您想删除它吗 并在框中显示 是 或 否 全部来自后面的代码 while reader Read System Web UI HtmlContro
  • ASP.Net 的最佳免费文件管理器 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 我的 switch 语句中的 if 语句在我的计算器中无法正常工作。需要帮助修复除以 0 错误

    我创建了一个基本计算器 但是用除法对其进行了编码 当我除以零时 它会在第二个文本框中向用户提供错误 但现在即使我除以 3 或任何其他不为 0 的数字 错误不断出现在我的第二个文本框中 namespace calc
  • 停止 IResponseCookies.Append() 编码 cookie。 dotnetcore 2.0 [重复]

    这个问题在这里已经有答案了 我们正在构建一个 dotnetcore 2 0 Web 应用程序 该应用程序通过 http 从遗留系统接收 cookie 并在响应中设置 cookie 当我调试应用程序时 我可以看到cookie value未进行
  • __doPostBack 方法如何被调用?调用方法在哪里?

    我用了一个
  • 自定义可视化 Web 部件属性 sharepoint

    我在 Visual Studio 2012 中创建可视 Web 部件属性时遇到问题 我被提及http msdn microsoft com en us library ee231551 aspx http msdn microsoft co
  • 本地化 ASP.NET 资源的滑动过期

    假设我们有 2 个站点 myDomain AU 和 myDomain RU 具有相同的代码和本地化资源文件 resx 和 ru resx 我们预计大多数英语用户将使用 AU 网站 大多数俄语用户将使用 RU 网站 但是 如果 AU 域的某些
  • 回发后,asp.net 文本框不会填充

    我使用 ASP NET 和 C 几个文本框来计算输入的结果 Textbox2 输入了一个值 单击按钮后 Textbox1 就会被填充 我第一次输入该值时效果很好 但是第二次更改 Textbox2 中的值时 我发现在单击按钮进行调试时该值被分
  • ASP.NET - Google Chrome 缓存 DropDownList 选择

    我的购物车页面上的 Google Chrome 和 Safari 似乎遇到了缓存问题 购物车中有 2 个下拉列表 当您在更改下拉列表中的值后点击结帐按钮时 它会将列表中选择的内容提交到数据库 解释意外的行为有点困难 所以我会尝试一步一步地写
  • ASP.NET Web Api 2 中的异常处理

    Problem 我需要处理 Web api 2 异常并返回一个具有正确状态代码的丰富对象 401 表示未经授权 404 表示 ContentNotFound 等 以及一些额外信息作为内容 此外 我需要内容看起来像序列化的Exception对
  • 部署项目 dll 导致“两者都存在类型 x”错误

    我有一个 Web 应用程序项目 一个业务逻辑项目和一个用于 Web 应用程序的 Web 部署项目 当我构建解决方案时 部署 Release bin 包含每个项目的 1 个 dll 因此我为 MyWeb dll MyWebBusiness d
  • ViewModel 的列表在操作中为 null

    我正在开发我的第一个 ASP NET MVC 3 应用程序 我有一个如下所示的视图 model IceCream ViewModels Note NotesViewModel using Html BeginForm Html Valida
  • ASP.NET Core 与现有的 IoC 容器和环境?

    我想运行ASP NET 核心网络堆栈以及MVC在已托管现有应用程序的 Windows 服务环境中 以便为其提供前端 该应用程序使用 Autofac 来处理 DI 问题 这很好 因为它已经有一个扩展Microsoft Extensions D
  • 将字符串转换为正确的 URI 格式?

    有没有简单的方法可以将电子邮件地址字符串转换为正确的 URI 格式 Input http mywebsite com validate email 3DE4ED727750215D957F8A1E4B117C38E7250C33 email
  • asp.net网格分页的SQL查询

    我在用iBatis and SQLServer 使用偏移量和限制进行分页查询的最佳方法是什么 也许我添加该列ROW NUMBER OVER ORDER BY Id AS RowNum 但这只会阻止简单查询的数据访问 在某些情况下 我使用选择
  • ASP.NET Core 会话超时

    我记得我们在 ASP NET 中使用了 session timeout 来更改会话超时 如果不更改 则为 20 分钟 我尝试在 Startup cs 中更改 ASP NET Core 3 1 中的会话超时 但没有任何反应 我对操作员使用身份

随机推荐