• 07 为 MVC 客户端刷新 Token


    原文:https://www.yuque.com/yuejiangliu/dotnet/gbzs4g


    07 为 MVC 客户端刷新 Token.mp4 (72.6 MB)

    本节是上节的补充,主要讲解如何使用 Refresh Token 刷新 Access Token。

    一、设置并启用过期时间

    打开 Idp 项目,修改 MVC Client 的 AccessTokenLifetime 为 60s:

    // MVC client, authorization code
    new Client
    {
        ...
        // 设为 True 即支持 Refresh Token
        AllowOfflineAccess = true, // offline_access
        AccessTokenLifetime = 60, // 60 seconds
    
        AllowedScopes =
        {
            ...
        }
    },

    通过 jwt.io 能够看到过期时间设置在 Token 里面了。

    image.png

    结果发现 exp 都过了还能从 Api1 获得资源。

    实际上是 Api1 中没有及时的验证 Token(默认为 300s 验证一次)。

    修改 Api1,Token 验证间隔为 1 分钟,且 Token 必须包含过期时间:

    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;
    
            options.Audience = "api1";
            options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(1);
            options.TokenValidationParameters.RequireExpirationTime = true;
        });

    效果:过期后报错 Exception: Unauthorized

    image.png

    二、Refresh Token

    参考 OpenID Connect 协议 构造 RefreshTokenRequest:

    image.png

    在 MVC Client 的 HomeController 中添加刷新 Token 的方法:

    private async Task<string> RenewTokenAsync()
    {
        var client = new HttpClient();
        var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
    
        if (disco.IsError) throw new Exception(disco.Error);
    
        var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
    
        // Refresh Access Token
        var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "mvc client",
            ClientSecret = "mvc secret",
            Scope = "api1 openid profile email phone address",
            GrantType = OpenIdConnectGrantTypes.RefreshToken,
            RefreshToken = refreshToken
        });
    
        if (tokenResponse.IsError) throw new Exception(tokenResponse.Error);
    
        var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
    
        var tokens = new[]
        {
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.IdToken,
                Value = tokenResponse.IdentityToken
            },
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.AccessToken,
                Value = tokenResponse.AccessToken
            },
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.RefreshToken,
                Value = tokenResponse.RefreshToken
            },
            new AuthenticationToken
            {
                Name = "expires_at",
                Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
            }
        };
    
        // 获取身份认证的结果,包含当前的 Principal 和 Properties
        var currentAuthenticateResult =
            await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    
        // 更新 Cookie 里面的 Token
        currentAuthenticateResult.Properties.StoreTokens(tokens);
    
        // 登录
        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
            currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
    
        return tokenResponse.AccessToken;
    }

    ToString("o"): image.png

    在 Index Action 中刷新 Token:

    public async Task<IActionResult> Index()
    {
        var client = new HttpClient();
        var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
        if (disco.IsError) throw new Exception(disco.Error);
    
        var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
    
        client.SetBearerToken(accessToken);
    
        var response = await client.GetAsync("http://localhost:5001/identity");
        if (!response.IsSuccessStatusCode)
        {
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                // 这样写仅为了方便演示
                await RenewTokenAsync();
                return RedirectToAction();
            }
    
            throw new Exception(response.ReasonPhrase);
        }
    
        var content = await response.Content.ReadAsStringAsync();
        return View("Index", content);
    }

    注:IdentityServer4.Samples 项目没有了,推荐参考官方文档 Switching to Hybrid Flow and adding API Access back 中的代码。

  • 相关阅读:
    测 试 报 告模板
    浅谈如何设计自动化测试框架
    GET与POST类型接口
    测试用例设计总结
    Java OO知识总结
    Java基础知识总结
    MIT 6.824学习笔记3 Go语言并发解析
    Go语言_并发
    MIT 6.824学习笔记4 Lab1
    Leetcode Lect3 二分法总结
  • 原文地址:https://www.cnblogs.com/springsnow/p/13879480.html
Copyright © 2020-2023  润新知