Servicios REST con .NET Core
  • Servicios REST con ASP.NET Core y Entity Framework Core
  • 1. Introducción
    • 1.1 Instalación Visual Studio Community
    • 1.2 Instalación de SQL Server en Mac
    • 1.3 Extensión Intellicode
    • 1.4 Aplicación de ejemplo
  • 2. Explicación de Conceptos
    • 2.1 Servicios REST
      • 2.1.1 OData
      • 2.1.2 GraphQL
    • 2.2 Entity Framework para versiones de tu base de datos
    • 2.3 Paquetes Nuget
    • 2.4 Archivo de recursos
    • 2.5 Estructura de los Proyectos .Net Core
    • 2.6 Código Limpio
    • 2.7 Programación asíncrona
    • 2.8 Git
    • 2.9 ¿Qué es Scrum?
      • 2.9.1 Personas
      • 2.9.2 Roles Scrum
      • 2.9.3 Backlog
      • 2.9.4 Reuniones del Scrum
  • 3. Control de código fuente y Scrum con Azure DevOps
    • 3.1 Introducción a Azure DevOps
      • 3.1.1 Crear un nuevo Proyecto en Azure DevOps
      • 3.1.2 Agregando personas al equipo de trabajo
      • 3.1.3 Agregando los sprints y la capacidad de trabajo
      • 3.1.4 Crear el Backlog y asignar User Stories al Sprint
      • 3.1.5 Crear Prototipos
      • 3.1.6 Conectar a Azure DevOps desde Visual Studio
        • 3.1.6.1 Crear tu proyecto con Visual Studio Community y sincronizarlo a Azure DevOps
      • 3.1.7 Consultar tus tareas pendientes
        • 3.1.7.1 Tareas y Dashboards con Azure DevOps
        • 3.1.7.2 Consultar tus tareas desde Visual Studio Community
      • 3.1.8 Trabajando con Ramas (Branches)
        • 3.1.8.1 Crear la rama desarrollo desde Azure DevOps
          • 3.1.8.1 Crear una rama(branch) desde tu tarea en Azure DevOps
        • 3.1.8.2 Como trabajar con ramas (branches) desde Visual Studio Community
        • 3.1.8.3 Crear el Pull Request con Azure DevOps
      • 3.1.9 Retrospectiva del Sprint
      • 3.1.10 Agregando una Wiki
    • 3.2 Integrando tu código fuente a GitHub
      • 3.2.1 Trabajando con ramas en GitHub
      • 3.2.2 Sincronizar los cambios del código con GitHub y Visual Studio
      • 3.3 Trabajando con Branches (Ramas) con Visual Studio
        • 3.3.1 Branches con Visual Studio
        • 3.3.2 Creando un template para tus PR (Pull Request)
        • 3.3.3 Protegiendo tu branch
  • 4. Creando tu primer servicio
    • 4.1 Crear las base de datos y los usuarios en MySQL
    • 4.2 Crear la tabla Categoría y sus validaciones
    • 4.3 Creando el servicio Categorias
    • 4.4 Probando tus servicios con POSTMAN
    • 4.5 Documentar y Probar tus servicios con Swagger
      • 4.5.1 Configurar Swagger
      • 4.5.2 Comentarios XML
      • 4.5.3 Generando la página de documentación
    • 4.6 Agregando índices
    • 4.7 Mejorando tu código
      • 4.7.1 Creando Objetos de Accesos a Datos
      • 4.7.2 Creando tus mensajes de error en diferentes idiomas
      • 4.7.3 Cambiando el formato del Json de los servicios
  • 5. Agregando el servicio para los productos
    • 5.1 Crear la tabla de Productos
    • 5.2 Formas de cargar información de tablas relacionadas
    • 5.3 Crear llaves fóraneas e índices
    • 5.4 Creando el servicio Productos
    • 5.5 Validar Reglas Mejorando tu código
      • 5.5.1 Agregando una excepción a todos nuestros servicios
      • 5.5.2 Agregando clases genéricas para validar y/o consultar información
      • 5.5.3 Alternativa para validar reglas con ef core
  • 6. Cambiar de base de datos a SQL Server
    • 6.1 Cambiar la base de datos a SQL Server
    • 6.2 Cambiar a SQL Server en Azure
  • 7. Crear servicios con OData
    • 7.1.1 Creando el modelo Clientes
    • 7.1.2 Creando el modelo ClienteCategoría
    • 7.1.3 Agregando paquete Nuget para OData
    • 7.1.4 Configurar el EDM Model
    • 7.1.5 Configurar el servicio OData y llaves foráneas
    • 7.1.6 Creando el Controller para clientes
    • 7.1.7 Configurar y probar los servicios con OData
    • 7.1.8 Práctica Crear el servicio para ClientesCategorias
    • 7.1.9 Recomendaciones de seguridad y rendimiento a tomar en cuenta con OData
  • 8. GraphQL
    • 8.1.1 Creando la tabla Caducidad
    • 8.1.2 Creando el query
    • 8.1.3 Configurando y probando graphQL
    • 8.1.4 Creando la Mutation
    • 8.1.5 Probando nuestros servicios con Postman
  • 9. Seguridad
    • 9.1 Json Web Tokens
    • 9.2 Seguridad basada en roles y usuarios
      • 9.2.1 Creando nuestra tabla roles e insertando los roles principales
      • 9.2.2 Consideraciones de seguridad para almacenar tus passwords
      • 9.2.3 Creando las tablas para manejar la seguridad
      • 9.2.4 Agregando usuarios y roles
    • 9.3 Agregando seguridad a nuestros servicios
    • 9.4 Creando nuestro servicio de login y generar el token
    • 9.5 ¿Cómo agregar seguridad basada en roles a los Servicios REST?
      • 9.5.1 Seguridad basada en claims
      • 9.5.2 Creando las tablas para validar permisos por cada tabla
      • 9.5.3 Seguridad basada en directivas
      • 9.5.4 Seguridad con Action Filters
    • 9.6 Guardando el historial de cambios
    • 9.7 Refrescando tu token
    • 9.8 Seguridad Mejorando tu código
      • 9.8.1 ¿Cómo limitar el número de intentos incorrectos en el login?
      • 9.8.2 ¿Cómo obtener la ciudad del usuario por medio de la IP?
      • 9.8.3 Habilitando CORS
  • 10. Pruebas Unitarias
    • 10.1 Agregando el proyecto de pruebas unitarias
    • 10.2 Crear una prueba unitaria
      • 10.2.1 Ejecutando las pruebas unitarias
    • 10.3 Agregando una base de datos en memoria para nuestras pruebas unitarias
    • 10.4 Agregando la referencia de nuestro proyecto CaducaRest
      • 10.4.1 Agregando paquetes nuget necesarios
    • 10.5 Configurando Clases para Objetos Sustitutos
      • 10.5.1 Configurando el Contexto para utilizar la base de datos en Memoria
      • 10.5.2 Configurando el objeto para sustituir mensajes de Error por idioma
    • 10.6 Agregando pruebas para las Categorías
  • 11. Integración continua
    • 11.1 ¿Qué es la integración continua?
    • 11.2 Subir tu código fuente a BitBucket
      • 11.2.1 Integración continua y pruebas automáticas con Bitbucket
    • 11.3 Integración continua y pruebas automáticas en Azure DevOps
  • 12. Pruebas de integración
    • 12.1 ¿Qué es SpecFlow?
    • 12.2 Agregando el proyecto de pruebas de integración
    • 12.3 Configurando Specflow
    • 12.4 Creando pruebas para el login
    • 12.5 Agregando las pruebas de Integración a Azure Devops
    • 12.6 Specflow Mejorando tu código
      • 12.6.1 Cambiando las pruebas a español
      • 12.6.2 Pasando tablas a nuestras pruebas
      • 12.6.3 Agregar los passwords como variables de ambiente
      • 12.6.4 Probando con SQLite
      • 12.6.5 Agregando diferentes parámetros con MSTest
      • 12.6.6 Generando el reporte living doc de specflow
  • 13. Integración continua con Postman
    • 13.1 Recomendaciones para probar tus servicios
    • 13.2 Instrucciones básicas para probar con Postman
    • 13.3 Crear colecciones en Postman
    • 13.4 Agregar pruebas a tus servicios
    • 13.5 Crear environments
    • 13.6 Agregando datos de prueba con archivos .csv
    • 13.7 Exportando tus colecciones y ejecutarlas con Newman.
    • 13.8 Agregando las colecciones de postman al pipeline
  • 14. Pruebas de usuario
    • 14.1 Page Object Model
    • 14.2 ¿Qué es Selenium?
      • 14.2.1 Selenium Instrucciones básicas
      • 14.2.2 Agregando el proyecto de pruebas de usuario
    • 14.3 ¿Qué es Cypress?
      • 14.3.1 Cypress Instrucciones básicas
    • 14.4 ¿Qué es Playwright?
      • 14.4.1 Playwright Instrucciones básicas
  • 15. Despliegue Continuo con Azure DevOps y Azure
    • 15.1 Crear un App Service en Azure
    • 15.2 Generando Artifacts en Azure Pipelines
    • 15.3 Generando el Release en Azure Pipelines al App Service de Azure
    • 15.4 Ejecutando las colecciones de Postman después del release
    • 15.5 Agregando las pruebas de usuario en Azure Pipelines
  • 16. Instalación en Windows Server e IIS
    • 16.1 Instalar IIS en Windows Server
    • 16.2 Instalación del ASP.NET Core Module/Hosting Bundle
    • 16.3 Crea el Sitio Web en IIS
  • 17. Instalación en Linux
    • 17.1 Creando una máquina virtual linux en Azure
    • 17.2 Habilitando el acceso remoto
    • 17.3 Configura linux para .Net Core
    • 17.4 Instalando mysql
    • 17.5 Instalando Nginx y configurando tu servicio
    • 17.6 Instalando un certificado SSL gratuito con CertBot
    • 17.7 Agregando diferentes subdominios
  • 18. Docker
