OAuth Authorization Provider для asp net core

от автора

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

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

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

   public interface IOAuthProvider     {         Task ByPassword(OAuthProviderContext oAuthProviderContext);         Task ByRefreshToken(OAuthProviderContext oAuthProviderContext);     }


То есть в конечном итоге реализовав один этот интерфейс и связав его в 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=»

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

Весь код


ссылка на оригинал статьи https://habr.com/post/424101/


Комментарии

Добавить комментарий

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