Главная » Хабрахабр » OAuth Authorization Provider для asp net core

OAuth Authorization Provider для asp net core

Начал изучать asp.net core и первое что пытался найти это некое подобие «OAuthAuthorizationServerProvider» для реализации генерации тикета и «IAuthenticationTokenProvider» для реализаций рефреш токена как в обычном asp.net, но не нашел. Не исключено, что плохо искал, и может появится коммент типа «вот обкатанная библиотека для этого дела».

Хорошо, что можно довольно просто написать свой Middleware для обработки запросов, и связать его с некоторой реализацией провайдера через «сервисы» в ConfigureServices.

Итак, мне нужно что бы были методы для получения токена по логину и паролю по адресу "/token" и метод по получению токена по рефреш-токену.

public interface IOAuthProvider

То есть в конечном итоге реализовав один этот интерфейс и связав его в ConfigureServices авторизация должна работать.

В параметре «OAuthProviderContext» будут храниться данные контекста для авторизации:

public class OAuthProviderContext { public bool HasError { get; private set; } public string Error { get; private set; } public string AccessToken { get; private set; } public string RefreshToken { get; set; } public string ClientId { get; set; } public string Username { get; set; } public string Password { get; set; } public void SetError(string error) { Error = error; HasError = true; } public void SetToken(string access_token, string refresh_token) { AccessToken = access_token; RefreshToken = refresh_token; } }

Теперь надо сделать middleware, которое будет работать с будущими реализациями IOAuthProvider:

class OAuthProviderMiddleware { RequestDelegate _next; IOAuthProvider _oAuthProvider; public OAuthProviderMiddleware(RequestDelegate next, IOAuthProvider oAuthProvider) { _next = next; _oAuthProvider = oAuthProvider; } public async Task Invoke(HttpContext context) { OAuthProviderContext _oAuthProviderContext; string path = context.Request.Path.Value.ToLower().Trim(); bool isPost = context.Request.Method.ToLower() == "post"; if (path == "/token" && isPost) { var form = context.Request.Form; if (!form.ContainsKey("grant_type")) { await context.BadRequest("invalid grant_type"); return; } if (!form.ContainsKey("client_id")) { await context.BadRequest("invalid client_id"); return; } string grant_type = form["grant_type"]; string client_id = form["client_id"]; switch (grant_type) { case "password": { if (!form.ContainsKey("username")) { await context.BadRequest("invalid username"); return; } if (!form.ContainsKey("password")) { await context.BadRequest("invalid password"); return; } string username = form["username"]; string password = form["password"]; _oAuthProviderContext = new OAuthProviderContext() { ClientId = client_id, Username = username, Password = password }; await _oAuthProvider.ByPassword(_oAuthProviderContext); if (_oAuthProviderContext.HasError) { await context.BadRequest(_oAuthProviderContext.Error); return; } else { await context.WriteToken(_oAuthProviderContext); return; } }; case "refresh_token": { if (!form.ContainsKey("refresh_token")) { await context.BadRequest("invalid refresh_token"); return; } string refresh_token = form["refresh_token"]; _oAuthProviderContext = new OAuthProviderContext() { ClientId = client_id, RefreshToken = refresh_token }; await _oAuthProvider.ByRefreshToken(_oAuthProviderContext); if (_oAuthProviderContext.HasError) { await context.BadRequest(_oAuthProviderContext.Error); return; } else { await context.WriteToken(_oAuthProviderContext); return; } }; default: { await context.BadRequest("invalid grant_type"); return; }; } } else { await _next.Invoke(context); } } } public static class OAuthExtensions { public static IApplicationBuilder UseOAuth(this IApplicationBuilder builder) { return builder.UseMiddleware<OAuthProviderMiddleware>(); } internal static async Task BadRequest(this HttpContext context, string Error) { context.Response.StatusCode = 400; await context.Response.WriteAsync(Error); } internal static async Task WriteToken(this HttpContext context, OAuthProviderContext _oAuthProviderContext) { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(new { access_token = _oAuthProviderContext.AccessToken, refresh_token = _oAuthProviderContext.RefreshToken })); } }

В Configure потом можно будет вызвать обертку

app.UseOAuth();

Далее остается написать конкретную реализацию IOAuthProvider.

В конце еще будет ссылка на код. Токен будет в виде JWT, а рефреш-токен рандомный byte[] массив длиной 100 представленный как Base64.

