oauth-2.0 - 最少特权的身份服务器在 Azure 上无法正常工作

标签 oauth-2.0 azure-web-app-service identityserver3 least-privilege

我正在尝试实现遵循 OAUTH2/OIDC 协议(protocol)的架构。为了做到这一点,我有 STS(Identity Server v3 by leastprivilege)、ASP.NET WebApi 和 ASP.NET MVC 客户端应用程序。我的目标是将 STS 和 REST 服务托管在 Azure 上,以便不同的客户端可以将它们用作公共(public)服务。到目前为止,一切都很好。在我决定添加一个使用其中一种重定向流程(授权代码流程)的新客户端之前,一切似乎都运行顺利且完美。我想利用它提供的刷新 token 选项。我想为该客户提供短期访问 token (10 分钟),并让他使用刷新 token 来获取新 token 。这就是它在代码中的样子:

STS:

new Client 
{
    ClientId = "tripgalleryauthcode",
    ClientName = "Trip Gallery (Authorization Code)",
    Flow = Flows.AuthorizationCode, 
    AllowAccessToAllScopes = true,
    RequireConsent = false,

    RedirectUris = new List<string>
    {
        Tripgallery.Constants.TripgalleryMvcAuthCodePostLogoutCallback
    },           

    ClientSecrets = new List<Secret>()
    {
        new Secret(Tripgallery.Constants.TripgalleryClientSecret.Sha256())
    },

    // refresh token options
    AccessTokenType = AccessTokenType.Jwt,
    AccessTokenLifetime = 600,
    RefreshTokenUsage = TokenUsage.OneTimeOnly, // Every time generates new refresh token. Not only access token.
    RefreshTokenExpiration = TokenExpiration.Sliding,
    SlidingRefreshTokenLifetime = 1296000,

    PostLogoutRedirectUris = new List<string>()
    {
        Tripgallery.Constants.TripgalleryPostLogoutCallback
    }
}

Mvc 应用程序(客户端):

private ObjectCache _cache;
private readonly string tokensCacheKey = "Tokens";

public HomeController()
{
    _cache = MemoryCache.Default;
}

// GET: Home
public ActionResult Index()
{
    var authorizeRequest = new AuthorizeRequest(Constants.BoongalooSTSAuthorizationEndpoint);

    var state = HttpContext.Request.Url.OriginalString;

    var url = authorizeRequest.CreateAuthorizeUrl(
    "tripgalleryauthcode",
    "code",
    "openid profile address tripgallerymanagement offline_access",
    Constants.TripgalleryMvcAuthCodePostLogoutCallback,
    state);

    HttpContext.Response.Redirect(url);
    return null;
}

public async Task<ActionResult> StsCallBackForAuthCodeClient()
{
    var authCode = Request.QueryString["code"];

    var client = new TokenClient(
    Constants.TripgallerySTSTokenEndpoint,
    "tripgalleryauthcode",
    Constants.TripgalleryClientSecret
    );

    var tokenResponse = await client.RequestAuthorizationCodeAsync(
    authCode,
    Constants.TripgalleryMvcAuthCodePostLogoutCallback
    );

    this._cache[this.tokensCacheKey] = new TokenModel()
    {
        AccessToken = tokenResponse.AccessToken,
        IdToken = tokenResponse.IdentityToken,
        RefreshToken = tokenResponse.RefreshToken,
        AccessTokenExpiresAt = DateTime.Parse(DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToString(CultureInfo.InvariantCulture))
    };

    return View();
}

public ActionResult StartCallingWebApi()
{
    var timer = new Timer(async (e) =>
    {
        var cachedStuff = this._cache.Get(this.tokensCacheKey) as TokenModel;
        await ExecuteWebApiCall(cachedStuff);
    }, null, 0, Convert.ToInt32(TimeSpan.FromMinutes(20).TotalMilliseconds));

    return null;
}

private async Task ExecuteWebApiCall(TokenModel cachedStuff)
{
    // Ensure that access token expires in more than one minute
    if (cachedStuff != null && cachedStuff.AccessTokenExpiresAt > DateTime.Now.AddMinutes(1))
    {
        await MakeValidApiCall(cachedStuff);
    }
    else
    {
        // Use the refresh token to get a new access token, id token and refresh token
        var client = new TokenClient(
            Constants.TripgallerySTSTokenEndpoint,
            "tripgalleryauthcode",
            Constants.TripgalleryClientSecret
        );

        if (cachedStuff != null)
        {
            var newTokens = await client.RequestRefreshTokenAsync(cachedStuff.RefreshToken);

            var value = new TokenModel()
            {
                AccessToken = newTokens.AccessToken,
                IdToken = newTokens.IdentityToken,
                RefreshToken = newTokens.RefreshToken,
                AccessTokenExpiresAt =
                    DateTime.Parse(
                        DateTime.Now.AddSeconds(newTokens.ExpiresIn).ToString(CultureInfo.InvariantCulture))
            };

            this._cache.Set(this.tokensCacheKey, (object)value, new CacheItemPolicy());

            await MakeValidApiCall(value);
        }
    }
}

问题是,如果我将 STS 托管在 Azure 上,出于某种原因,如果我决定在访问 token 过期 20 分钟或更长时间后使用刷新 token ,我会收到错误消息。不管我的刷新 token 生命周期是 15 天。

