有了新的access_token后,如何更新Cookie?

时间:2019-04-14 13:14:47

标签: c# .net owin identityserver4

使用刷新令牌获取新的访问令牌后,我想使用该访问令牌更新客户端Cookie。

我的客户端能够使用ajax登录并调用我的REST API,但是当该第一授权到期时,该API调用自然将不再起作用。

我有一个使用自己的REST API的.NET Web应用程序。该API是同一项目的一部分。它没有自己的启动配置。

在每个请求的标头中发送cookie时,它需要具有新的未过期的访问令牌,这样我才不会收到该请求的“用户未授权”。

现在,我可以使用刷新令牌来获取新令牌,但是cookie的值没有更改,因此我认为我需要在客户端发送任何请求之前更新cookie以反映新的访问令牌。 / p>

下面是我的混合客户端:

using IdentityModel.Client;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace Cts.HomeService.Web.App_Start
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var identityServerSection = (IdentityServerSectionHandler)System.Configuration.ConfigurationManager.GetSection("identityserversection");

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies",
                CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
            });


            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = "localTestClient",
                Authority = "http://localhost:5000",
                RedirectUri = identityServerSection.Identity.RedirectUri,
                Scope = "openid profile offline_access",
                ResponseType = "code id_token",
                RequireHttpsMetadata = false,
                PostLogoutRedirectUri = identityServerSection.Identity.RedirectUri,

                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role",
                },
                SignInAsAuthenticationType = "Cookies",
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        var tokenClient = new TokenClient(
                            "http://localhost:5000/connect/token",
                            "localTestClient",
                            "");

                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                            n.Code, n.RedirectUri);

                        if (tokenResponse.IsError)
                        {
                            throw new Exception(tokenResponse.Error);
                        }

                        // use the access token to retrieve claims from userinfo
                        var userInfoClient = new UserInfoClient(
                            "http://localhost:5000/connect/userinfo");

                        var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                        // create new identity
                        var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                        id.AddClaims(userInfoResponse.Claims);

                        id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                        id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                        id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                        id.AddClaim(new Claim("id_token", tokenResponse.IdentityToken));
                        id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                        n.AuthenticationTicket = new AuthenticationTicket(
                            new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                            n.AuthenticationTicket.Properties);
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        {
                            // so here I'll grab the access token
                            if (isAccessTokenExpired()) {
                                var cancellationToken = new CancellationToken();
                                var newAccessToken = context.GetNewAccessTokenAsync(refresh_token, null, cancellationToken);
                               // now what?
                            }

                            // if signing out, add the id_token_hint
                            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                            {
                                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                                if (idTokenHint != null)
                                {
                                    n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                                }
                            }
                            return Task.FromResult(0);
                        }
                    }
                }
            });
        }
    }
}

我研究了很多事情,但是cookie的价值始终保持不变。我已经考虑过删除旧的cookie,而只是手动构建新的cookie,但这需要以正确的方式对其进行加密,并且闻起来很有趣,肯定不是惯用的方式。

我觉得我必须缺少一些简单的东西。我希望可以使用一种简单的“ UpdateCookie(newToken)”方法,并且已经尝试使用SignIn()和SignOut(),但这些方法对我来说并没有奏效,实际上似乎根本没有与Cookie进行交互。

2 个答案:

答案 0 :(得分:1)

这就是我的工作方式,添加以下几行:

initialize'
C:/Ruby25-x64/lib/ruby/gems/2.5.0/gems/selenium-webdriver-3.141.0/lib/selenium/webdriver/remote/response.rb:69:in

然后在AuthorizationCodeReceived中将其添加到末尾:

<?php
//Debug mode on

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// Fetch session last typed data
session_start();
$_SESSION['name'] = (isset($_POST['name']) ? $_POST['name'] : '');
$_SESSION['phone'] = (isset($_POST['phone']) ? $_POST['phone'] : '');
$_SESSION['gpa'] = (isset($_POST['gpa']) ? $_POST['gpa'] : '');
$_SESSION['type'] = (isset($_POST['type']) ? $_POST['type'] : '');

//Define Errors
$name_err ='Veuillez entrer le nom';
$phone_err = 'Veuillez entrer le numéro de telephone';
$gpa_err = 'Veuillez entrer le gpa';
$type_err ='Veuillez entrer le type';

//Define Placeholders
$name_ph ='name';
$phone_ph = 'Numéro de téléphone';
$gpa_ph = 'Entrez le gpa';
$type_ph ='le type';