Powered by GitBook
On this page
  • Crear clases para Generar y el código para Refrescar el Token
  • Crear el método para el login.

Was this helpful?

  1. 9. Seguridad

9.4 Creando nuestro servicio de login y generar el token

Vamos a crear nuestro servicio de login en el cual además de validar al usuario vamos a regresar el token. Según que tanta seguridad necesite tu aplicación puedes darle un tiempo de vida más largo o más corto. En mi caso como es una aplicación sencilla que no maneja datos muy confidenciales le dejaré un tiempo de 15 días. Para aplicaciones que manejen información más confidencial similar a un banco puedes darle tiempo de una hora. También si quieres probar que los token caducan y el servicio para refrescar el token puedes darle un tiempo más corto para pruebas de 2 minutos, solamente para que no debas esperar a que expiren los tokens.

Vamos a crear una carpeta llamada DTO (Data Transfer Object) en esta carpeta vamos a guardar archivos que necesitamos para nuestros servicios pero que no pertenecen a ninguna tabla, por ejemplo vamos a crear una clase llamada TokenDTO para regresar los datos del Token cuando un usuario inicia sesión correctamente.

TokenDTO
public class TokenDTO
{
    public string Token { get; set; }
    
    public DateTime TokenExpiration { get; set; }
    
    public string Nombre { get; set; }