enter image description here

也就是STS产生的日志:

w3wp.exe Warning: 0 : 2017-04-06 12:01:21.456 +00:00 [Warning] AuthorizationCodeStore not configured - falling back to InMemory
w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] TokenHandleStore not configured - falling back to InMemory
w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] ConsentStore not configured - falling back to InMemory
w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] RefreshTokenStore not configured - falling back to InMemory
w3wp.exe Information: 0 : 2017-04-06 12:01:22.371 +00:00 [Information] Start token request
w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Client secret id found: "tripgalleryauthcode"
w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Client validation success
w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Start token request validation
w3wp.exe Information: 0 : 2017-04-06 12:01:22.433 +00:00 [Information] Start validation of refresh token request
w3wp.exe Warning: 0 : 2017-04-06 12:01:22.574 +00:00 [Warning] "Refresh token is invalid"
 "{
  \"ClientId\": \"tripgalleryauthcode\",
  \"ClientName\": \"Trip Gallery (Authorization Code)\",
  \"GrantType\": \"refresh_token\",
  \"RefreshToken\": \"140cfb19405a6a4cbace29646751194a\",
  \"Raw\": {
    \"grant_type\": \"refresh_token\",
    \"refresh_token\": \"140cfb19405a6a4cbace29646751194a\"
  }
}"
w3wp.exe Information: 0 : 2017-04-06 12:01:22.590 +00:00 [Information] End token request
w3wp.exe Information: 0 : 2017-04-06 12:01:22.590 +00:00 [Information] Returning error: invalid_grant
w3wp.exe Information: 0 : 2017-04-06 12:01:29.465 +00:00 [Information] Start discovery request
w3wp.exe Information: 0 : 2017-04-06 12:01:29.512 +00:00 [Information] Start key discovery request

与在我的本地计算机上运行的 STS 相同的情况按预期工作。我可以使用刷新 token 获取新 token 。

enter image description here

已解决: 问题确实是 Fred Han - MSFT 指出的。我需要为我的刷新 token 实现持久存储。实现它真的很容易。我是这样做的:

身份服务器的Startup.cs:

var idServerServiceFactory = new IdentityServerServiceFactory()
                                .UseInMemoryClients(Clients.Get())
                                .UseInMemoryScopes(Scopes.Get());

//...

// use custom service for tokens maintainance
var customRefreshTokenStore = new CustomRefreshTokenStore();
idServerServiceFactory.RefreshTokenStore = new Registration<IRefreshTokenStore>(resolver => customRefreshTokenStore);

var options = new IdentityServerOptions
{
    Factory = idServerServiceFactory,

    // .....

}

idsrvApp.UseIdentityServer(options);

CustomRefreshTokenStore.cs

public class CustomRefreshTokenStore : IRefreshTokenStore
{
    public Task StoreAsync(string key, RefreshToken value)
    {
        // code that uses persitant storage mechanism
    }

    public Task<RefreshToken> GetAsync(string key)
    {
        // code that uses persitant storage mechanism
    }

    public Task RemoveAsync(string key)
    {
        // code that uses persitant storage mechanism
    }

    public Task<IEnumerable<ITokenMetadata>> GetAllAsync(string subject)
    {
        // code that uses persitant storage mechanism
    }

    public Task RevokeAsync(string subject, string client)
    {
        // code that uses persitant storage mechanism
    }
}

最佳答案

w3wp.exe Warning: 0 : 2017-04-06 12:01:21.456 +00:00 [Warning] AuthorizationCodeStore not configured - falling back to InMemory

w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] TokenHandleStore not configured - falling back to InMemory

w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] ConsentStore not configured - falling back to InMemory

w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] RefreshTokenStore not configured - falling back to InMemory

您似乎在内存中存储/维护数据,如果您将其托管在负载均衡器后面的多实例的 Azure 网站上,这可能是问题的原因。您可以尝试将数据存储在其他数据存储中,而不是内存存储中。

_cache = MemoryCache.Default;

此外,您在 Web API 应用程序中通过内存存储和检索 tokensCacheKey,这在 Azure 多实例网络场环境中无法正常工作。请将数据存储在外部存储中,例如 Azure 存储、数据库或 Redis 缓存。

关于oauth-2.0 - 最少特权的身份服务器在 Azure 上无法正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43249912/

相关文章:

Azure 无缘无故强制使用 https?

Sitecore 9 联合身份验证与 IdentityServer3,无限循环

c# - ASP.Net Core 1.1 MVC 应用程序中的 401 未经 IdentityServer3 授权

oauth - Google OAuth2 用于托管在 NAT 后面的 Web 应用程序(没有公共(public) IP 的内网服务器)

java - 如何检查用户是否已从 Google/Facebook/GitHub 等注销 - OAuth2

angular - 使用 angular 2+ 的社交登录时,信息以何种方式在组件之间传递?

azure - Azure Web 应用程序部署期间的 "Error: could not find a part of the path"

Azure应用服务: Select the endpoint based on HTTP header value

c# - 当 # 提供而不是时,如何在 C# 中分离查询字符串?

python - 使用 python 对 GCP 计算 API 端点进行身份验证