手动生成IdentityServer4参考令牌并保存到PersistedGrants表中。

3

我学习了一周的IdentityServer4并成功实现了使用 ResourceOwnedPassword 流程的简单身份验证流程。

现在,我正在按照 这篇教程 实现使用 IdentityServer4 进行 Google 身份验证。

这是我的操作步骤:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    //...

    const string connectionString = @"Data Source=.\SQLEXPRESS;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;";
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddProfileService<IdentityServerProfileService>()
        .AddResourceOwnerValidator<IdentityResourceOwnerPasswordValidator>()
        // this adds the config data from DB (clients, resources)
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder =>
            {
                builder.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            };

        })
        // this adds the operational data from DB (codes, tokens, consents)
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            // this enables automatic token cleanup. this is optional.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 30;
        });

    // Add jwt validation.
    services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddIdentityServerAuthentication(options =>
        {
            // base-address of your identityserver
            options.Authority = "https://localhost:44386";

            options.ClaimsIssuer = "https://localhost:44386";

            // name of the API resource
            options.ApiName = "api1";
            options.ApiSecret = "secret";

            options.RequireHttpsMetadata = false;

            options.SupportedTokens = SupportedTokens.Reference;

        });

    //...
}

** 谷歌控制器(用于处理从谷歌返回的令牌)**

public class GLoginController : Controller
    {
        #region Properties

        private readonly IPersistedGrantStore _persistedGrantStore;

        private readonly IUserFactory _userFactory;

        private readonly IBaseTimeService _baseTimeService;

        private readonly ITokenCreationService _tokenCreationService;

        private readonly IReferenceTokenStore _referenceTokenStore;

        private readonly IBaseEncryptionService _baseEncryptionService;

        #endregion

        #region Constructor

        public GLoginController(IPersistedGrantStore persistedGrantStore,
            IBaseTimeService basetimeService,
            ITokenCreationService tokenCreationService,
            IReferenceTokenStore referenceTokenStore,
            IBaseEncryptionService baseEncryptionService,
            IUserFactory userFactory)
        {
            _persistedGrantStore = persistedGrantStore;
            _baseTimeService = basetimeService;
            _userFactory = userFactory;
            _tokenCreationService = tokenCreationService;
            _referenceTokenStore = referenceTokenStore;
            _baseEncryptionService = baseEncryptionService;
        }

        #endregion

        #region Methods

        [HttpGet("login")]
        [AllowAnonymous]
        public IActionResult Login()
        {
            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = "/api/google/handle-external-login"
            };

            return Challenge(authenticationProperties, "Google");
        }

        [HttpGet("handle-external-login")]
        //[Authorize("ExternalCookie")]
        [AllowAnonymous]
        public async Task<IActionResult> HandleExternalLogin()
        {
            //Here we can retrieve the claims
            var authenticationResult = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var principal = authenticationResult.Principal;

            var emailAddress = principal.FindFirst(ClaimTypes.Email)?.Value;
            if (string.IsNullOrEmpty(emailAddress))
                return NotFound(new ApiMessageViewModel("Email is not found"));

            // Find user by using username.
            var loadUserConditions = new LoadUserModel();
            loadUserConditions.Usernames = new HashSet<string> { emailAddress };
            loadUserConditions.Pagination = new PaginationValueObject(1, 1);

            // Find users asynchronously.
            var loadUsersResult = await _userFactory.FindUsersAsync(loadUserConditions);
            var user = loadUsersResult.FirstOrDefault();

            // User is not defined.
            if (user == null)
            {
                user = new User(Guid.NewGuid(), emailAddress);
                user.Email = emailAddress;
                user.HashedPassword = _baseEncryptionService.Md5Hash("abcde12345-");
                user.JoinedTime = _baseTimeService.DateTimeUtcToUnix(DateTime.UtcNow);
                user.Kind = UserKinds.Google;
                user.Status = UserStatuses.Active;

                //await _userFactory.AddUserAsync(user);
            }
            else
            {
                // User is not google account.
                if (user.Kind != UserKinds.Google)
                    return Forbid("User is not allowed to access system.");
            }

            var token = new Token(IdentityServerConstants.TokenTypes.IdentityToken);
            var userCredential = new UserCredential(user);

            token.Claims = userCredential.GetClaims();
            token.AccessTokenType = AccessTokenType.Reference;
            token.ClientId = "ro.client";
            token.CreationTime = DateTime.UtcNow;
            token.Audiences = new[] {"api1"};
            token.Lifetime = 3600;


            return Ok();
        }

        #endregion
    }

一切进展顺利,我可以从Google OAuth2获取认证信息,使用Google电子邮件地址在数据库中查找用户,如果他们没有任何账户,那么就为他们注册。

我的问题是:我如何在HandleExternalLogin方法中使用Google OAuth2的认证信息来生成参考令牌,将其保存到PersistedGrants表中并返回给客户端。

这意味着当用户访问https://localhost:44386/api/google/login时,在被重定向到Google同意屏幕后,他们可以接收由IdentityServer4生成的access_tokenrefresh_token

谢谢。

1个回答

1
在IdentityServer中,令牌的类型(jwt还是引用)可由请求令牌的每个客户端(应用程序)进行配置链接1AccessTokenType.Reference对于TokenTypes.AccessToken有效,而不是像您的片段中那样对于TokenTypes.IdentityToken有效。
通常最好按照原始快速入门指南并将通用代码扩展到您的需求。我现在可以看到的只是您的特定内容,而不是负责创建IdSrv会话并重定向回客户端的默认部分。
如果您仍然希望手动创建令牌:
  • ITokenService 注入到您的控制器中。
  • 修复我上面提到的错误:使用 TokenTypes.AccessToken 而不是 TokenTypes.IdentityToken
  • 调用 var tokenHandle = await TokenService.CreateAccessTokenAsync(token);

tokenHandlePersistedGrantStore 中的一个关键字。


我不理解你的想法。我尝试实现IdentityServer4 UI,但他们使用了in-memory用户存储。由于缺乏详细的指南,反复阅读代码并找到自定义它的方法非常令人沮丧。现在,我只想知道是否可以手动生成reference token并保存到persistedgrantstore中。 - Redplane
其实,你问的是有点不同的事情。你问如何将外部认证与交互式流程和一些非交互逻辑结合起来,我的建议是避免这样做。看看:你并没有得到RO概念的任何好处,因为你无论如何都会将用户重定向到Google。所以,对于你来说,将应用程序设置为与你已经为Google定义的相同授权类型会更简单。然后,在客户端定义中设置AccessTokenType.Reference,就可以了,不需要进一步编码。 - d_f
关于in-memory用户存储:您可以自由切换为基于 Identity 或自己的 DI 存储,而无需超出或重写总体逻辑。 - d_f
添加了你最有可能在寻找的代码行:CreateAccessTokenAsync(token);,其行为取决于 token.Typetoken.AccessTokenType。如果它们是 OidcConstants.TokenTypes.AccessTokenAccessTokenType.Reference,则会存储该令牌。 - d_f
抱歉回复晚了。让我试一试。 - Redplane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接