【问题标题】:Generate access token with IdentityServer4 without password使用 IdentityServer4 生成访问令牌,无需密码
【发布时间】:2017-10-25 14:45:35
【问题描述】:

我使用 ROPC 流创建了受 IdentityServer4 保护的 ASP.NET Core WebApi(使用此示例:https://github.com/robisim74/AngularSPAWebAPI)。

如何在没有密码的情况下从服务器手动生成access_token?

【问题讨论】:

  • “没有密码”是什么意思?资源所有者 密码 凭证流程要求客户端提供用户的用户名和密码才能获得访问令牌。
  • 是的!但我想在服务器上生成令牌,而不是在客户端上
  • 我想替代 /connect/token 端点,以便管理员用户可以在不知道密码的情况下生成其他用户的 access_token
  • 听起来你想要一个模拟功能。我们已经实现了这样的事情,但通过隐式/混合流程,这只是登录过程中的另一个步骤。有关模拟的权限存储在 idsrv4 数据库中。通过自定义 API 生成带有您喜欢的任何声明的签名 JWT 也很容易,但我建议避免使用除隐式或混合之外的任何内容进行最终用户身份验证。我们仅将资源所有者密码用于旧版客户端支持。
  • 我的客户是SPA。我想让超级管理员以用户身份登录并查看他们的问题并通过他们的眼睛查看站点问题

标签: c# asp.net-core identityserver4


【解决方案1】:
[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS, 
    [FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory,
    [FromServices] IdentityServerOptions options)
{
    var Request = new TokenCreationRequest();                        
    var User = await userManager.FindByIdAsync(id.ToString());
    var IdentityPricipal = await principalFactory.CreateAsync(User);
    var IdServerPrincipal = IdentityServerPrincipal.Create(User.Id.ToString(), User.UserName, IdentityPricipal.Claims.ToArray());

    Request.Subject = IdServerPrincipal;
    Request.IncludeAllIdentityClaims = true;
    Request.ValidatedRequest = new ValidatedRequest();
    Request.ValidatedRequest.Subject = Request.Subject;
    Request.ValidatedRequest.SetClient(Config.GetClients().First());
    Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
    Request.ValidatedRequest.Options = options;
    Request.ValidatedRequest.ClientClaims = IdServerPrincipal.Claims.ToArray();

    var Token = await TS.CreateAccessTokenAsync(Request);
    Token.Issuer = "http://" + HttpContext.Request.Host.Value;

    var TokenValue = await TS.CreateSecurityTokenAsync(Token);
    return Ok(TokenValue);
}

对于新发布的 IdentityServer 2.0.0,代码需要一些修改:

[HttpPost("loginas/{id}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> LoginAs(int id, [FromServices] ITokenService TS, 
    [FromServices] IUserClaimsPrincipalFactory<ApplicationUser> principalFactory, 
    [FromServices] IdentityServerOptions options)
{
    var Request = new TokenCreationRequest();
    var User = await userManager.FindByIdAsync(id.ToString());
    var IdentityPricipal = await principalFactory.CreateAsync(User);
    var IdentityUser = new IdentityServerUser(User.Id.ToString());
    IdentityUser.AdditionalClaims = IdentityPricipal.Claims.ToArray();
    IdentityUser.DisplayName = User.UserName;
    IdentityUser.AuthenticationTime = System.DateTime.UtcNow;
    IdentityUser.IdentityProvider = IdentityServerConstants.LocalIdentityProvider;
    Request.Subject = IdentityUser.CreatePrincipal();
    Request.IncludeAllIdentityClaims = true;
    Request.ValidatedRequest = new ValidatedRequest();
    Request.ValidatedRequest.Subject = Request.Subject;
    Request.ValidatedRequest.SetClient(Config.GetClients().First());
    Request.Resources = new Resources(Config.GetIdentityResources(), Config.GetApiResources());
    Request.ValidatedRequest.Options = options;
    Request.ValidatedRequest.ClientClaims = IdentityUser.AdditionalClaims;
    var Token = await TS.CreateAccessTokenAsync(Request);
    Token.Issuer = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value;
    var TokenValue = await TS.CreateSecurityTokenAsync(Token);
    return Ok(TokenValue);
}

【讨论】:

  • 能否请您为这段代码提供一些上下文?它在哪里——在 IdentityServer 主机中,还是在外部应用程序中?看起来您也在为您的用户存储使用 ASPNET Core Identity。
  • 我在同一个应用程序中有一个 api 和 IdentityServer,而 LoginAs 是其控制器之一的一部分。如果我拆分 api 和 auth 部分,那么 LoginAs 将在 auth 部分中
  • 介意发布你的创业公司,我在这个 [FromServices] 上有问题
  • 我的层级很大,有什么依赖不能解决的告诉我一下?
  • 如果 auth 部分从 api 部分中分离出来,该请求的授权选项是什么?
【解决方案2】:

使用这个:
http://docs.identityserver.io/en/latest/topics/tools.html

使用身份服务器自带的这个工具:
在构造函数中声明,通过依赖注入接收。
IdentityServer4.IdentityServerTools _identityServerTools

var issuer = "http://" + httpRequest.Host.Value; var token = await _identityServerTools.IssueJwtAsync( 30000, 发行人, 新 System.Security.Claims.Claim[1] { 新 System.Security.Claims.Claim("cpf", cpf) } );

【讨论】:

  • 好主意!但它有一点限制:它不能生成 RefreshToken 并且需要从用户及其角色手动生成声明
  • 如何手动验证和解码这个令牌?
【解决方案3】:

这是实现此目的的另一种方法:

首先创建一个名为 loginBy 的自定义授权

    public class LoginByGrant : ICustomGrantValidator
    {
        private readonly ApplicationUserManager _userManager;

        public string GrantType => "loginBy";

        public LoginByGrant(ApplicationUserManager userManager)
        {
            _userManager = userManager;
        }     

        public async Task<CustomGrantValidationResult> ValidateAsync(ValidatedTokenRequest request)
        {

            var userId = Guid.Parse(request.Raw.Get("user_id"));

            var user = await _userManager.FindByIdAsync(userId);

            if (user == null)
                return await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult("user not exist"));

            var userClaims = await _userManager.GetClaimsAsync(user.Id);

            return
                await Task.FromResult<CustomGrantValidationResult>(new CustomGrantValidationResult(user.Id.ToString(), "custom", userClaims));

        }
    }

然后在身份启动类中添加此自定义授权

    factory.CustomGrantValidators.Add(
                        new Registration<ICustomGrantValidator>(resolver => new LoginByGrant(ApplicaionUserManager)));

最后在你的 api 中

      public async Task<IHttpActionResult> LoginBy(Guid userId)
       {
        var tokenClient = new TokenClient(Constants.TokenEndPoint, Constants.ClientId, Constants.Secret);

        var payload = new { user_id = userId.ToString() };

        var result = await tokenClient.RequestCustomGrantAsync("loginBy", "customScope", payload);

        if (result.IsError)
            return Ok(result.Json);

        return Ok(new { access_token = result.AccessToken, expires_in = result.ExpiresIn});
       }

【讨论】:

  • 它是与 IdentityServer4 一起使用还是仅适用于 IdentityServer3?
  • identityServer4 也支持 ICustomGrantValidator 所以应该很相似
  • 嗨,@xray。感谢您采用这种方法。但是,想问一下您是否曾经在 localhost 上遇到过错误“unsupported_grant_type”?
  • @xray 看起来 IdentityServer4 不支持 ICustomGrantValidator:docs.identityserver.io/en/dev/…,我认为在 IdentityServer4 中你必须使用 IExtensionGrantValidator
  • 有没有办法生成刷新令牌以及更新访问令牌?
【解决方案4】:

关于我对您最初问题的评论。在隐式/混合流中实现模拟功能。如果用户被确定为“超级管理员”,则在身份验证后向他们提供一个额外的步骤,让他们输入/选择他们希望模拟的帐户。完成后,只需在身份服务器上将会话建立为选定用户(并可能存储其他声明,表明它是模拟会话以及谁在进行模拟)。然后,任何令牌都将像您是该用户一样发出,而且所有人都无需知道密码。

此外,如果您希望自己创建令牌,请查看 IdSrv4 提供的 ITokenCreationService。您可以将其注入您自己的控制器/服务/任何内容中,并使用 CreateTokenAsync(Token token) 生成带有您喜欢的任何声明的签名 JWT。

【讨论】:

  • 谢谢!我已经看过 ITokenCreationService 了,你能给我举个例子吗?
  • 它只有一个方法,接受一个 IdentityServer4.Models.Token 对象并返回编码字符串。
  • 其实我怀疑我需要使用 TokenCreationRequest 而我不知道如何从 IdentityUser 获取 ClaimsPrincipal
【解决方案5】:

回答有点晚了。

在我的Generating Access Token Without Password 的情况下,还有另一个identity server 作为组织sso,我们的实现已经使用IdentityServer,所以我们需要从第二个IdentityServer 获取用户令牌(在用户登录并重定向到我们的app),提取sub,检查它是否已经存在(如果没有插入我们本地的IdentityServer),最后选择用户并使用新授权为用户获取令牌。 您的客户应该将此granttype 作为允许的授予类型(此处为userexchange):

请参阅:identity server docsduende docs 了解更多信息

    public class TokenExchangeGrantValidator : IExtensionGrantValidator {

        protected readonly UserManager<ToranjApplicationUser> _userManager;
        private readonly IEventService _events;

        public TokenExchangeGrantValidator(ITokenValidator validator, IHttpContextAccessor httpContextAccessor, UserManager<ToranjApplicationUser> userManager
            , IEventService events) {
            _userManager = userManager;
            _events = events;
        }


        public async Task ValidateAsync(ExtensionGrantValidationContext context) {
            var userName = context.Request.Raw.Get("uname");

            if (string.IsNullOrEmpty(userName)) {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
                return;
            }

            var user = await _userManager.FindByNameAsync(userName);
            // or use this one, if you are sending userId
            //var user = await _userManager.FindByIdAsync(userId);
            if (null == user) {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
                return;
            }

            await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName, false, context.Request.ClientId));
            var customResponse = new Dictionary<string, object>
                {
                {OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken}
            };
            context.Result = new GrantValidationResult(
                subject: user.Id.ToString(),
                authenticationMethod: GrantType,
                customResponse: customResponse);
        }

        public string GrantType => "userexchange";
    }

startupConfigureServices 之后var builder = services.AddIdentityServer(...) 添加您新创建的类。

    builder.AddExtensionGrantValidator<TokenExchangeGrantValidator>();

调用它来获取令牌很简单:

POST /connect/token

grant_type=userexchange&
scope=yourapi&
uname=yourusername&
client_id=yourClientId
client_secret=secret

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-06
    • 2019-12-25
    • 2021-02-19
    • 2018-10-26
    • 2021-09-25
    • 1970-01-01
    • 2018-09-23
    相关资源
    最近更新 更多