// Vars fetch data from form for email and form use

    $name = (isset($_POST['name']) ? $_POST['name'] : '');
    $phone = (isset($_POST['phone']) ? $_POST['phone'] : '');
    $gpa = (isset($_POST['gpa']) ? $_POST['gpa'] : '');
    $type = (isset($_POST['type']) ? $_POST['type'] : '');

// User ip Address
    $ipadd = $_SERVER['REMOTE_ADDR'];

// Include db config file
    require_once "db.php";

    mysql_select_db(DB_NAME); 

//Safe database record and telegram or whatsapp message 
This is where I need you help :)

?>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <br>
    <center><img src="/images/School-Logo.png" alt="school" style="width:123px;height:93px;"></center>
    <link rel="icon" rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
    <title>Récuperation et envoi de donnes scolaires</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css">
    <link rel="stylesheet" href="/css/custom.css">
    <style type="text/css">
        body{ font: 14px sans-serif; }
        .wrapper{ width: 350px; padding: 20px; }
    </style>   
</head>
<body>
   <center> 
        <h1>School system</h1>
        <h3>Collecting Storing data and sending notifications</h3>

        <div class="wrapper">
        <!––  Informations -->
            <div class="studentform">

                <b>
                    Note:
                    <br>
                <ul>

                 <li>Entrez les informations</li>
                 <br>


                </ul>
                </b>

            </div>
            <br>

        <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" id="stud" method="post">

            <div class="form-group <?php echo (!empty($name_err)) ? 'has-error' : ''; ?>">
                <p align="left">Name <?php echo $name; ?> <font color="red">*</font> </p>
                <input type="text" name="name" class="form-control" placeholder="<?php echo $name_ph;?>" value="<?php echo $_SESSION['name'];?>">
                <span class="help-block"><?php echo $name_err; ?></span>
            </div>    
            <div class="form-group <?php echo (!empty($phone_err)) ? 'has-error' : ''; ?>">
                <p align="left">Contact <?php echo $phone; ?> <font color="red">*</font></p>
                <input type="text" name="phone" class="form-control" placeholder="<?php echo $phone_ph;?>" value="<?php echo $_SESSION['phone'];?>" align="left" >
                <span class="help-block"><?php echo $phone_err; ?></span>
            </div>
            <div class="form-group <?php echo (!empty($gpa_err)) ? 'has-error' : ''; ?>">
                <p align="left">GPA <font color="red">*</font></p>
                <input type="text" name="gpa" class="form-control" placeholder="<?php echo $gpa_ph;?>" value="<?php echo $_SESSION['gpa'];?>" align="left">
                <span class="help-block"><?php echo $gpa_err; ?></span>
            </div>


             <div class="form-group <?php echo (!empty($type_err)) ? 'has-error' : ''; ?>">
                <p align="left">C <font color="red">*</font></p>
                <select name="type" form="stud" class="pickupselect" style="width:100%;">
                <option value="bachelor">Bachelor</option>
                <option value="master">Master</option>
                </select>
                <span class="help-block"><?php echo $type_err; ?></span>
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-primary" value="Entrez les informations" align="left">
            </div>
        </form>
    </div>  </center>  
</body>
<footer>
<center>school data 2019</center>
<br>
</footer>
</html>

newIdentity是您的声明身份,希望对您有所帮助。

答案 1 :(得分:0)

我最近遇到了同样的问题,解决方法是:

  1. UseTokenLifetime = false中设置OpenIdConnectAuthenticationOptions用于配置OAuth中间件(否则,会话cookie的生存期将设置为访问令牌的生存期,通常为一小时)
  2. 创建自己的CookieAuthenticationProvider来验证访问令牌的有效期
  3. 令牌过期(或即将过期)时:
    1. 使用刷新令牌获取新的访问令牌(如果MSAL用于OAuth,这是一个简单的IConfidentialClientApplication.AcquireTokenSilent()方法调用)
    2. 使用IIdentity方法使用获取的访问令牌构建一个新的ISecurityTokenValidator.ValidateToken()对象
    3. 用新建的身份替换请求上下文身份
    4. 致电IAuthenticationManager.SignIn(properties, freshIdentity)更新会话cookie

以下是使刷新令牌与OWIN cookie中间件一起使用的完整解决方案:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using EPiServer.Logging;
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;