    public string RefreshToken { get; set; }
}

Agregamos otro clase llamada LoginDTO el cual va a incluir los datos que necesitamos para realizar el login, para empezar solo pediremos usuario y contraseña.

LoginDTO
public class LoginDTO
{
    [Required(ErrorMessage = "Required")]
    [StringLength(15)]
    public string Usuario { get; set; }

    [Required(ErrorMessage = "Required")]
    [StringLength(255)]
    public string Password { get; set; }

}

Crear clases para Generar y el código para Refrescar el Token

En nuestra carpeta Core agregamos una clase Token el cual tendrá 2 métodos uno para generar el Token y otro para generar el método para refrescar el token.

Como generar el token

La clase que genera el Token de parte de .NET es JwtSecurityToken, en nuestro método GenerarToken recibe como parámetros la lista de claims adicionales que deseas regresar en tu token.

Para generar el token vamos a utilizar una llave simétrica la cual nos servirá para hacer el hash al token, similar a como lo hicimos con el password del usuario con el salt, para firmar utilizaremos el algoritmo SHA-256 con la función SingningCredentials, e indicamos que expira en 15 días, agregamos también quien es el issuer y quien es la audiencia.

Por último con el método JwtSecurityTokenHandler() generamos la cadena que contiene el token.

Token.cs
public class Token
{
    protected readonly IConfiguration Config;

