#c# #asp.net-identity #identityserver4 #asp.net-core-2.1
#c# #asp.net-идентификатор #identityserver4 #asp.net-core-2.1
Вопрос:
Я изучал IdentityServer4
в течение недели и успешно внедрил простой поток аутентификации с ResourceOwnedPassword
помощью flow.
Теперь я внедряю аутентификацию Google с помощью IdentityServer4, следуя этому руководству
Это то, что я делаю:
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;
});
//...
}
** Контроллер Google (который предназначен для обработки возвращаемого токена из Google **
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, и регистрировать их, если у них нет учетных записей.
Мой вопрос: как я могу использовать утверждения Google OAuth2, которые я получаю в HandleExternalLogin
методе, для генерации ссылочного токена, сохранения его в таблице PersistedGrants и возврата клиенту.
Это означает, что при доступе пользователя https://localhost:44386/api/google/login
, после перенаправления на экран согласия Google, они могут получить access_token
, refresh_token
который был сгенерирован IdentityServer4
.
Спасибо,
Ответ №1:
- В IdentityServer тип (jwt ссылки) токена настраивается для каждого клиента (приложения), запросившего токен.
AccessTokenType.Reference
допустимо дляTokenTypes.AccessToken
неTokenTypes.IdentityToken
как в вашем фрагменте.
В общем, было бы проще следовать оригинальному quickstart, а затем расширить общий код в соответствии с вашими потребностями. То, что я вижу сейчас во фрагменте выше, — это только ваши конкретные данные, а не часть по умолчанию, отвечающая за создание сеанса IdSrv и перенаправление обратно клиенту.
Если вы все еще хотите создать токен вручную:
- внедрите
ITokenService
в ваш контроллер. - исправьте ошибку, о которой я упоминал выше:
TokenTypes.AccessToken
вместоTokenTypes.IdentityToken
- вызов
var tokenHandle = await TokenService.CreateAccessTokenAsync(token);
tokenHandle
является ли ключ в PersistedGrantStore
Комментарии:
1. Я не понимаю вашей идеи. Я пытался реализовать пользовательский интерфейс IdentityServer4 , но они используют
in-memory
хранилище пользователей. Очень неприятно снова и снова читать код и находить способ его настройки из-за отсутствия подробного руководства. Теперь я просто хочу знать, могу ли я сгенерироватьreference token
вручную и сохранить вpersistedgrantstore
или нет.2. Ну, на самом деле вы спросили немного о другом. Вы спросили, как объединить внешнюю аутентификацию с интерактивным потоком с некоторой неинтерактивной логикой, и мой совет — избегать этого. Смотрите: вы не получаете никакой выгоды от концепции RO, поскольку все равно перенаправляете своего пользователя в Google. Таким образом, вам было бы проще установить для своего приложения тот же тип гранта, который вы уже определили для Google. Затем вы просто устанавливаете
AccessTokenType.Reference
в своем определении клиента, и все, дальнейшее кодирование не требуется.3. И что касается
in-memory
пользовательского хранилища: вы можете свободно переключить его в DI на то, которое основано на Identity, или на ваше собственное. Без перезаписи общей логики.4. Добавлена строка кода, которую вы, скорее всего, ищете.
CreateAccessTokenAsync(token);
ведет себя в зависимости отtoken.Type
иtoken.AccessTokenType
. В случае, если они являютсяOidcConstants.TokenTypes.AccessToken
иAccessTokenType.Reference
, он сохраняет токен.5. Извините за мой поздний ответ. Позвольте мне попробовать