namespace MyApp
{
    public class OwinStartup
    {
        public void Configuration(IAppBuilder app)
        {
            var openIdConnectOptions = new OpenIdConnectAuthenticationOptions
            {
                UseTokenLifetime = false,
                // ...
            };

            var msalAppBuilder = new MsalAppBuilder();
            var refreshTokenHandler = new RefreshTokenHandler(msalAppBuilder, openIdConnectOptions);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                CookieManager = new SystemWebChunkingCookieManager(),
                Provider = new RefreshTokenCookieAuthenticationProvider(refreshTokenHandler)
            });
        }
    }

    public class RefreshTokenCookieAuthenticationProvider : CookieAuthenticationProvider
    {
        private readonly RefreshTokenHandler _refreshTokenHandler;

        private static readonly ILogger _log = LogManager.GetLogger();

        public RefreshTokenCookieAuthenticationProvider(RefreshTokenHandler refreshTokenHandler)
        {
            _refreshTokenHandler = refreshTokenHandler;
        }

        public override async Task ValidateIdentity(CookieValidateIdentityContext context)
        {
            var exp = context.Identity?.FindFirst("exp")?.Value;

            if (string.IsNullOrEmpty(exp))
            {
                return;
            }

            var utcNow = DateTimeOffset.UtcNow;
            var expiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse(exp));
            var maxMinsBeforeExpires = TimeSpan.FromMinutes(2);

            if (expiresUtc - utcNow >= maxMinsBeforeExpires)
            {
                return;
            }

            try
            {
                var freshIdentity = await _refreshTokenHandler.TryRefreshAccessTokenAsync(context.Identity);

                if (freshIdentity != null)
                {
                    context.ReplaceIdentity(freshIdentity);
                    context.OwinContext.Authentication.SignIn(context.Properties, (ClaimsIdentity) freshIdentity);
                }
                else
                {
                    context.RejectIdentity();
                }
            }
            catch (Exception ex)
            {
                _log.Error("Can't refresh user token", ex);
                context.RejectIdentity();
            }
        }
    }

    public class RefreshTokenHandler
    {
        private readonly MsalAppBuilder _msalAppBuilder;
        private readonly OpenIdConnectAuthenticationOptions _openIdConnectOptions;

        public RefreshTokenHandler(
            MsalAppBuilder msalAppBuilder,
            OpenIdConnectAuthenticationOptions openIdConnectOptions)
        {
            _msalAppBuilder = msalAppBuilder;
            _openIdConnectOptions = openIdConnectOptions;
        }

        public async Task<IIdentity> TryRefreshAccessTokenAsync(IIdentity identity, CancellationToken ct = default)
        {
            try
            {
                var idToken = await GetFreshIdTokenAsync(identity, ct);
                var freshIdentity = await GetFreshIdentityAsync(idToken, ct);

                return freshIdentity;
            }
            catch (MsalUiRequiredException)
            {
                return null;
            }
        }

        private async Task<string> GetFreshIdTokenAsync(IIdentity identity, CancellationToken ct)
        {
            var principal = new ClaimsPrincipal(identity);
            var app = _msalAppBuilder.BuildConfidentialClientApplication(principal);

            var accounts = await app.GetAccountsAsync();
            var result = await app.AcquireTokenSilent(new[] {"openid"}, accounts.FirstOrDefault()).ExecuteAsync(ct);

            return result.IdToken;
        }

        private async Task<IIdentity> GetFreshIdentityAsync(string idToken, CancellationToken ct)
        {
            var validationParameters = await CreateTokenValidationParametersAsync(ct);
            var principal = _openIdConnectOptions.SecurityTokenValidator.ValidateToken(idToken, validationParameters, out _);
            var identity = (ClaimsIdentity) principal.Identity;

            return identity;
        }

        // This is additional code for cases with multiple issuers - can be skipped if this configuration is static
        private async Task<TokenValidationParameters> CreateTokenValidationParametersAsync(CancellationToken ct)
        {
            var validationParameters = _openIdConnectOptions.TokenValidationParameters.Clone();
            var configuration = await _openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(ct);

            validationParameters.ValidIssuers = (validationParameters.ValidIssuers ?? new string[0])
                .Union(new[] {configuration.Issuer})
                .ToList();
            validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys ?? new SecurityKey[0])
                .Union(configuration.SigningKeys)
                .ToList();

            return validationParameters;
        }
    }

    // From official samples: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/blob/master/TaskWebApp/Utils/MsalAppBuilder.cs
    public class MsalAppBuilder
    {
        public IConfidentialClientApplication BuildConfidentialClientApplication(ClaimsPrincipal currentUser)
        {
            // ...
        }
    }
}