public class OAuthProviderImplement : IOAuthProvider { IServiceProvider _services; IOptions<AuthOptions> _authOptions = null; Helper _helper = null; public OAuthProviderImplement(IServiceProvider services, IOptions<AuthOptions> authOptions, Helper helper) { _services = services; _authOptions = authOptions; _helper = helper; } public async Task ByPassword(OAuthProviderContext context) { ClaimsIdentity identity = await GetIdentity(context.Username, context.ClientId, context.Password); if (identity == null) { context.SetError("User not found"); return; } string encodedJwt = CreateJWT(identity); string refresh_token = await CreateRefreshToken(context.ClientId, identity); if (refresh_token == null) { context.SetError("Error while create refresh token"); return; } context.SetToken(encodedJwt, refresh_token); return; } public async Task ByRefreshToken(OAuthProviderContext context) { ProtectedTicket protectedTicket = await GrantRefreshToken(context.RefreshToken); if (protectedTicket == null) { context.SetError("Invalid refresh token"); return; } if (protectedTicket.clientid != context.ClientId) { context.SetError("Invalid client id"); return; } ClaimsIdentity identity = await GetIdentity(protectedTicket.username, protectedTicket.clientid); if (identity == null) { context.SetError("User not found"); return; } string encodedJwt = CreateJWT(identity); context.SetToken(encodedJwt, context.RefreshToken); return; } string CreateJWT(ClaimsIdentity identity) { var now = DateTime.UtcNow; // создаем JWT-токен var jwt = new JwtSecurityToken( issuer: _authOptions.Value.Issuer, audience: _authOptions.Value.Audience, notBefore: now, claims: identity.Claims, expires: now.Add(TimeSpan.FromSeconds(_authOptions.Value.LifetimeSeconds)), signingCredentials: new SigningCredentials(_authOptions.Value.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); return encodedJwt; } async Task<ClaimsIdentity> GetIdentity(string username, string clientid, string password = null) { using (var serviceScope = _services.GetRequiredService<IServiceScopeFactory>().CreateScope()) { IAuthRepository _repo = serviceScope.ServiceProvider.GetService<IAuthRepository>(); ApplicationUser user = null; if (password != null) { user = await _repo.FindUser(username, password); } else { user = await _repo.FindUser(username); } if (user != null) { var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }; foreach (var r in await _repo.GetRoles(user)) { claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, r)); } claims.Add(new Claim("client_id", clientid)); ClaimsIdentity claimsIdentity = new ClaimsIdentity( claims, "Password", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); return claimsIdentity; } else { } // если пользователя не найдено return null; } } async Task<string> CreateRefreshToken(string clientid, ClaimsIdentity claimsIdentity) { using (var serviceScope = _services.GetRequiredService<IServiceScopeFactory>().CreateScope()) { IAuthRepository _repo = serviceScope.ServiceProvider.GetService<IAuthRepository>(); Client client = _repo.FindClient(clientid); var refreshTokenId = _helper.GetHash(_helper.GenerateRandomCryptographicKey(100)); var refreshTokenLifeTime = client.RefreshTokenLifeTime; var now = DateTime.UtcNow; var token = new RefreshToken() { Id = _helper.GetHash(refreshTokenId), ClientId = clientid, Subject = claimsIdentity.Name, IssuedUtc = now, ExpiresUtc = now.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) }; token.ProtectedTicket = JsonConvert.SerializeObject(new ProtectedTicket { clientid = clientid, username = claimsIdentity.Name }); var result = await _repo.AddRefreshToken(token); if (result) { return refreshTokenId; } return null; } } async Task<ProtectedTicket> GrantRefreshToken(string refreshTokenId) { using (var serviceScope = _services.GetRequiredService<IServiceScopeFactory>().CreateScope()) { IAuthRepository _repo = serviceScope.ServiceProvider.GetService<IAuthRepository>(); string hashedTokenId = _helper.GetHash(refreshTokenId); ProtectedTicket protectedTicket = null; var refreshToken = await _repo.FindRefreshToken(hashedTokenId); if (refreshToken != null) { //Get protectedTicket from refreshToken class protectedTicket = JsonConvert.DeserializeObject<ProtectedTicket>(refreshToken.ProtectedTicket); return protectedTicket; } else { return null; } } } }

Теперь обработаются запросы:
post "/token" с данными в body: grant_type="password", client_id="ngAuth", username="admin", password="123"
и
post "/token" с данными в body: grant_type="refresh_token", client_id="ngAuth", refresh_token="dgDrVQHylvHmi8QZ5oThVjWyqdrLYKhp1/XHsIJI65g="

На этом все, вообщем напишите кто че думает.

Весь код


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Разбор Memory Forensics с OtterCTF и знакомство с фреймворком Volatility

Привет, Хабр! Именно ее я хочу разобрать в этом посте, всем кому интересно — добро пожаловать под кат. Недавно закончился OtterCTF (для интересующихся — ссылка на ctftime), который в этом году меня, как человека, достаточно плотно связанного с железом откровенно ...

Манекен на турбореактивно-электрическом коптере-гибриде

Очередное свидетельство, что 2019 год будет годом хайпа турбореактивных штуковин. Американский стартап ElectraFly и вояки в 2019 на ракетном полигоне в Юте планируют испытания индивидуального квадрокоптера с турбореактивным двигателем. При наборе высоты турбореактивный движок будет помогать винтам, а потом давать ...