    public Token(IConfiguration config)
    {
        Config = config;
    }

    public string GenerarToken(Claim[] claims)
    {
        var key = new SymmetricSecurityKey
                   (Encoding.UTF8.GetBytes(Config["Tokens:Key"]));
        var creds = new SigningCredentials
                   (key, SecurityAlgorithms.HmacSha256);
        JwtSecurityToken jwtToken = new JwtSecurityToken
                   (Config["Tokens:Issuer"],
                    Config["Tokens:Issuer"],
                    claims,
                    expires: DateTime.Now.AddDays(1).ToLocalTime(),
                    signingCredentials: creds);
        string token = new JwtSecurityTokenHandler()
                    .WriteToken(jwtToken);
            return token;
    }
}

Como refrescar el Token

Debido a que nuestro token expira después de 15 días ya no será válido, algunos de los servicios REST incluyen un método para refrescar el token, por seguridad si tu token nunca expira y un hacker obtiene tu token podría acceder siempre a tus datos a menos que desactives tu usuario, para refrescar el token, al momento de que realizas el login se suele regresar una cadena aleatoria, esta cadena aleatoria se debe enviar como parámetro junto con tu token para validar que es un token válido.

Creamos una función RefrescarToken el cual nos va a generar la cadena aleatoria para refrescar el token, para eso obtenemos un número random, luego lo convertimos a una cadena base 64, nuestro método para refrescar token será por medio GET, el convertir a base64 nos genera caracteres con &,?,/,+,- los cuales indican parámetros en nuestros métodos GET, remplazaremos estos caracteres por números

Token.cs
public class Token
{
    public string RefrescarToken()
    {
        var randomNumber = new byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber)
                .Replace("$", "1").Replace("/", "2")
                .Replace("&", "3").Replace("+", "4")
                .Replace("-", "5").Replace("?", "6");
        }
    }
}

Crear el método para el login.

Agregamos una clase RolDAO el cual nos regresará los roles que tiene el usuario que se envía como parámetro

RolDAO.cs
public class RolDAO
{
    private readonly CaducaContext contexto;
    private readonly LocService localizacion;

    public RolDAO(CaducaContext context, LocService localize)
    {
        this.contexto = context;
        this.localizacion = localize;
    }

    public List<string> ObtenerRolesPorUsuarios(int usuarioId)
    {
            return (from usuarioRol in contexto.UsuarioRol
                join rol in contexto.Rol
                    on usuarioRol.RolId equals rol.Id
                where usuarioRol.UsuarioId == usuarioId
            select rol.Nombre).ToList();
    }
}

En nuestro archivo UsuarioDAO agregamos el método para el LoginAsync.

Primero realizamos las siguientes validaciones:

  1. Que el usuario exista

  2. Que el password del usuario coincida con el que está guardado, para esto al password enviado por el usuario le agregamos el salt (adicional1)

  3. Que el usuario se encuentre activo

Si cumple todas las condiciones agregamos:

  • El claim con el id del usuario. Utilizaré el claim de tipo Sid

  • El claim con los roles del usuario.

  • El token generado

  • El código para refrescar el token

