diff --git a/src/building blocks/JSE.WebAPI.Core/User/AspNetUser.cs b/src/building blocks/JSE.WebAPI.Core/User/AspNetUser.cs index 7e37e8f..fa04ec2 100644 --- a/src/building blocks/JSE.WebAPI.Core/User/AspNetUser.cs +++ b/src/building blocks/JSE.WebAPI.Core/User/AspNetUser.cs @@ -29,6 +29,11 @@ public string ObterUserToken() return EstaAutenticado() ? _accessor.HttpContext.User.GetUserToken() : ""; } + public string ObterUserRefreshToken() + { + return EstaAutenticado() ? _accessor.HttpContext.User.GetUserRefreshToken() : ""; + } + public bool EstaAutenticado() { return _accessor.HttpContext.User.Identity.IsAuthenticated; diff --git a/src/building blocks/JSE.WebAPI.Core/User/ClaimsPrincipalExtensions.cs b/src/building blocks/JSE.WebAPI.Core/User/ClaimsPrincipalExtensions.cs index e01d2b8..e2835a9 100644 --- a/src/building blocks/JSE.WebAPI.Core/User/ClaimsPrincipalExtensions.cs +++ b/src/building blocks/JSE.WebAPI.Core/User/ClaimsPrincipalExtensions.cs @@ -36,5 +36,17 @@ public static string GetUserToken(this ClaimsPrincipal principal) var claim = principal.FindFirst("JWT"); return claim?.Value; } + + public static string GetUserRefreshToken(this ClaimsPrincipal principal) + { + if (principal == null) + { + throw new ArgumentException(nameof(principal)); + } + + var claim = principal.FindFirst("RefreshToken"); + return claim?.Value; + } + } } \ No newline at end of file diff --git a/src/building blocks/JSE.WebAPI.Core/User/IAspNetUser.cs b/src/building blocks/JSE.WebAPI.Core/User/IAspNetUser.cs index 718e0e5..e7c19f3 100644 --- a/src/building blocks/JSE.WebAPI.Core/User/IAspNetUser.cs +++ b/src/building blocks/JSE.WebAPI.Core/User/IAspNetUser.cs @@ -9,6 +9,7 @@ public interface IAspNetUser Guid ObterUserId(); string ObterUserEmail(); string ObterUserToken(); + string ObterUserRefreshToken(); bool EstaAutenticado(); bool PossuiRole(string role); IEnumerable ObterClaims(); diff --git a/src/web/JSE.WebApp.MVC/Controllers/IdentidadeController.cs b/src/web/JSE.WebApp.MVC/Controllers/IdentidadeController.cs index bdabc6b..33391af 100644 --- a/src/web/JSE.WebApp.MVC/Controllers/IdentidadeController.cs +++ b/src/web/JSE.WebApp.MVC/Controllers/IdentidadeController.cs @@ -1,19 +1,16 @@ -using JSE.WebApp.MVC.Models; +using JSE.WebApp.MVC.Controllers; +using JSE.WebApp.MVC.Models; using JSE.WebApp.MVC.Services; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -namespace JSE.WebApp.MVC.Controllers +namespace NSE.WebApp.MVC.Controllers { public class IdentidadeController : MainController { - private readonly IAutenticacaoService _autenticacaoService; - public IdentidadeController(IAutenticacaoService autenticacaoService) + public IdentidadeController( + IAutenticacaoService autenticacaoService) { _autenticacaoService = autenticacaoService; } @@ -27,18 +24,17 @@ public IActionResult Registro() [HttpPost] [Route("nova-conta")] - public async Task Registro(UsuarioRegistroViewModel usuarioRegistroViewModel) + public async Task Registro(UsuarioRegistroViewModel usuarioRegistro) { - if (!ModelState.IsValid) return View(usuarioRegistroViewModel); + if (!ModelState.IsValid) return View(usuarioRegistro); - var resposta = await _autenticacaoService.Registro(usuarioRegistroViewModel); + var resposta = await _autenticacaoService.Registro(usuarioRegistro); - if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioRegistroViewModel); + if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioRegistro); - await RealizarLogin(resposta); + await _autenticacaoService.RealizarLogin(resposta); return RedirectToAction("Index", "Catalogo"); - } [HttpGet] @@ -51,17 +47,16 @@ public IActionResult Login(string returnUrl = null) [HttpPost] [Route("login")] - public async Task Login(UsuarioLoginViewModel usuarioLoginViewModel, string returnUrl = null) + public async Task Login(UsuarioLoginViewModel usuarioLogin, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; + if (!ModelState.IsValid) return View(usuarioLogin); - if (!ModelState.IsValid) return View(usuarioLoginViewModel); + var resposta = await _autenticacaoService.Login(usuarioLogin); - var resposta = await _autenticacaoService.Login(usuarioLoginViewModel); + if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioLogin); - if (ResponsePossuiErros(resposta.ResponseResult)) return View(usuarioLoginViewModel); - - await RealizarLogin(resposta); + await _autenticacaoService.RealizarLogin(resposta); if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Catalogo"); @@ -72,35 +67,8 @@ public async Task Login(UsuarioLoginViewModel usuarioLoginViewMod [Route("sair")] public async Task Logout() { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + await _autenticacaoService.Logout(); return RedirectToAction("Index", "Catalogo"); } - - private async Task RealizarLogin(UsuarioRespostaLoginViewModel usuarioRespostaLoginViewModel) - { - var token = ObterTokenFormatado(usuarioRespostaLoginViewModel.AccessToken); - - var claims = new List(); - claims.Add(new Claim("JWT", usuarioRespostaLoginViewModel.AccessToken)); - claims.AddRange(token.Claims); - - var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - - var authProperties = new AuthenticationProperties - { - ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60), - IsPersistent = true - }; - - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - new ClaimsPrincipal(claimsIdentity), - authProperties); - } - - private static JwtSecurityToken ObterTokenFormatado(string jwtToken) - { - return new JwtSecurityTokenHandler().ReadToken(jwtToken) as JwtSecurityToken; - } } -} +} \ No newline at end of file diff --git a/src/web/JSE.WebApp.MVC/Extensions/ExceptionMiddleware.cs b/src/web/JSE.WebApp.MVC/Extensions/ExceptionMiddleware.cs index 48c80d9..9664817 100644 --- a/src/web/JSE.WebApp.MVC/Extensions/ExceptionMiddleware.cs +++ b/src/web/JSE.WebApp.MVC/Extensions/ExceptionMiddleware.cs @@ -1,4 +1,5 @@ -using Polly.CircuitBreaker; +using JSE.WebApp.MVC.Services; +using Polly.CircuitBreaker; using Refit; using System.Net; @@ -7,14 +8,17 @@ namespace JSE.WebApp.MVC.Extensions public class ExceptionMiddleware { private readonly RequestDelegate _next; + private static IAutenticacaoService _autenticacaoService; public ExceptionMiddleware(RequestDelegate next) { _next = next; } - public async Task InvokeAsync(HttpContext httpContext) + public async Task InvokeAsync(HttpContext httpContext, IAutenticacaoService autenticacaoService) { + _autenticacaoService = autenticacaoService; + try { await _next(httpContext); @@ -41,6 +45,17 @@ private static void HandleRequestExceptionAsync(HttpContext context, HttpStatusC { if (statusCode == HttpStatusCode.Unauthorized) { + if(_autenticacaoService.TokenExpirado()) + { + if(_autenticacaoService.RefreshTokenValido().Result) + { + context.Response.Redirect(context.Request.Path); + return; + } + } + + _autenticacaoService.Logout(); + context.Response.Redirect($"/login?ReturnUrl={context.Request.Path}"); return; } diff --git a/src/web/JSE.WebApp.MVC/Models/UsuarioRespostaLoginViewModel.cs b/src/web/JSE.WebApp.MVC/Models/UsuarioRespostaLoginViewModel.cs index 81557bc..e317bc6 100644 --- a/src/web/JSE.WebApp.MVC/Models/UsuarioRespostaLoginViewModel.cs +++ b/src/web/JSE.WebApp.MVC/Models/UsuarioRespostaLoginViewModel.cs @@ -5,6 +5,7 @@ namespace JSE.WebApp.MVC.Models public class UsuarioRespostaLoginViewModel { public string AccessToken { get; set; } + public string RefreshToken { get; set; } public double ExpiresIn { get; set; } public UsuarioTokenViewModel UsuarioToken { get; set; } public ResponseResult ResponseResult { get; set; } diff --git a/src/web/JSE.WebApp.MVC/Services/AutenticacaoService.cs b/src/web/JSE.WebApp.MVC/Services/AutenticacaoService.cs index f699109..b09459a 100644 --- a/src/web/JSE.WebApp.MVC/Services/AutenticacaoService.cs +++ b/src/web/JSE.WebApp.MVC/Services/AutenticacaoService.cs @@ -1,20 +1,32 @@ using JSE.Core.Communication; +using JSE.WebAPI.Core.User; using JSE.WebApp.MVC.Extensions; using JSE.WebApp.MVC.Models; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Options; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; namespace JSE.WebApp.MVC.Services { public class AutenticacaoService : Service, IAutenticacaoService { private readonly HttpClient _httpClient; + private readonly IAuthenticationService _authenticationService; + private readonly IAspNetUser _user; + public AutenticacaoService(HttpClient httpClient, - IOptions settings) + IOptions settings, + IAuthenticationService authenticationService, + IAspNetUser user) { httpClient.BaseAddress = new Uri(settings.Value.AutenticacaoUrl); - _httpClient = httpClient; + _httpClient = httpClient; + _authenticationService = authenticationService; + _user = user; } public async Task Login(UsuarioLoginViewModel usuarioLogin) @@ -50,5 +62,81 @@ public async Task Registro(UsuarioRegistroViewMod return await DeserializarObjetoResponse(response); } + + public async Task RealizarLogin(UsuarioRespostaLoginViewModel resposta) + { + var token = ObterTokenFormatado(resposta.AccessToken); + + var claims = new List(); + claims.Add(new Claim("JWT", resposta.AccessToken)); + claims.Add(new Claim("RefreshToken", resposta.RefreshToken)); + claims.AddRange(token.Claims); + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + + var authProperties = new AuthenticationProperties + { + ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8), + IsPersistent = true + }; + + await _authenticationService.SignInAsync( + _user.ObterHttpContext(), + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + } + + public async Task Logout() + { + await _authenticationService.SignOutAsync( + _user.ObterHttpContext(), + CookieAuthenticationDefaults.AuthenticationScheme, + null); + } + + public static JwtSecurityToken ObterTokenFormatado(string jwtToken) + { + return new JwtSecurityTokenHandler().ReadToken(jwtToken) as JwtSecurityToken; + } + + public async Task UtilizarRefreshToken(string refreshToken) + { + var refreshTokenContent = ObterConteudo(refreshToken); + + var response = await _httpClient.PostAsync("/api/identidade/refresh-token", refreshTokenContent); + + if (!TratarErrosResponse(response)) + { + return new UsuarioRespostaLoginViewModel + { + ResponseResult = await DeserializarObjetoResponse(response) + }; + } + + return await DeserializarObjetoResponse(response); + } + + public bool TokenExpirado() + { + var jwt = _user.ObterUserToken(); + if (jwt is null) return false; + + var token = ObterTokenFormatado(jwt); + return token.ValidTo.ToLocalTime() < DateTime.Now; + } + + public async Task RefreshTokenValido() + { + var resposta = await UtilizarRefreshToken(_user.ObterUserRefreshToken()); + + if (resposta.AccessToken != null && resposta.ResponseResult == null) + { + await RealizarLogin(resposta); + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/src/web/JSE.WebApp.MVC/Services/IAutenticacaoService.cs b/src/web/JSE.WebApp.MVC/Services/IAutenticacaoService.cs index 8806b92..b84d953 100644 --- a/src/web/JSE.WebApp.MVC/Services/IAutenticacaoService.cs +++ b/src/web/JSE.WebApp.MVC/Services/IAutenticacaoService.cs @@ -5,7 +5,10 @@ namespace JSE.WebApp.MVC.Services public interface IAutenticacaoService { Task Login(UsuarioLoginViewModel usuarioLogin); - Task Registro(UsuarioRegistroViewModel usuarioRegistro); + Task RealizarLogin(UsuarioRespostaLoginViewModel resposta); + Task Logout(); + bool TokenExpirado(); + Task RefreshTokenValido(); } } \ No newline at end of file