Spring Security
Dada la importancia de la seguridad y la popularidad de Spring Security, muchos desarrolladores Java están aprendiendo este útil framework, pero no es tan fácil. Proteger una aplicación con OAuth, OICD y JWT puede parecer una tarea abrumadora.
Necesitas dedicar mucho tiempo a comprender la seguridad y cómo implementarla con el framework.
Aprendí Spring Security por mi cuenta y me llevó más tiempo del que esperaba.
Onion Architecture
La arquitectura onion architecture. Permite separar responsabilidades entres grandes capas:
Capa | Descripció |
---|---|
Domain | Entidades, interfaces de los servicios y excepciones de dominio. |
Application | Implementación de servicios de la aplicación, mappers y DTOs. |
Infrastructure | Controladores, filtros, configuración de la aplicación, interfaz del repositorio JPA e implementación del repositorio. |
A continuación, puedes observar la estructura:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
auth
├── application
│ ├── AuthCookieConstants.java
│ ├── mappers
│ │ └── AuthMapper.java
│ └── services
│ ├── AuthServiceImpl.java
│ └── TokenServiceImpl.java
├── domain
│ ├── AuthException.java
│ ├── Role.java
│ ├── services
│ │ ├── AuthService.java
│ │ └── TokenService.java
│ ├── User.java
│ └── UserRepository.java
└── infrastructure
├── config
│ ├── EncoderConfig.java
│ └── SecurityConfig.java
├── controllers
│ └── AuthController.java
├── dtos
│ ├── CreateUserDto.java
│ ├── LoginRequestDTO.java
│ └── UserResponseDTO.java
├── filters
│ ├── JwtAuthenticationFilter.java
└── persistance
└── PostgresUserRepository.java
El tipo de inyección de dependencias que he usado es mediante constructor. Es la mejor forma de inyectar dependencias en Spring y la forma recomendada en la documentación oficial.
Si sueles usar inyección de dependencias mediante campos, te recomiendo que lo cambies a inyección de dependencias mediante constructor.
Lombok
Para reducir el código repetitivo, podemos usar la biblioteca Lombok que nos permite reducir el código visible mediante anotaciones. En este caso, las anotaciones más comunes son: @Getter
, @Setter
, @NoArgsConstructor
, @AllArgsConstructor
y @Builder
.
Entidades de dominio anotadas con JPA
Las entidades de dominio son clases que representan cosas del problema que resolvemos. En este caso una entidad sería User
.
Estas clases llevan las anotaciones @Entity
y @Table
para indicar que son también entidades JPA.
Anotar entidades de dominio con anotaciones de un framework puede ser considerado mala práctica. EL término se llama contaminación de dominio. He decidido hacerlo para simplificar el código y no tener que crear mappers innecesariamente que añaden complejidad.
La clase WebSecurityConfig
es la configuración principal de Spring Security. Se encarga de configurar la seguridad de la aplicación y de definir qué rutas son públicas y cuáles no.
¿Por qué UserDetails?
UserDetails
es una interfaz que representa a un usuario en Spring Security y es usada por un servicio llamado UserDetailsService
.
Esto es necesario para que Spring Security pueda autenticar a un usuario sin implementar código adicional.
DTOs
Los DTOs (Data Transfer Objects) son objetos que se usan para transferir datos entre capas. En este caso, se usan para transferir datos entre la capa de insfraestructura y la capa de aplicación.
AuthMapper
El AuthMapper
es una clase que transforma DTOs a entidades de dominio o al revés. Es una buena práctica tener un mapper para únicamente transformar datos entre capas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.auth.application.mappers;
public class AuthMapper {
private AuthMapper() {
throw new UnsupportedOperationException("This class should never be instantiated");
}
public static User fromDto(final CreateUserDto createUserDto) {
return User.builder()
.email(createUserDto.email())
.firstName(createUserDto.firstName())
.build();
}
public static Authentication fromDto(final LoginRequestDTO LoginRequestDTO) {
return new UsernamePasswordAuthenticationToken(loginRequestDTO.email(), loginRequestDTO.password());
}
public static UserResponseDTO toDto(final User user) {
return new UserResponseDTO(user.getId(), user.getFirstName(), user.getEmail(), user.getRole());
}
}