UsuarioDAO.cs
public class UsuarioDAO
{
   public async Task<TokenDTO> LoginAsync(LoginDTO loginDTO, 
                                          IConfiguration config)
   {
       TokenDTO tokenDTO = new TokenDTO();
       Seguridad seguridad = new Seguridad();
       Token token = new Token(config);
       var usuario = await contexto.Usuario.FirstOrDefaultAsync
                                 (usu => usu.Clave == loginDTO.Usuario);
       if (usuario == null)
       {
            customError = new CustomError(400,
               String.Format(this.localizacion
                    .GetLocalizedHtmlString("GeneralNoExiste"),
                                               "La clave del usuario"));
            return tokenDTO;
       }
       if (usuario.Password != seguridad
                    .GetHash(usuario.Adicional1 + loginDTO.Password ))
       {
            customError = new CustomError(400, 
                  this.localizacion
                       .GetLocalizedHtmlString("PasswordIncorrecto"));
            return tokenDTO;
       }
       if (!usuario.Activo)
       {
            customError = new CustomError(403,
                   this.localizacion
                       .GetLocalizedHtmlString("UsuarioInactivo"));
            return tokenDTO;
       }           
       var claims = new Claim[]
       {
           new Claim(ClaimTypes.Sid, usuario.Id.ToString()),
       };
       RolDAO rolDAO = new RolDAO(contexto, localizacion);
       var roles = rolDAO.ObtenerRolesPorUsuarios(usuario.Id);
       foreach (var rol in roles)
       {
           claims.Add(new Claim(ClaimTypes.Role, rol));
       }
       DateTime fechaExpiracion = DateTime.Now.AddDays(15).ToLocalTime();
       tokenDTO.Token = token.GenerarToken(claims, fechaExpiracion);
       tokenDTO.TokenExpiration = fechaExpiracion;
       tokenDTO.UsuarioId = usuario.Id;
       tokenDTO.RefreshToken = token.RefrescarToken();
       return tokenDTO;
   }
}

Agregamos los mensajes de error a nuestros archivos de recursos de mensajes.

En el método Post ponemos entre paréntesis la palabra Login para que el login se acceda mediante la siguiente url:

POST http://localhost:5000/api/Usuarios/Login

Agregamos entre [] la palabra AllowAnonymous el cual le indica al servicio que permite el acceso a este servicio no requiere token, cualquiera puede acceder a este servicio.

UsuariosController.cs
public class UsuariosController : ControllerBase
{
    public UsuariosController(CaducaContext context,
                                  LocService localize,
                                  IConfiguration config,
                                  IWebHostEnvironment hostingEnvironment,
                                  IHttpContextAccessor accessor) : base(context, localize)
    {           
        _config = config;
        _accessor = accessor;
        _hostingEnvironment = hostingEnvironment;
        usuarioDAO = new UsuarioDAO(context, localize, _hostingEnvironment.ContentRootPath);
    }
        
    [HttpPost("Login")]
    [AllowAnonymous]
    public async Task<IActionResult> PostAsync(
                                           [FromBody] LoginDTO loginDTO)
    {
        var token = await usuarioDAO.LoginAsync(loginDTO, _config);
        if (string.IsNullOrEmpty(token.Token))         
            return StatusCode(usuarioDAO.customError.StatusCode,
                           usuarioDAO.customError.Message);
        return Ok(token);
    }
}

Para acceder a los servicios con Swagger

  1. Da clic en el botón Authorize que se encuentra arriba a la derecha.

  2. Teclea la palabra Bearer seguido de un espacio y el token generado por el login

  3. Dar clic en el botón Authorize de la ventana popup

Ahora puedes consultar cualquier servicio y te regresará la información

Para probar los servicios con Postman

  1. Da clic en la pestaña Authorization

  2. Del lado izquierdo en Type Selecciona Bearer Token

  3. En Token escribe el token generado por el método Login

Previous9.3 Agregando seguridad a nuestros serviciosNext9.5 ¿Cómo agregar seguridad basada en roles a los Servicios REST?

Last updated 2 years ago

Was this helpful?

Agregamos un nuevo controller (UsuariosController) para los servicios de los usuarios puedes ver el siguiente para recordar cómo crear un nuevo controller.

link