├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── messages.properties │ │ ├── application-prod.properties │ │ ├── application.properties │ │ ├── application-dev.properties │ │ └── application-test.properties │ └── java │ │ └── com │ │ └── almadavic │ │ └── securitystandard │ │ ├── enumerated │ │ └── RoleName.java │ │ ├── service │ │ ├── customException │ │ │ ├── DatabaseException.java │ │ │ ├── ResourceNotFoundException.java │ │ │ ├── InvalidParamException.java │ │ │ ├── SamePasswordException.java │ │ │ ├── PasswordDoesntMatchRegisterUserException.java │ │ │ ├── EmailAlreadyRegisteredException.java │ │ │ └── NicknameAlreadyRegisteredException.java │ │ ├── businessRule │ │ │ ├── registerUser │ │ │ │ ├── RegisterUserVerification.java │ │ │ │ ├── RegisterUserArgs.java │ │ │ │ ├── PasswordMatchRegisterUser.java │ │ │ │ ├── EmailAlreadyRegistered.java │ │ │ │ └── NicknameAlreadyRegistered.java │ │ │ ├── changePassword │ │ │ │ ├── ChangePasswordVerification.java │ │ │ │ ├── ChangePasswordArgs.java │ │ │ │ ├── DifferentPassword.java │ │ │ │ └── PasswordMatchsDataBasePassword.java │ │ │ └── findUsersByParameter │ │ │ │ ├── FindUsersArgs.java │ │ │ │ ├── FindUsersByRoleNameVerification.java │ │ │ │ ├── InvalidRoleName.java │ │ │ │ ├── NoRoleName.java │ │ │ │ └── ValidRoleName.java │ │ └── serviceAction │ │ │ ├── ServiceParent.java │ │ │ ├── AuthenticationService.java │ │ │ ├── TokenService.java │ │ │ ├── UserAreaService.java │ │ │ ├── UserService.java │ │ │ ├── AuthenticationServiceImpl.java │ │ │ ├── UserAreaServiceImpl.java │ │ │ ├── TokenServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ ├── repository │ │ ├── RoleRepository.java │ │ └── UserRepository.java │ │ ├── SecurityStandardApplication.java │ │ ├── dto │ │ ├── response │ │ │ ├── RoleDTO.java │ │ │ ├── UserMonitoringDTO.java │ │ │ └── UserDTO.java │ │ └── request │ │ │ ├── ChangePassword.java │ │ │ ├── Login.java │ │ │ └── RegisterUserDTO.java │ │ ├── config │ │ ├── securityConfig │ │ │ ├── Token.java │ │ │ ├── SecurityConfigurations.java │ │ │ └── SecurityConfigurationsImpl.java │ │ ├── exceptionConfig │ │ │ ├── handler │ │ │ │ ├── ForbiddenHandler.java │ │ │ │ ├── UnauthorizedHandler.java │ │ │ │ ├── AuthorizationAuthenticationHandler.java │ │ │ │ └── ResourceExceptionHandler.java │ │ │ └── standardError │ │ │ │ ├── validationArgsStandardError │ │ │ │ ├── StandardErrorArgsNotValid.java │ │ │ │ └── ValidationErrorCollection.java │ │ │ │ └── commonStandardError │ │ │ │ └── StandardError.java │ │ ├── SwaggerConfigurations.java │ │ └── StartProjectConfigurations.java │ │ ├── controller │ │ ├── AuthenticationControllerImpl.java │ │ ├── AuthenticationController.java │ │ ├── UserAreaControllerImpl.java │ │ ├── UserControllerImpl.java │ │ ├── UserAreaController.java │ │ └── UserController.java │ │ ├── util │ │ └── UserMapper.java │ │ ├── entity │ │ ├── Role.java │ │ └── User.java │ │ └── filter │ │ └── AuthenticationJWTFilter.java └── test │ └── java │ └── com │ └── almadavic │ └── securitystandard │ ├── service │ ├── userAreaService │ │ └── MyProfile.java │ └── userService │ │ ├── RegistrationUserTest.java │ │ └── FindUsersTest.java │ └── controller │ ├── userAreaController │ ├── MyProfileTest.java │ └── ChangePasswordTest.java │ ├── ClassTestParent.java │ ├── authentication │ └── AuthenticationTest.java │ ├── userController │ ├── RegistrationUserTest.java │ └── FindUsersTest.java │ └── authorization │ ├── AccessDeniedAuthorizationTest.java │ └── AccessAllowedAuthorizationTest.java ├── .gitignore ├── LICENSE ├── pom.xml ├── README.md ├── mvnw.cmd └── mvnw /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Almadavic/security-standard/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | NotBlank=The field cannot be null or empty. 2 | NotNull= The value cannot be null. 3 | Email=The e-mail format has to be valid. 4 | Size= The password needs to have between 6 and 18 characters. 5 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/enumerated/RoleName.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.enumerated; 2 | 3 | 4 | public enum RoleName { // ENUM que representa o nome dos perfis do sistema. 5 | 6 | ROLE_ADMIN, 7 | ROLE_USER 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | #URL 2 | spring.datasource.url=${DATABASE_URL} 3 | 4 | #DDL 5 | spring.jpa.hibernate.ddl-auto=none 6 | 7 | #SQL 8 | spring.jpa.show-sql=false 9 | spring.jpa.properties.hibernate.format_sql=false 10 | 11 | #JWT 12 | jwt.secret=${JWT_SECRET} 13 | jwt.expiration=${JWT_EXPIRATION} -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/DatabaseException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class DatabaseException extends RuntimeException { // Erro generalizado do Banco de Dados. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public DatabaseException(String msg) { 13 | super(msg); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/registerUser/RegisterUserVerification.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.registerUser; 2 | 3 | 4 | public interface RegisterUserVerification { // Regra de negocio relacionada á registrar um usuário no sistema. 5 | 6 | // SOLID - Utilizado o Open Closed Principle 7 | 8 | void verification(RegisterUserArgs args); // As classes que implementam essa interface terão que implementar esse método. 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class ResourceNotFoundException extends RuntimeException { // Quando um recurso não é encontrado. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public ResourceNotFoundException(String msg) { 13 | super(msg); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/ServiceParent.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | 6 | public abstract class ServiceParent { 7 | 8 | @Autowired 9 | protected PasswordEncoder encoder; // injeção de dependencia de Encoder -> Codificar uma senha para ser salva no banco e para fazer validação (match). 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/InvalidParamException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class InvalidParamException extends RuntimeException { // Erro quando o usuário passa algum paramêtro inválido na request. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public InvalidParamException(String msg) { 13 | super(msg); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.config.securityConfig.Token; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | 7 | 8 | public interface AuthenticationService { // Service de autenticação deve implementar essa interface. 9 | 10 | Token authenticate(Login loginData); // Método com a lógica para se autenticar no sistema. 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import org.springframework.security.core.Authentication; 5 | 6 | 7 | public interface TokenService { // Service do Token deve implementar essa interface. 8 | 9 | String generateToken(Authentication authentication); // método para gerar o token. 10 | 11 | String getSubject(String token); // Método para obter o id do usuário através do token. 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #API URL 2 | server.servlet.context-path=/security-api 3 | 4 | 5 | # PERFIL ATIVO NO MOMENTO 6 | spring.profiles.active=${APP_PROFILE:dev} 7 | 8 | 9 | #SQL 10 | spring.jpa.show-sql=true 11 | spring.jpa.properties.hibernate.format_sql=true 12 | 13 | 14 | #EXTRA CONFIGS 15 | spring.jackson.deserialization.fail-on-unknown-properties=true 16 | spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED 17 | spring.mvc.throw-exception-if-no-handler-found=true 18 | spring.web.resources.add-mappings=false -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/registerUser/RegisterUserArgs.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.registerUser; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | 7 | 8 | public record RegisterUserArgs(RegisterUserDTO registerData, 9 | UserRepository userRepository) { // Argumentos necessários para poder verificar se um usuário pode ser registrado no sistema. 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/SamePasswordException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class SamePasswordException extends RuntimeException { // Quando o usuário vai trocar a senha, e tenta trocar pela a que ele já tinha no banco. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public SamePasswordException() { 13 | super("Your new password cannot be equal the last one"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/changePassword/ChangePasswordVerification.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.changePassword; 2 | 3 | 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | 6 | 7 | public interface ChangePasswordVerification { // Regras de négocio relacionada a troca de senha. 8 | 9 | // SOLID - Utilizado o Open Closed Principle 10 | 11 | void verification(ChangePasswordArgs args); // As classes que implementam essa interface terão que implementar esse método. 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | 36 | ## Outros Arquivos 37 | 38 | countLines.bat 39 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/findUsersByParameter/FindUsersArgs.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.findUsersByParameter; 2 | 3 | 4 | import com.almadavic.securitystandard.repository.UserRepository; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | public record FindUsersArgs(UserRepository userRepository, Pageable pageable, 10 | String roleName) { // Argumentos necessários para poder verificar qual pagina de usuários será retornada. 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/PasswordDoesntMatchRegisterUserException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class PasswordDoesntMatchRegisterUserException extends RuntimeException { // Erro quando o usuário tenta registrar um usuário e os campos da senha não correspondem. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public PasswordDoesntMatchRegisterUserException() { 13 | super("The passwords don't match"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/EmailAlreadyRegisteredException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class EmailAlreadyRegisteredException extends RuntimeException { // Erro quando o usuário tenta cadastrar uma conta com um e-mail que já exista. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public EmailAlreadyRegisteredException(String email) { 13 | super("This e-mail: " + email + " already exists in the system"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/changePassword/ChangePasswordArgs.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.changePassword; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.ChangePassword; 5 | import com.almadavic.securitystandard.entity.User; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | public record ChangePasswordArgs(ChangePassword changePasswordDTO, User user, 9 | PasswordEncoder encoder) { // Argumentos necessários para poder verificar se o o usuário pode mudar sua senha. 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/customException/NicknameAlreadyRegisteredException.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.customException; 2 | 3 | 4 | import java.io.Serial; 5 | 6 | 7 | public class NicknameAlreadyRegisteredException extends RuntimeException { // Erro quando o usuário tenta cadastrar uma conta com um nickname que já exista. 8 | 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public NicknameAlreadyRegisteredException(String nickname) { 13 | super("This nickname: " + nickname + " already exists in the system"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.repository; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.Role; 5 | import com.almadavic.securitystandard.enumerated.RoleName; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.Optional; 10 | 11 | 12 | @Repository // Repositorio de Role ( No geral) 13 | public interface RoleRepository extends JpaRepository { 14 | 15 | Optional findByName(RoleName name); // Método encontra uma role pelo nome 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | #datasource 2 | spring.datasource.url=jdbc:postgresql://localhost:5432/SecuritySkeletonAPI 3 | spring.datasource.username=postgres 4 | spring.datasource.password=1234567 5 | 6 | 7 | #JPA 8 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 9 | spring.jpa.hibernate.ddl-auto=update 10 | 11 | 12 | #JWT 13 | jwt.expiration=8760 14 | jwt.secret=A+X;fTJP&Pd,TD9dwVq(hsHX,ya^et;yz$*\yL5S8EJN:%P:X%H9>#nYLrX}@\s?CQcpspH,2emzBc!Q[V'AYa~uzF8WR~AUrMzxp/V$9([S9X#zj/CH('#]B_Hc+%fGhe27YB;^j4\Xk=Ju"Ap~_&kY^f)sy4:qTq_Ec!-z!@aAp~sLKGU>$ 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/UserAreaService.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.ChangePassword; 5 | import com.almadavic.securitystandard.dto.response.UserDTO; 6 | import com.almadavic.securitystandard.entity.User; 7 | 8 | 9 | public interface UserAreaService { // Service de userArea deve implementar essa interface. 10 | 11 | UserDTO myProfile(User userLogged); // método com a lógica para visualizar o perfil do usuário logado. 12 | 13 | String changePassword(ChangePassword cpDTO, User userLogged); // método com a lógica para alterar a senha. 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | #datasource 2 | spring.datasource.driver-class-name=org.h2.Driver 3 | spring.datasource.url=jdbc:h2:mem:securitystandard 4 | spring.datasource.username=sa 5 | spring.datasource.password= 6 | 7 | 8 | #JPA 9 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 10 | spring.jpa.hibernate.ddl-auto=create 11 | 12 | 13 | #h2 14 | spring.h2.console.enabled=true 15 | spring.h2.console.path=/h2-console 16 | 17 | 18 | #JWT 19 | jwt.expiration=5 20 | jwt.secret=A+X;fTJP&Pd,TD9dwVq(hsHX,ya^et;yz$*\yL5S8EJN:%P:X%H9>#nYLrX}@\s?CQcpspH,2emzBc!Q[V'AYa~uzF8WR~AUrMzxp/V$9([S9X#zj/CH('#]B_Hc+%fGhe27YB;^j4\Xk=Ju"Ap~_&kY^f)sy4:qTq_Ec!-z!@aAp~sLKGU>$ 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/SecurityStandardApplication.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | import org.springframework.data.web.config.EnableSpringDataWebSupport; 8 | 9 | 10 | @SpringBootApplication 11 | @EnableSpringDataWebSupport 12 | // Com essa anotação, habilitamos esse suporte para o Spring pegar da requisição dos parâmetros da url, os campos, as informações de paginação e ordenação. 13 | @EnableCaching // Habilita o cache! 14 | public class SecurityStandardApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(SecurityStandardApplication.class, args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/findUsersByParameter/FindUsersByRoleNameVerification.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.findUsersByParameter; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.data.domain.Page; 7 | 8 | 9 | @AllArgsConstructor 10 | public abstract class FindUsersByRoleNameVerification { // Design Patterns - Chain of Responsibility 11 | // Classe faz com que as classes filhas tenham o atributo e o método no construtor, vai fazendo validações dentro das classes até achar um return correto. 12 | 13 | protected FindUsersByRoleNameVerification nextOne; 14 | 15 | public abstract Page verification(FindUsersArgs args); // Método de verificação que as classes vão herdar. 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/response/RoleDTO.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.response; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.Role; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 7 | import lombok.Getter; 8 | 9 | 10 | @JsonPropertyOrder(value = {"roleName"}) // Estou garantindo a ordem dos atributos no JSON. 11 | @Getter 12 | public class RoleDTO { // DTO que será retornado pro client, representa as roles dos usuarios. 13 | 14 | @JsonProperty(value = "roleName") // Dando nome para o atributo no JSON 15 | private final String roleName; // nome da role 16 | 17 | public RoleDTO(Role role) { 18 | this.roleName = role.getAuthority(); 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return roleName; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/securityConfig/Token.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.securityConfig; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | 9 | 10 | @AllArgsConstructor 11 | @Getter 12 | @JsonPropertyOrder(value = {"token", "type"}) // Estou garantindo a ordem dos atributos no JSON. 13 | public class Token { // Essa classe tem como objetivo retornar para o client o token(que armazena os dados do user "logado") 14 | // e o type quando o usuário se autentica. 15 | 16 | @JsonProperty(value = "token") // Dando nome para o atributo no JSON 17 | private String token; // o token (hash) 18 | 19 | @JsonProperty(value = "type") // Dando nome para o atributo no JSON 20 | private String type; // tipo do token 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/findUsersByParameter/InvalidRoleName.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.findUsersByParameter; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.service.customException.InvalidParamException; 6 | import org.springframework.data.domain.Page; 7 | 8 | 9 | public class InvalidRoleName extends FindUsersByRoleNameVerification { // Validação caso o client passa um parametro errado como role para o findAll 10 | 11 | public InvalidRoleName() { 12 | super(null); 13 | } 14 | 15 | @Override 16 | public Page verification(FindUsersArgs args) { // Se chegar até aqui nessa classe, é porque o parametro está inválido. 17 | 18 | String roleName = args.roleName(); 19 | 20 | throw new InvalidParamException("This parameter (role) : { " + roleName + " } is invalid"); 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/UserService.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 5 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | 10 | 11 | public interface UserService extends UserDetailsService { // Service de user deve implementar essa interface. 12 | 13 | UserMonitoringDTO register(RegisterUserDTO registerData); // método com a lógica para se registrar no sistema. 14 | 15 | Page findAll(Pageable pageable, String roleName); // método com a lógica para retornar uma pagina de usuários registrados no sistema. 16 | 17 | UserMonitoringDTO findById(String id); // método com a lógica para retornar um usuário específico do sistema pelo id. 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/request/ChangePassword.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.request; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | 11 | 12 | @AllArgsConstructor //Usado na parte de TESTES -> Para instanciar um DTO! 13 | @Getter 14 | @JsonPropertyOrder(value = {"yourPassword", "newPassword"}) 15 | public class ChangePassword { // DTO que representa a mudança de senha do usuário. 16 | 17 | @NotBlank // --> Campo obrigatorio! 18 | @JsonProperty(value = "yourPassword") 19 | private String password; // Password do usuário logado. 20 | 21 | @NotBlank // --> Campo obrigatorio! 22 | @Size(min = 6, max = 18) // --> Validação do tamanho da senha 23 | @JsonProperty(value = "newPassword") 24 | private String newPassword; // Nova senha. 25 | 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Victor Almada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/handler/ForbiddenHandler.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.handler; 2 | 3 | 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.security.access.AccessDeniedException; 8 | import org.springframework.security.web.access.AccessDeniedHandler; 9 | 10 | 11 | public class ForbiddenHandler extends AuthorizationAuthenticationHandler implements AccessDeniedHandler { 12 | // Classe trata o erro forbidden e retorna pro usuário de uma maneira mais amigável 13 | 14 | @Override 15 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) { 16 | 17 | if (!response.isCommitted()) { 18 | 19 | status = HttpStatus.FORBIDDEN.value(); 20 | error = "Forbidden"; 21 | messageError = "Access Denied"; 22 | 23 | responseClient(request, response, status, error, messageError); 24 | 25 | } 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/response/UserMonitoringDTO.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.response; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.util.UserMapper; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 8 | import lombok.Getter; 9 | 10 | import java.util.List; 11 | 12 | 13 | @JsonPropertyOrder(value = {"id", "nickname", "email", "roles", "registrationMoment"}) 14 | @Getter 15 | // Estou garantindo a ordem dos atributos no JSON. 16 | public class UserMonitoringDTO extends UserDTO { // DTO que será retornado pro client, representa o usuário ( na visão do administrador). 17 | 18 | @JsonProperty(value = "id") // Dando nome para o atributo no JSON 19 | private final String id; // id do usuário 20 | 21 | @JsonProperty(value = "roles") // Dando nome para o atributo no JSON 22 | private final List rolesDTO; // roles do usuario 23 | 24 | public UserMonitoringDTO(User user) { 25 | super(user); 26 | this.id = user.getId(); 27 | this.rolesDTO = UserMapper.toRoleDTO(user.getAuthorities()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.repository; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.Optional; 12 | 13 | 14 | @Repository 15 | public interface UserRepository extends JpaRepository { 16 | 17 | Optional findByEmail(String email); // Método que retorna um User passando o email. // Método usado para autenticação. 18 | 19 | Optional findByNickname(String nickname); // Método retorna um usuario pelo nickname. 20 | 21 | @Query(value = "SELECT u.id, u.username, u.email, u.password, u.registration_moment FROM tb_users U " + 22 | "INNER JOIN tb_users_roles UR ON U.id = UR.user_id " + 23 | "INNER JOIN tb_roles R ON UR.role_id = R.id WHERE r.name = :roleName", nativeQuery = true) 24 | Page findByRole(Pageable pageable, String roleName); // Query criada para retornar a página com usuários que tenham o nome da role como informado. 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/request/Login.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.request; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import jakarta.validation.constraints.Email; 7 | import jakarta.validation.constraints.NotBlank; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | 12 | 13 | @AllArgsConstructor //Usado na parte de TESTES -> Para instanciar um DTO! 14 | @Getter 15 | @JsonPropertyOrder(value = {"email", "password"}) 16 | public class Login { // DTO que representa o Login do usuário, email e senha! 17 | 18 | @NotBlank // --> Campo obrigatorio! 19 | @Email // --> Validação se está em formato de e-mail 20 | @JsonProperty(value = "email") 21 | private String email; // email do usuário 22 | 23 | @NotBlank // --> Campo obrigatorio! 24 | @JsonProperty(value = "password") 25 | private String password; // senha do usuário 26 | 27 | public UsernamePasswordAuthenticationToken toConvert() { // Converter esses dados informados pelo usuário em um token. 28 | return new UsernamePasswordAuthenticationToken(email, password); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/securityConfig/SecurityConfigurations.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.securityConfig; 2 | 3 | 4 | import org.springframework.security.authentication.AuthenticationManager; 5 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.security.web.SecurityFilterChain; 10 | import org.springframework.web.cors.CorsConfigurationSource; 11 | 12 | 13 | public interface SecurityConfigurations { // Interface SecurityConfigurations 14 | 15 | PasswordEncoder encoder(); // Encoder de senha, codifica a senha. 16 | 17 | // Autentica o usuário no sistema. 18 | AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception; 19 | 20 | // Configurações de autorização 21 | SecurityFilterChain filterChain(HttpSecurity http) throws Exception; 22 | 23 | // Permite que o servidor seja consumido pelo front-end. 24 | CorsConfigurationSource corsConfigurationSource(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/findUsersByParameter/NoRoleName.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.findUsersByParameter; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | 10 | public class NoRoleName extends FindUsersByRoleNameVerification { // Validação caso o client não passe nenhum parametro como role para o findAll 11 | 12 | public NoRoleName(FindUsersByRoleNameVerification nextOne) { 13 | super(nextOne); 14 | } 15 | 16 | @Override 17 | public Page verification(FindUsersArgs args) { 18 | 19 | String roleName = args.roleName(); // role supostamente passada pelo usuário. 20 | Pageable pageable = args.pageable(); // Paginação. 21 | UserRepository userRepository = args.userRepository(); // repository para retornar a página. 22 | 23 | if (roleName == null) { // Se não passar nenhum parametro 24 | return userRepository.findAll(pageable); // Vai retornar o page normal 25 | } 26 | 27 | return nextOne.verification(args); // Caso não, vai para a proxima validação (da proxima classe). 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/standardError/validationArgsStandardError/StandardErrorArgsNotValid.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.standardError.validationArgsStandardError; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | 10 | 11 | @JsonPropertyOrder(value = {"field", "message"}) // ordena como os atributos serão mostrados no JSON. 12 | public class StandardErrorArgsNotValid implements Serializable { // Classe de formatação de exception! Será retornado o erro de uma forma mais agradável para o cliente. 13 | // Essa classe será a classe de formatação de erros de validação. 14 | 15 | @Serial 16 | private static final long serialVersionUID = 1L; 17 | 18 | @JsonProperty(value = "field") // -> nome do campo no JSON 19 | private final String field; // -> Campo que apresentou algum erro por não seguir regras de validação. 20 | 21 | @JsonProperty(value = "message") // -> nome do campo no JSON. 22 | private final String message; // -> Mensagem do erro 23 | 24 | public StandardErrorArgsNotValid(String field, String message) { 25 | this.field = field; 26 | this.message = message; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/registerUser/PasswordMatchRegisterUser.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.registerUser; 2 | 3 | 4 | import com.almadavic.securitystandard.service.customException.PasswordDoesntMatchRegisterUserException; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.stereotype.Component; 7 | 8 | 9 | @Component 10 | // Indica que essa classe será gerenciada automaticamente pelo Spring em momento de execução, não precisaremos instanciar manualmente. 11 | @Order(3) // Ordem de chamada da classe, no caso de uma lista por exemplo. 12 | public class PasswordMatchRegisterUser implements RegisterUserVerification { // Verifica se as senhas que o usuário passou para se cadastrar são iguais. 13 | 14 | @Override 15 | public void verification(RegisterUserArgs args) { // Método que faz a verificação. 16 | 17 | String password = args.registerData().getPassword(); // Password inserida pelo usuário 18 | String confirmationPassword = args.registerData().getConfirmationPassword(); // Password de confirmação insirida pelo usuário ( tem que ser identica a de cima) 19 | 20 | if (!password.equals(confirmationPassword)) { 21 | throw new PasswordDoesntMatchRegisterUserException(); // Se for diferente, será lançada uma exception. 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/changePassword/DifferentPassword.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.changePassword; 2 | 3 | 4 | import com.almadavic.securitystandard.service.customException.SamePasswordException; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | @Component 11 | // Indica que essa classe será gerenciada automaticamente pelo Spring em momento de execução, não precisaremos instanciar manualmente. 12 | @Order(2) // Ordem de chamada da classe, no caso de uma lista por exemplo. 13 | public class DifferentPassword implements ChangePasswordVerification { // Regra de négocio que verifica se o usuário está 14 | // tentando modificar a senha dela para a mesma que a antiga. 15 | 16 | @Override 17 | public void verification(ChangePasswordArgs args) { // Método que faz a verificação. 18 | 19 | String newPassword = args.changePasswordDTO().getNewPassword(); // Nova senha que o usuário está tentando cadastrar. 20 | 21 | String passwordDataBase = args.changePasswordDTO().getPassword(); // Senha do banco de dados 22 | 23 | if(newPassword.equals(passwordDataBase)) { 24 | throw new SamePasswordException(); // Se for igual, lança a exception 25 | } 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/response/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.response; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 8 | import lombok.Getter; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | 13 | @JsonPropertyOrder(value = {"nickname", "email", "registrationMoment"}) 14 | @Getter 15 | // Estou garantindo a ordem dos atributos no JSON. 16 | public class UserDTO { // DTO que será retornado pro client, representa o usuário (perfil na visão do usuario). 17 | 18 | @JsonProperty(value = "nickname") // Dando nome para o atributo no JSON 19 | private final String nickname; // username do usuario 20 | 21 | @JsonProperty(value = "email") // Dando nome para o atributo no JSON 22 | private final String email; // email do usuario 23 | 24 | @JsonProperty(value = "registrationMoment") // Dando nome para o atributo no JSON 25 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy HH:mm") 26 | private final LocalDateTime registrationMoment; // momento de registro do usuário 27 | 28 | public UserDTO(User user) { 29 | this.nickname = user.getNickname(); 30 | this.email = user.getUsername(); 31 | this.registrationMoment = user.getRegistrationMoment(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/handler/UnauthorizedHandler.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.handler; 2 | 3 | 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.AuthenticationEntryPoint; 9 | 10 | 11 | public class UnauthorizedHandler extends AuthorizationAuthenticationHandler implements AuthenticationEntryPoint { 12 | // O Spring retorna 403 ao inves do 401, então tive que criar essa classe 13 | // que para quando o usuário não estiver autenticado, retornar o 401 ao inves do 403. Quando o usuário está autenticado e não autorizado, o 403 continua 14 | // funcionando normalmente. 15 | 16 | @Override 17 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) { // Método criado para retornar o erro 18 | // 401 de uma forma mais amigável. 19 | 20 | if (!response.isCommitted()) { 21 | 22 | 23 | status = HttpStatus.UNAUTHORIZED.value(); 24 | error = "Unhautorized"; 25 | messageError = "In order to access this recource, you have to be logged in the system"; 26 | 27 | responseClient(request, response, status, error, messageError); 28 | 29 | } 30 | 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/dto/request/RegisterUserDTO.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.dto.request; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import jakarta.validation.constraints.Email; 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.Size; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | 12 | 13 | @AllArgsConstructor //Usado na parte de TESTES -> Para instanciar um DTO! 14 | @Getter 15 | @JsonPropertyOrder(value = {"nickname", "email", "password", "confirmationPassword"}) 16 | public class RegisterUserDTO { // DTO que representa o cadastro do usuário no sistema. 17 | 18 | @NotBlank // --> Campo obrigatorio! 19 | @JsonProperty(value = "nickname") 20 | private String nickname; // apelido (username) para ser cadastrado 21 | 22 | @NotBlank // --> Campo obrigatorio! 23 | @Email // --> Validação se está em formato de e-mail 24 | @JsonProperty(value = "email") 25 | private String email; // email do usuário 26 | 27 | @Size(min = 6, max = 18) // --> Validação do tamanho da senha 28 | @JsonProperty(value = "password") 29 | private String password; // nova senha para ser cadastrada 30 | 31 | @NotBlank // --> Campo obrigatorio! 32 | @JsonProperty(value = "confirmationPassword") 33 | private String confirmationPassword; // confirmação de senha 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/changePassword/PasswordMatchsDataBasePassword.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.changePassword; 2 | 3 | 4 | import com.almadavic.securitystandard.service.customException.DatabaseException; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | @Component 11 | // Indica que essa classe será gerenciada automaticamente pelo Spring em momento de execução, não precisaremos instanciar manualmente. 12 | @Order(1) // Ordem de chamada da classe, no caso de uma lista por exemplo. 13 | public class PasswordMatchsDataBasePassword implements ChangePasswordVerification { // Regra de négocio que verifica se o 14 | // usuário está digitando sua senha corretamente para poder muda-lá. 15 | 16 | @Override 17 | public void verification(ChangePasswordArgs args) { // Método que faz a verificação 18 | 19 | String passwordDTO = args.changePasswordDTO().getPassword(); // Senha para o usuário digitar (que seja igual a do banco) 20 | 21 | String passwordDataBase = args.user().getPassword(); // Senha do banco de dados. 22 | 23 | PasswordEncoder encoder = args.encoder(); // Verificará se as senhas combinam. 24 | 25 | 26 | if (!encoder.matches(passwordDTO,passwordDataBase)) { // Verifica se as senhas são iguais. 27 | throw new DatabaseException("The password is not correct (not match)"); // Se for diferente, lança a exception 28 | } 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/AuthenticationControllerImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.config.securityConfig.Token; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import com.almadavic.securitystandard.service.serviceAction.AuthenticationService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | 15 | @RequiredArgsConstructor 16 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 17 | @RestController // Identificando que é um rest-controller 18 | @RequestMapping(value = "/auth") // Recurso para "encontrar" esse controller 19 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 20 | public class AuthenticationControllerImpl implements AuthenticationController { // Controller para fazer a autenticação 21 | 22 | private final AuthenticationService authService; // Service onde terá a lógica relacionada a autenticação do usuário no sistema. 23 | 24 | @Override 25 | @PostMapping // Método HTTP POST 26 | public ResponseEntity authenticate(Login loginData) { // Método para se autenticar no client. 27 | return ResponseEntity.ok().body(authService.authenticate(loginData)); // Token que será retornado para o Client. 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/registerUser/EmailAlreadyRegistered.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.registerUser; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | import com.almadavic.securitystandard.service.customException.EmailAlreadyRegisteredException; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Optional; 11 | 12 | 13 | @Component 14 | // Indica que essa classe será gerenciada automaticamente pelo Spring em momento de execução, não precisaremos instanciar manualmente. 15 | @Order(2) // Ordem de chamada da classe, no caso de uma lista por exemplo. 16 | public class EmailAlreadyRegistered implements RegisterUserVerification { // Se o usuário já tiver registrada no sistema (email), outra não pode registrar com o mesmo email. 17 | 18 | @Override 19 | public void verification(RegisterUserArgs args) { // Método que faz a verificação. 20 | 21 | String email = args.registerData().getEmail(); // e-mail com que o usuário pretende se cadastrar no sistema. 22 | 23 | UserRepository userRepository = args.userRepository(); // Repository para se conectar com o banco. 24 | 25 | Optional user = userRepository.findByEmail(email); // Retorna um Optional indicando se já existe algum usuário com aquele e-mail no banco. 26 | 27 | if (user.isPresent()) { 28 | throw new EmailAlreadyRegisteredException(email); // Se tiver, vai dar erro, pois não pode ter 2 e-mails iguais. 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/registerUser/NicknameAlreadyRegistered.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.registerUser; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | import com.almadavic.securitystandard.service.customException.NicknameAlreadyRegisteredException; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Optional; 11 | 12 | 13 | @Component 14 | // Indica que essa classe será gerenciada automaticamente pelo Spring em momento de execução, não precisaremos instanciar manualmente. 15 | @Order(1) // Ordem de chamada da classe, no caso de uma lista por exemplo. 16 | public class NicknameAlreadyRegistered implements RegisterUserVerification { // Se o usuário já tiver registrada no sistema (email), outra n pode registrar com o msm email. 17 | 18 | @Override 19 | public void verification(RegisterUserArgs args) { // Método que faz a verificação. 20 | 21 | String nickname = args.registerData().getNickname(); // username com que o usuário pretende se cadastrar. 22 | UserRepository userRepository = args.userRepository(); // Repository para se conectar com o banco. 23 | 24 | Optional user = userRepository.findByNickname(nickname); // Retorna um Optional indicando se já existe algum usuário com aquele nickname no banco. 25 | 26 | if (user.isPresent()) { 27 | throw new NicknameAlreadyRegisteredException(nickname); // Se tiver, vai dar erro, pois não pode ter 2 nicknames iguais. 28 | } 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/businessRule/findUsersByParameter/ValidRoleName.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.businessRule.findUsersByParameter; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | 10 | public class ValidRoleName extends FindUsersByRoleNameVerification { // Validação caso o client passa um parametro correto como role para o findAll 11 | 12 | public ValidRoleName(FindUsersByRoleNameVerification nextOne) { 13 | super(nextOne); 14 | } 15 | 16 | @Override 17 | public Page verification(FindUsersArgs args) { 18 | 19 | String roleName = args.roleName(); // role passada pelo usuário. 20 | Pageable pageable = args.pageable(); // Paginação. 21 | UserRepository userRepository = args.userRepository(); // repository para retornar a página. 22 | 23 | String role = "ROLE_" + roleName.toUpperCase(); 24 | 25 | boolean validParameter = validParameter(role); 26 | 27 | if (validParameter) { // Se a role estiver certa. 28 | return userRepository.findByRole(pageable, role); // Vai retornar todos os usuários que contém aquela ROLE 29 | } 30 | 31 | return nextOne.verification(args); // Caso não aconteça, vai chamar a proxima validação (da proxima classe). 32 | 33 | } 34 | 35 | private boolean validParameter (String role) { // Método para verificar se a role passada pelo usuário é válida. 36 | return role.equalsIgnoreCase("ROLE_ADMIN") || role.equalsIgnoreCase("ROLE_USER"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/service/userAreaService/MyProfile.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.userAreaService; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.response.UserDTO; 5 | import com.almadavic.securitystandard.entity.User; 6 | import com.almadavic.securitystandard.repository.UserRepository; 7 | import com.almadavic.securitystandard.service.serviceAction.UserAreaService; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | 14 | 15 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 16 | @SpringBootTest 17 | public class MyProfile { // Classe que testa o service associado com a funcionalidade MyProfile. 18 | 19 | @Autowired 20 | private UserAreaService userAreaService; // Usado para testar o método MyProfile. 21 | 22 | @Autowired 23 | private UserRepository userRepository; // Usado para buscar um usuário do banco. 24 | 25 | @Test 26 | void loggedUserData() { 27 | 28 | User user = userRepository.findByEmail("user2@hotmail.com").get(); // Usuário do banco de dados. 29 | 30 | UserDTO userDTO = userAreaService.myProfile(user); // Quando passado o usuário, o método retorna um DTO desse usuário. 31 | 32 | Assertions.assertEquals(user.getNickname(), userDTO.getNickname()); // O nome do DTO tem que ser o mesmo do usuário no banco. 33 | Assertions.assertEquals(user.getUsername(), userDTO.getEmail()); // O e-mail do DTO tem que ser o mesmo do usuário no banco. 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/SwaggerConfigurations.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config; 2 | 3 | 4 | import io.swagger.v3.oas.models.Components; 5 | import io.swagger.v3.oas.models.ExternalDocumentation; 6 | import io.swagger.v3.oas.models.OpenAPI; 7 | import io.swagger.v3.oas.models.info.Info; 8 | import io.swagger.v3.oas.models.info.License; 9 | import io.swagger.v3.oas.models.security.SecurityScheme; 10 | import org.springdoc.core.models.GroupedOpenApi; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | 15 | @Configuration 16 | public class SwaggerConfigurations { // Configurações do SWAGGER ! 17 | 18 | @Bean 19 | public GroupedOpenApi publicApi() { // Build 20 | 21 | return GroupedOpenApi.builder() 22 | .group("SecuritySkeletonAPI") 23 | .pathsToMatch("/**") 24 | .build(); 25 | 26 | } 27 | 28 | @Bean 29 | public OpenAPI almadaAPI() { // Nesse método estão algumas configurações que apareceram na interface do swagger e confs de autorização. 30 | 31 | return new OpenAPI() 32 | .info(new Info().title("SecuritySkeletonAPI") 33 | .description("My standard project for authentication and authorization.") 34 | .version("v0.0.1") 35 | .license(new License().name("Spring Doc").url("http://springdoc.org"))) 36 | .components(new Components().addSecuritySchemes("bearer-key", new SecurityScheme().type(SecurityScheme.Type.HTTP) 37 | .scheme("bearer") 38 | .bearerFormat("JWT"))) 39 | .externalDocs(new ExternalDocumentation()); 40 | 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/AuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError.StandardError; 5 | import com.almadavic.securitystandard.config.securityConfig.Token; 6 | import com.almadavic.securitystandard.dto.request.Login; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.media.Content; 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 11 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 12 | import io.swagger.v3.oas.annotations.tags.Tag; 13 | import jakarta.validation.Valid; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | 17 | 18 | @Tag(name = "authentication", description = "Authenticate in the system") 19 | // Como será chamado o nome do "controller" no swagger e a descrição. 20 | public interface AuthenticationController { // Controller de autenticação deve implementar essa interface. 21 | 22 | @Operation(summary = "Sign in the system.") // Summary - Documentação no Swagger 23 | @ApiResponses(value = { // Informações relacionadas ao response! 24 | @ApiResponse(responseCode = "200", description = "User logged", 25 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Token.class))}), 26 | @ApiResponse(responseCode = "400", description = "E-mail and / or password are / is wrong", 27 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}) 28 | }) 29 | ResponseEntity authenticate(@RequestBody @Valid Login loginData); // Método para se autenticar no sistema 30 | 31 | } -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/service/userService/RegistrationUserTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.userService; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 5 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 6 | import com.almadavic.securitystandard.enumerated.RoleName; 7 | import com.almadavic.securitystandard.service.serviceAction.UserService; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | 14 | 15 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 16 | @SpringBootTest 17 | public class RegistrationUserTest { // Classe que testa o service associado com a funcionalidade de registrar um usuário no sistema. 18 | 19 | @Autowired 20 | private UserService userService; // Usado para testar o método de registrar um usuário no sistema. 21 | 22 | @Test 23 | void returnRegisterData() { 24 | 25 | RegisterUserDTO userRequestDTO = new RegisterUserDTO("paulo", "paulo@hotmail.com", "1234567", "1234567"); // DTO de registro de usuário. 26 | 27 | UserMonitoringDTO userResponseDTO = userService.register(userRequestDTO); // O método recebe um DTO de registro e retorna um DTO de usuário. 28 | 29 | Assertions.assertEquals(userRequestDTO.getNickname(), userResponseDTO.getNickname()); // O nome do DTO tem que ser o mesmo do usuário no banco. 30 | Assertions.assertEquals(userRequestDTO.getEmail(), userResponseDTO.getEmail()); // O email do DTO tem que ser o mesmo do usuário no banco. 31 | Assertions.assertEquals(userResponseDTO.getRolesDTO().get(0).getRoleName(), RoleName.ROLE_USER.name()); // A role do usuário cadastrado, tem que ser a role = USER. 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/standardError/commonStandardError/StandardError.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 7 | 8 | import java.io.Serial; 9 | import java.io.Serializable; 10 | import java.time.Instant; 11 | 12 | 13 | @JsonPropertyOrder(value = {"timestamp", "status", "error", "message", "path"}) 14 | // ordena como os atributos serão mostrados no JSON. 15 | public class StandardError implements Serializable { // Classe de formatação de exception! Será retornado o erro de uma forma mais agradável para o cliente. 16 | // Classe de formatação de erros comuns 17 | 18 | @Serial 19 | private static final long serialVersionUID = 1L; 20 | 21 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy HH:mm", timezone = "GMT-3") 22 | // O tempo aparecer formatado pro cliente (JSON). 23 | @JsonProperty(value = "timestamp") // -> nome do campo no JSON 24 | private final Instant timestamp; // -> Tempo em que o erro ocorreu. 25 | 26 | @JsonProperty(value = "status") // -> nome do campo no JSON 27 | private final Integer status; // -> Status HTTP 28 | 29 | @JsonProperty(value = "error") // -> nome do campo no JSON 30 | private final String error; // -> Erro 31 | 32 | @JsonProperty(value = "message") // -> nome do campo no JSON 33 | private final String message; // -> Mensagem do erro 34 | 35 | @JsonProperty(value = "path") // -> nome do campo no JSON 36 | private final String path; // -> URI da requisição 37 | 38 | public StandardError(Integer status, String error, String message, String path) { 39 | this.timestamp = Instant.now(); 40 | this.status = status; 41 | this.error = error; 42 | this.message = message; 43 | this.path = path; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/AuthenticationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.config.securityConfig.Token; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import com.almadavic.securitystandard.service.customException.DatabaseException; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.security.authentication.AuthenticationManager; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.AuthenticationException; 13 | import org.springframework.stereotype.Service; 14 | 15 | 16 | @Service // Indica que é uma camada de serviço , o spring vai gerenciar automaticamente. 17 | @RequiredArgsConstructor 18 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 19 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 20 | public class AuthenticationServiceImpl implements AuthenticationService { // Serviço relacionado a autenticação do usuário no sistema. 21 | 22 | private final TokenService tokenService; // injeção de dependencia do TokenService -> gerar o token. 23 | 24 | private final AuthenticationManager authManager; // Injeção de dependencia automatica - > AuthenticationManager 25 | 26 | @Override 27 | public Token authenticate(Login loginData) { // Método para fazer o login e se autenticar no sistema. 28 | 29 | try { 30 | 31 | return new Token(tokenService.generateToken(authManager.authenticate(loginData.toConvert())), "Bearer"); 32 | 33 | } catch (AuthenticationException e) { 34 | throw new DatabaseException("E-mail and / or password is / are wrong!"); // Causará um erro caso os dados passados pelo usuário estejam errados. 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/UserAreaControllerImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.ChangePassword; 5 | import com.almadavic.securitystandard.dto.response.UserDTO; 6 | import com.almadavic.securitystandard.entity.User; 7 | import com.almadavic.securitystandard.service.serviceAction.UserAreaService; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.context.annotation.Primary; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | 17 | @RequiredArgsConstructor 18 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 19 | @RestController // Identificando que é um rest-controller 20 | @RequestMapping(value = "/userarea") // Recurso para "encontrar" esse controller 21 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 22 | public class UserAreaControllerImpl implements UserAreaController { // Controller onde os usuários interagem, (ÁREA DO USUÁRIO) 23 | 24 | private final UserAreaService userAreaService; // Service onde terá a lógica relacionada a área do usuário no sistema. 25 | 26 | @Override 27 | @GetMapping(value = "/myprofile") // Método HTTP GET -> Obter / Recuperar 28 | public ResponseEntity myProfile(User userLogged) { ///Método para visualizar "area do usuário" no sistema, ver dados do usuário logado. 29 | return ResponseEntity.ok().body(userAreaService.myProfile(userLogged)); 30 | } 31 | 32 | @Override 33 | @PutMapping(value = "/changepassword") // Método HTTP PUT - > ALTERAR / UPDATE 34 | public ResponseEntity changePassword(ChangePassword cpDTO, User userLogged) { // Método para alterar a senha da conta no sistema. 35 | return ResponseEntity.ok().body(userAreaService.changePassword(cpDTO, userLogged)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/standardError/validationArgsStandardError/ValidationErrorCollection.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.standardError.validationArgsStandardError; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 7 | 8 | import java.io.Serial; 9 | import java.io.Serializable; 10 | import java.time.Instant; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | 15 | @JsonPropertyOrder(value = {"timestamp", "status", "error", "validationErrorList", "path"}) 16 | // ordena como os atributos serão mostrados no JSON. 17 | public class ValidationErrorCollection implements Serializable { // Classe que será uma lista de exceptions de validation. 18 | 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | 22 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy HH:mm", timezone = "GMT-3") 23 | // Formatação da data no JSON 24 | @JsonProperty(value = "timestamp") // -> nome do campo no JSON 25 | private final Instant timestamp; // -> Tempo em que o erro ocorreu. 26 | 27 | @JsonProperty(value = "status") // -> nome do campo no JSON 28 | private final Integer status; // -> Status HTTP 29 | 30 | @JsonProperty(value = "error") // -> nome do campo no JSON 31 | private final String error; // -> Erro 32 | 33 | @JsonProperty(value = "path") // -> nome do campo no JSON 34 | private final String path; // -> URI da requisição 35 | 36 | @JsonProperty(value = "validationErrorList") // -> nome do campo no JSON 37 | private final List validationErrorList = new ArrayList<>(); // -> Lista com erros de validação 38 | 39 | public ValidationErrorCollection(Integer status, String path, String error) { 40 | this.timestamp = Instant.now(); 41 | this.status = status; 42 | this.path = path; 43 | this.error = error; 44 | } 45 | 46 | public void addStandardErrorArgsNotValid(StandardErrorArgsNotValid standard) { // Método para adicionar um erro de validação á lista. 47 | validationErrorList.add(standard); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/handler/AuthorizationAuthenticationHandler.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.handler; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError.StandardError; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import jakarta.servlet.http.HttpServletResponse; 9 | 10 | import java.io.IOException; 11 | 12 | 13 | public abstract class AuthorizationAuthenticationHandler { // Essa classe será utilizada por 2 classes handles, a classe atual tem o papel de retornar a 14 | //resposta para o cliente. Essa resposta será enviada dependendo em qual handler for chamado ( em 1 classe retorna a mensagem de um jeito, na outra, 15 | // de outro jeito. Forbidden ou Unauthorized. 16 | 17 | protected int status; // status da resposta 18 | 19 | protected String error; // o erro 20 | 21 | protected String messageError; // mensagem do erro 22 | 23 | private final ObjectMapper objectMapper = new ObjectMapper(); // "Transformar" o objeto em json 24 | 25 | protected void responseClient(HttpServletRequest request, HttpServletResponse response, int status, String error, String messageError) { // Faz com que a resposta seja retornada 26 | //ao cliente, e além disso, aplica alguns parâmetros 27 | try { // na resposta. 28 | 29 | objectMapper.registerModule(new JavaTimeModule()); 30 | 31 | response.setStatus(status); 32 | response.setContentType("application/json"); 33 | 34 | String uri = request.getRequestURI(); 35 | 36 | StandardError err = new StandardError(status, error, messageError, uri); 37 | 38 | response.getWriter().write(objectMapper.writeValueAsString(err)); 39 | 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/util/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.util; 2 | 3 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 4 | import com.almadavic.securitystandard.dto.response.RoleDTO; 5 | import com.almadavic.securitystandard.dto.response.UserDTO; 6 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 7 | import com.almadavic.securitystandard.entity.Role; 8 | import com.almadavic.securitystandard.entity.User; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @Component 17 | public class UserMapper { // Classe para fazer algumas conversões de DTO para User e User para DTO. 18 | 19 | public static User toUserEntity(RegisterUserDTO registerData, PasswordEncoder encoder) { 20 | 21 | return User.builder() 22 | .nickname(registerData.getNickname()) 23 | .email(registerData.getEmail()) 24 | .password(encoder.encode(registerData.getPassword())) // encoder --> codifica a senha ( mais informações na declaração do método). 25 | .build(); // -> Utilizado um builder na contrução do objeto pois é mais légivel e evita passar parametros errados. 26 | 27 | } 28 | 29 | public static UserDTO toUserDTO(User user) { // Converte uma entidade usuário em usuário DTO. 30 | return new UserDTO(user); 31 | } 32 | 33 | public static UserMonitoringDTO toUserMonitoringDTO(User user) { // Converte uma entidade usuário em Usuário DTO de monitoramento. 34 | return new UserMonitoringDTO(user); 35 | } 36 | 37 | public static Page toUserMonitoringDTO(Page users) { // Método converte uma página de usuários (entidade) para uma página de usuários DTO. 38 | return users.map(UserMonitoringDTO::new); // Convertendo de entidade para DTO cada elemento da lista e retornando essa nova lista DTO. 39 | } 40 | 41 | public static List toRoleDTO(List roles) { // Método para converter uma lista de Role para RoleDTO 42 | return roles.stream().map(RoleDTO::new).collect(Collectors.toList()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/userAreaController/MyProfileTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.userAreaController; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 13 | 14 | 15 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 16 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 17 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 18 | public class MyProfileTest extends ClassTestParent { // Classe testa o recurso /myprofile 19 | 20 | private final String path = "/userarea/myprofile"; 21 | 22 | @Test 23 | void myProfileAdmLogged() throws Exception { // Teste deve passar, acessar o recurso com um usuário role = administrador logado. 24 | 25 | Login loginData = new Login("admin@hotmail.com", "123456"); // DTO de Login que passamos na requisição para logar. 26 | 27 | String token = authenticate(loginData); // Loga o usuário no sistema através do DTO e retorna o token pora ser enviado nas próxima requisição. 28 | 29 | mockMvc.perform(get(path) // Caminho da requisição. 30 | .header("Authorization", token)) // Token que será enviado na requisição. 31 | .andExpect(status().is(ok)); // Status de resposta esperado. 32 | 33 | } 34 | 35 | @Test 36 | void myProfileUserLogged() throws Exception { // Teste deve passar, acessar o recurso com um usuário role = user logado. 37 | 38 | Login loginData = new Login("user2@hotmail.com", "123456"); 39 | 40 | String token = authenticate(loginData); 41 | 42 | mockMvc.perform(get(path) 43 | .header("Authorization", token)) 44 | .andExpect(status().is(ok)); 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.entity; 2 | 3 | 4 | import com.almadavic.securitystandard.enumerated.RoleName; 5 | import jakarta.persistence.*; 6 | import lombok.*; 7 | import org.springframework.security.core.GrantedAuthority; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | 13 | @Entity // Indica que será uma tabela do banco de dados. 14 | @Table(name = "tb_roles") // nome da tabela do banco de dados, será uma tabela de roles. 15 | @NoArgsConstructor // No caso, não está sendo usado pelo programador mas sim pelo JPA debaixo dos panos. 16 | @Builder // Cria um builder baseado no construtor da linha de baixo. 17 | @NonNull 18 | @AllArgsConstructor // No caso, só haveria 1 atributo no construtor, o name, pois não tem o final antes do atributo. 19 | @Getter // Cria os getters dos atributos 20 | @Setter // Cria os setters dos atributos 21 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) // HashCode and equals apenas com os atributos marcados com @Include. 22 | public class Role implements GrantedAuthority { // A classe que representam os perfis, roles, permissões de cada Usuário! 23 | 24 | @Id // Indica que será a PRIMARY-KEY 25 | @EqualsAndHashCode.Include // O hashCode irá usar esse atributo para destinguir um objeto de outro. 26 | @Column(name = "ID", nullable = false, unique = true) 27 | // name -> nome da coluna, nullable -> se pode ser nullo, unique -> o valor do campo não pode ser repetido 28 | private final String id = UUID.randomUUID().toString(); // Representa qual atributo será essa PRIMARY-KEY e a tipagem, no Caso será um UUID devido a segurança... 29 | 30 | @Column(name = "NAME", nullable = false, unique = true) 31 | @Getter(AccessLevel.NONE) // Não é necessário criar um getter para esse atributo, pois por padrão, já temos, devido a INTERFACE GRANTEDAUTHORITY. 32 | @Enumerated(EnumType.STRING) // Transforma um enum em uma String. 33 | private RoleName name; // Representa o nome da role. 34 | 35 | @ManyToMany(mappedBy = "roles") // Como está mapeado na outra classe. 36 | private final List users = new ArrayList<>(); // Um perfil tem uma lista de usuários associados a ele. 37 | //O final foi usado pois não é interessante alterar a lista, e sim adicionar e remover os atributos. 38 | 39 | @Override 40 | public String getAuthority() { 41 | return name.toString(); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "Id: " + id + ", Name: " + getAuthority(); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/ClassTestParent.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.Login; 5 | import com.almadavic.securitystandard.service.serviceAction.TokenService; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | 15 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 16 | public abstract class ClassTestParent { // As classes que vão herdar essa classe, vão ter acesso aos atributos e ao método, esse foi o objetivo de criação dessa classe. 17 | 18 | protected final int forbidden = 403; // Indica que a pessoa está logada mas não tem acesso para acessar tal recurso. 19 | protected final int unauthorized = 401; // Indica que a pessoa não está logada, portanto não pode acessar o recurso. 20 | protected final int badRequest = 400; // Indica que foi passado algo errado na requisição. 21 | protected final int ok = 200; // Indica que está tudo certo, não teve nenhum problema. 22 | protected final int internalServerError = 500; // Indica que ocorreu algum erro no servidor. 23 | protected final int notFound = 404; // Indica que o recurso não foi encontrado. 24 | protected final int created = 201; // Indica que um recurso foi criado no sistema. 25 | 26 | @Autowired 27 | protected MockMvc mockMvc; // Serve para fazer MOCK, no caso, estamos testando a autorização através de MOCK. 28 | 29 | @Autowired 30 | protected ObjectMapper objectMapper; // Utilizado para enviar um objeto JSON na requisição. 31 | 32 | @Autowired 33 | private AuthenticationManager authManager; // Injeção de dependência para logar um usuário no sistema. 34 | 35 | @Autowired 36 | private TokenService tokenService; // Injeção de dependência para gerar o token para ser utilizado em uma próxima requisição. 37 | 38 | protected String authenticate(Login loginData) { // Autentica o usuário 39 | 40 | UsernamePasswordAuthenticationToken login = loginData.toConvert(); // converter os dados passado pelo usuario em um token de autenticação. 41 | 42 | Authentication authentication = authManager.authenticate(login); // autenticar usuário com base nos dados informados por ele. 43 | 44 | String token = tokenService.generateToken(authentication); // Gera o token. 45 | 46 | return "Bearer " + token; // Retorna o token ao client. 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/UserAreaServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.ChangePassword; 5 | import com.almadavic.securitystandard.dto.response.UserDTO; 6 | import com.almadavic.securitystandard.entity.User; 7 | import com.almadavic.securitystandard.repository.UserRepository; 8 | import com.almadavic.securitystandard.service.businessRule.changePassword.ChangePasswordArgs; 9 | import com.almadavic.securitystandard.service.businessRule.changePassword.ChangePasswordVerification; 10 | import com.almadavic.securitystandard.util.UserMapper; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.List; 16 | 17 | 18 | @Service // Indica que é uma camada de serviço , o spring vai gerenciar automaticamente. 19 | @RequiredArgsConstructor 20 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 21 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 22 | public class UserAreaServiceImpl extends ServiceParent implements UserAreaService { // Serviço relacionado a area do usuário no sistema. 23 | 24 | private final UserRepository userRepository; // injeção de dependencia de UserRepository -> salvar, atualizar e buscar um usuário do banco de dados. 25 | 26 | private final List changePasswordVerifications; // Lista com regras de négocio (verificacão) relacionadas a troca de senha. 27 | 28 | @Override 29 | public UserDTO myProfile(User userLogged) { // Método recebe o usuário logado. 30 | return UserMapper.toUserDTO(userLogged); // retorna o DTO desse usuário para o client. 31 | } 32 | 33 | @Override 34 | public String changePassword(ChangePassword cpDTO, User userLogged) { // Método altera a senha da conta de um usuário. 35 | 36 | changePasswordVerifications.forEach(v -> v.verification(new ChangePasswordArgs(cpDTO, userLogged, encoder))); // verificações se os dados informados estão válidos para alteração de senha. 37 | 38 | updatePassword(cpDTO, userLogged); // método atualiza a senha do usuário (mais informações na declaração do método). 39 | 40 | userRepository.save(userLogged); // salva o usuário com A SENHA ALTERADA no banco de dados. 41 | 42 | return "Password changed successfully!"; // retorna uma mensagem que a alteração de senha foi feita com sucesso 43 | 44 | } 45 | 46 | private void updatePassword(ChangePassword cpDTO, User userLogged) { // Método obtem a senha (nova) passada pelo usuário e altera a senha da conta com essa nova senha passada. 47 | userLogged.setPassword(encoder.encode(cpDTO.getNewPassword())); // Seta a senha codificada no usuário. 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/UserControllerImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 5 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 6 | import com.almadavic.securitystandard.service.serviceAction.UserService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.util.UriComponentsBuilder; 17 | 18 | import java.net.URI; 19 | 20 | 21 | @RequiredArgsConstructor 22 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 23 | @RestController // Identificando que é um rest-controller 24 | @RequestMapping(value = "/users") // Recurso para "encontrar" esse controller 25 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 26 | public class UserControllerImpl implements UserController { // Controller para o ADM controlar os usuários do sistema e para o Usuário se cadastrar, como um CRUD 27 | 28 | private final UserService userService; // Service onde terá a lógica relacionada ao usuário no sistema. 29 | 30 | @Override 31 | @PostMapping(value = "/register") // Método HTTP POST - Cadastrar / Criar 32 | public ResponseEntity register(RegisterUserDTO registerData, UriComponentsBuilder uriBuilder) { // Método para se registrar no sistema 33 | 34 | UserMonitoringDTO userDTO = userService.register(registerData); 35 | 36 | URI uri = uriBuilder.path("/users/{id}").buildAndExpand(userDTO.getId()).toUri(); // Recurso novo criado no sistema baseado no novo usuário. 37 | 38 | String message = userDTO.getNickname() + ", your account was registered successfully!"; // Mensagem de sucesso após registro no sistema. 39 | 40 | return ResponseEntity.created(uri).body(message); 41 | } 42 | 43 | @Override 44 | @GetMapping // Método HTTP GET -> Obter / Recuperar 45 | public ResponseEntity> findAll(Pageable pageable, String roleName) { // Método que retorna uma page de users do sistema. 46 | return ResponseEntity.ok().body(userService.findAll(pageable, roleName)); 47 | } 48 | 49 | @Override 50 | @GetMapping(value = "/{id}") 51 | public ResponseEntity findById(String id) { // Método HTTP GET -> Obter / Recuperar 52 | return ResponseEntity.ok().body(userService.findById(id)); // Método que retorna um usuário especifico do sistema pelo ID. 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/filter/AuthenticationJWTFilter.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.filter; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.User; 5 | import com.almadavic.securitystandard.repository.UserRepository; 6 | import com.almadavic.securitystandard.service.serviceAction.TokenService; 7 | import jakarta.servlet.FilterChain; 8 | import jakarta.servlet.ServletException; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import jakarta.servlet.http.HttpServletResponse; 11 | import lombok.AllArgsConstructor; 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 13 | import org.springframework.security.core.Authentication; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.web.filter.OncePerRequestFilter; 16 | 17 | import java.io.IOException; 18 | 19 | 20 | @AllArgsConstructor 21 | public class AuthenticationJWTFilter extends OncePerRequestFilter { 22 | 23 | // Quando a aplicação subir, essa classe vai ser chamada antes do spring security. 24 | // Nas proximas requisições , o spring security não vai ser chamado mais, pois as informações já serão salvas em memória, 25 | // porém, esse filtro sera chamado em todas as requisições para recuperar o token, autenticar se tiver necessidade... 26 | 27 | private TokenService tokenService; // Dependencia tokenService 28 | private UserRepository userRepository; // Dependencia userRepository 29 | 30 | @Override 31 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 32 | // Método faz algumas ações, depois disso, o fluxo normal segue, a requisição continua para onde deveria. 33 | 34 | String tokenJWT = recoverToken(request); // É recuperado o token da request. 35 | 36 | if (tokenJWT != null) { // Se o token for diferente de null 37 | 38 | User user = recoverUser(tokenJWT); // É recuperado o usuário associado ao token 39 | 40 | Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); // Instancia com dados do usuario para autenticar. 41 | SecurityContextHolder.getContext().setAuthentication(authentication); // A autenticação é feita. 42 | 43 | } 44 | 45 | filterChain.doFilter(request, response); // Essa linha diz que a requisição irá continuar... 46 | } 47 | 48 | private User recoverUser(String tokenJWT) { // Método para recuperar o usuario logado 49 | String subject = tokenService.getSubject(tokenJWT); // É recuperado o sujeito do token, no caso, o id 50 | User user = userRepository.findById(subject).get(); // Recupera o usuário relacionado com esse id. 51 | return user; 52 | } 53 | 54 | private String recoverToken(HttpServletRequest request) { // Método recupera o token passado no header da request. 55 | String authorizationHeader = request.getHeader("Authorization"); 56 | if (authorizationHeader != null) { 57 | return authorizationHeader.replace("Bearer ", ""); 58 | } 59 | 60 | return null; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/authentication/AuthenticationTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.authentication; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import com.almadavic.securitystandard.service.customException.DatabaseException; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.ActiveProfiles; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | 18 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 19 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe (vai para o ar). 20 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 21 | public class AuthenticationTest extends ClassTestParent { // Classe testa a autenticação de um usuário no sistema. 22 | 23 | private final String path = "/auth"; // recurso (URI) 24 | 25 | @Test 26 | void loginFailPasswordWrong() throws Exception { // Login deve falhar pois a senha está incorreta. 27 | 28 | Login loginData = new Login("admin@hotmail.com", "1234567"); 29 | 30 | mockMvc.perform(post(path) 31 | .contentType("application/json") 32 | .content(objectMapper.writeValueAsString(loginData))) 33 | .andExpect(status().is(badRequest)) 34 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof DatabaseException)) 35 | .andExpect(result -> assertEquals("E-mail and / or password is / are wrong!" 36 | , result.getResolvedException().getMessage())); 37 | 38 | } 39 | 40 | @Test 41 | void loginFailEmailWrong() throws Exception { // Login deve falhar pois o e-mail está incorreto. 42 | 43 | Login loginData = new Login("adminn@hotmail.com", "123456"); 44 | 45 | mockMvc.perform(post(path) 46 | .contentType("application/json") 47 | .content(objectMapper.writeValueAsString(loginData))) 48 | .andExpect(status().is(badRequest)) 49 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof DatabaseException)) 50 | .andExpect(result -> assertEquals("E-mail and / or password is / are wrong!" 51 | , result.getResolvedException().getMessage())); 52 | 53 | } 54 | 55 | @Test 56 | void loginOk() throws Exception { // Login deve ser feito com sucesso. 57 | 58 | Login loginData = new Login("admin@hotmail.com", "123456"); 59 | 60 | mockMvc.perform(post(path) 61 | .contentType("application/json") 62 | .content(objectMapper.writeValueAsString(loginData))) 63 | .andExpect(status().is(ok)); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/UserAreaController.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError.StandardError; 5 | import com.almadavic.securitystandard.dto.request.ChangePassword; 6 | import com.almadavic.securitystandard.dto.response.UserDTO; 7 | import com.almadavic.securitystandard.entity.User; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.Parameter; 10 | import io.swagger.v3.oas.annotations.media.Content; 11 | import io.swagger.v3.oas.annotations.media.Schema; 12 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 13 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 14 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 15 | import io.swagger.v3.oas.annotations.tags.Tag; 16 | import jakarta.validation.Valid; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | 21 | 22 | @Tag(name = "user-area", description = "The user area, where the own user performs operations") 23 | // Como será chamado o nome do "controller" no swagger e a descrição. 24 | public interface UserAreaController { // Controller de UserArea deve implementar essa interface 25 | 26 | @Operation(summary = "Access the logged user informations", security = {@SecurityRequirement(name = "bearer-key")}) 27 | // SecurityRequirement - Segurança de metodo no Swagger 28 | @ApiResponses(value = { // Informações relacionadas ao response! 29 | @ApiResponse(responseCode = "200", description = "Return the user profile", 30 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = UserDTO.class))}), 31 | @ApiResponse(responseCode = "401", description = "You have to be logged in the system in order to access this resource", 32 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}) 33 | }) 34 | ResponseEntity myProfile(@AuthenticationPrincipal @Parameter(hidden = true) User userLogged); // Método para ver dados do proprio usuário logado no sistema. 35 | 36 | 37 | @Operation(summary = "Change the account password", security = {@SecurityRequirement(name = "bearer-key")}) 38 | // SecurityRequirement - Segurança de metodo no Swagger 39 | @ApiResponses(value = { // Informações relacionadas ao response! 40 | @ApiResponse(responseCode = "200", description = "Password changed", 41 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = String.class))}), 42 | @ApiResponse(responseCode = "400", description = "Password entered doesn't match the user password in database", 43 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 44 | @ApiResponse(responseCode = "500", description = "The new password cannot be equal the last one", 45 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 46 | }) 47 | ResponseEntity changePassword(@RequestBody @Valid ChangePassword cpDTO, @AuthenticationPrincipal 48 | @Parameter(hidden = true) User userLogged); // Método para alterar a senha do usuário no sistema. 49 | 50 | } 51 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.1 9 | 10 | 11 | com.almadavic 12 | security-standard 13 | 1.0 14 | security-standard 15 | A base project to serve for other projects that use authentication. 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-security 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-validation 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | com.h2database 38 | h2 39 | runtime 40 | 41 | 42 | org.postgresql 43 | postgresql 44 | runtime 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | true 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | org.springdoc 63 | springdoc-openapi-starter-webmvc-ui 64 | 2.0.2 65 | 66 | 67 | com.auth0 68 | java-jwt 69 | 4.2.1 70 | 71 | 72 | com.fasterxml.jackson.datatype 73 | jackson-datatype-jsr310 74 | 75 | 76 | 77 | 78 | SecurityStandard 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-maven-plugin 83 | 84 | 85 | 86 | org.projectlombok 87 | lombok 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/TokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | 5 | import com.almadavic.securitystandard.dto.response.RoleDTO; 6 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 7 | import com.almadavic.securitystandard.entity.User; 8 | import com.almadavic.securitystandard.util.UserMapper; 9 | import com.auth0.jwt.JWT; 10 | import com.auth0.jwt.algorithms.Algorithm; 11 | import com.auth0.jwt.exceptions.JWTCreationException; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Primary; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.time.Instant; 19 | import java.time.LocalDateTime; 20 | import java.time.ZoneOffset; 21 | import java.util.List; 22 | 23 | 24 | @Service // Indica que é uma camada de serviço , o spring vai gerenciar automaticamente. 25 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 26 | @RequiredArgsConstructor 27 | public class TokenServiceImpl implements TokenService { // Serviço relacionado ao token. 28 | 29 | @Value("${jwt.expiration}") // var de ambiente , localização externa - application(test,prod).properties 30 | private String expiration; // tempo de expiração do token 31 | 32 | @Value("${jwt.secret}") // var de ambiente , localização externa - application(test,prod).properties 33 | private String secret; // regra de como o token será codificado. 34 | 35 | private final UserMapper userMapper; 36 | 37 | public String generateToken(Authentication authentication) { // Método que gera um token. 38 | 39 | UserMonitoringDTO userDTO = userMapper.toUserMonitoringDTO((User) authentication.getPrincipal()); // Cria um DTO baseado no usuário logado. 40 | 41 | List roles = convertFromObjectListToStringList(userDTO.getRolesDTO()); // Converte uma lista de RoleDTO para string 42 | 43 | try { 44 | return JWT.create() // Cria um token baseado no usuario logado 45 | .withIssuer("SecurityStandard API") 46 | .withSubject(userDTO.getId()) 47 | .withClaim("email", userDTO.getEmail()) 48 | .withClaim("roles", roles) 49 | .withIssuedAt(Instant.now()) // !!!!!!!!!! Colocar JWT na frente de Spring boot c Spring Securit readme 50 | .withExpiresAt(expirationInstant()) 51 | .sign(secretAlgorithm()); 52 | } catch (JWTCreationException exception) { 53 | throw new RuntimeException("Generating jwt token error", exception); 54 | } 55 | } 56 | 57 | public String getSubject(String token) { // Método que recupera o subject do token (ID do usuário) 58 | try { 59 | return JWT.require(secretAlgorithm()) 60 | .withIssuer("SecurityStandard API") 61 | .build() 62 | .verify(token) 63 | .getSubject(); 64 | } catch (RuntimeException exception) { 65 | throw new RuntimeException("JWT Token invalid or expired!"); 66 | } 67 | } 68 | 69 | private List convertFromObjectListToStringList(List roles) { // Método para converter uma lista de objetos em uma lista de strings 70 | return roles.stream().map(String::valueOf).toList(); 71 | } 72 | 73 | private Instant expirationInstant() { // Método que retorna o tempo (QUANDO) o token vai expirar. 74 | return LocalDateTime.now().plusHours(Long.parseLong(this.expiration)) 75 | .toInstant(ZoneOffset.of("-03:00")); 76 | 77 | } 78 | 79 | private Algorithm secretAlgorithm () { // Algoritmo de codificação. 80 | return Algorithm.HMAC256(secret); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/StartProjectConfigurations.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config; 2 | 3 | 4 | import com.almadavic.securitystandard.entity.Role; 5 | import com.almadavic.securitystandard.entity.User; 6 | import com.almadavic.securitystandard.enumerated.RoleName; 7 | import com.almadavic.securitystandard.repository.RoleRepository; 8 | import com.almadavic.securitystandard.repository.UserRepository; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.boot.CommandLineRunner; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Profile; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | 19 | @RequiredArgsConstructor 20 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 21 | @Configuration // Indica que é uma classe de configuração 22 | @Profile(value = {"test"}) // Define em quais ambientes (profiles) essa classe será "chamada" 23 | public class StartProjectConfigurations implements CommandLineRunner { // Essa classe é uma clase separada de configuração, Ela serve para popular o banco de dados quando a aplicação sobe (NO CASO)! 24 | 25 | private final UserRepository userRepository; // Repositório da entidade Usuário 26 | 27 | private final RoleRepository roleRepository; // Repositório da entidade Role 28 | 29 | private final PasswordEncoder encoder; // Encoder para codificar a senha 30 | 31 | 32 | @Override 33 | public void run(String... args) { // Objetivo método -> Ele serve para popular o banco de dados e testar se os relacionamentos estão corretos. Sempre que a aplicação 34 | // subir e estiver no Profile informado, esse método irá ser chamado. 35 | 36 | 37 | User u1 = User.builder() // Criando um usuário através de um Builder. 38 | .nickname("admin") 39 | .email("admin@hotmail.com") 40 | .password(encoder.encode("123456")) 41 | .build(); 42 | 43 | User u2 = User.builder() // Criando um usuário através de um Builder. 44 | .nickname("user1") 45 | .email("user1@hotmail.com") 46 | .password(encoder.encode("123456")) 47 | .build(); 48 | 49 | User u3 = User.builder() // Criando um usuário através de um Builder. 50 | .nickname("user2") 51 | .email("user2@hotmail.com") 52 | .password(encoder.encode("123456")) 53 | .build(); 54 | 55 | List users = userRepository.saveAll(Arrays.asList(u1, u2, u3)); // Salvando uma lista dos usuários no banco. 56 | 57 | Role r1 = Role.builder() // Criando uma role através de um Builder. 58 | .name(RoleName.ROLE_ADMIN) 59 | .build(); 60 | 61 | Role r2 = Role.builder() 62 | .name(RoleName.ROLE_USER) // Criando uma role através de um Builder. 63 | .build(); 64 | 65 | roleRepository.saveAll(Arrays.asList(r1, r2)); // Salvando uma lista das roles no banco. 66 | 67 | users.get(0).addRole(r1); // Relacionando as entidades. 68 | users.get(1).addRole(r2); // Relacionando as entidades. 69 | users.get(2).addRole(r2); // Relacionando as entidades. 70 | 71 | users = userRepository.saveAll(users); // Salvando a lista de usuários novamente no banco depois do relacionamento. 72 | 73 | System.out.println(users); // Printando. 74 | 75 | } 76 | 77 | } 78 | 79 | /* 80 | ------ EXPLICAÇÃO CLASSE -------- 81 | É criado um usuário, uma role e são associados entre sí no banco (ManyToMany), O método popula o banco com esses dados para facilitar os testes pelo 82 | postman, para não ter que ficar criando os dados de cadastro de forma obrigatória toda vez quando não desejado. Além disso, nos permite verificar se o 83 | relacionamento entre as classes está correto. 84 | */ -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/service/userService/FindUsersTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.userService; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 5 | import com.almadavic.securitystandard.entity.User; 6 | import com.almadavic.securitystandard.repository.UserRepository; 7 | import com.almadavic.securitystandard.service.customException.ResourceNotFoundException; 8 | import com.almadavic.securitystandard.service.serviceAction.UserService; 9 | import jakarta.transaction.Transactional; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.test.context.ActiveProfiles; 18 | 19 | 20 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 21 | @SpringBootTest 22 | public class FindUsersTest { // Classe que testa o service associado com a funcionalidade de encontrar usuários no sistema. 23 | 24 | @Autowired 25 | private UserService userService; // Usado para testar os métodos findById e findAll. 26 | 27 | @Autowired 28 | private UserRepository userRepository; // Usado para buscar um usuário do banco. 29 | 30 | @Test 31 | @Transactional // Esse annotation foi utilizado pois estava tendo problemas relacionados ao proxy depois da atualização do Spring. 32 | void findUsersParameterAdministrator() { 33 | 34 | Pageable pageable = PageRequest.of(0, 10); // Páginação 35 | 36 | Page usersDto = userService.findAll(pageable, "ADMIN"); // Quando é passado a paginação e o nome do role, o método deve retornar uma página 37 | // com os usuários que tenham essa role. 38 | boolean containsRoleUser = false; 39 | 40 | for (UserMonitoringDTO userDTO : usersDto) { 41 | 42 | if (!userDTO.getRolesDTO().get(0).getRoleName().equals("ROLE_ADMIN")) { // Lógica para verificar se o método não está retornando usuários com uma role 43 | containsRoleUser = true; // que não deveria. 44 | } 45 | 46 | Assertions.assertFalse(containsRoleUser); 47 | 48 | } 49 | 50 | } 51 | 52 | @Test 53 | @Transactional // Esse annotation foi utilizado pois estava tendo problemas relacionados ao proxy depois da atualização do Spring. 54 | void findUsersParameterUser() { 55 | 56 | Pageable pageable = PageRequest.of(0, 10); 57 | 58 | Page usersDto = userService.findAll(pageable, "USER"); 59 | 60 | boolean containsRoleAdministrator = false; 61 | 62 | for (UserMonitoringDTO userDTO : usersDto) { 63 | 64 | if (!userDTO.getRolesDTO().get(0).getRoleName().equals("ROLE_USER")) { 65 | containsRoleAdministrator = true; 66 | } 67 | 68 | Assertions.assertFalse(containsRoleAdministrator); 69 | 70 | } 71 | 72 | } 73 | 74 | @Test 75 | @Transactional 76 | void findById() { 77 | 78 | User user = userRepository.findByEmail("admin@hotmail.com") // É encontrado um usuário no banco de dados pelo e-mail. 79 | .orElseThrow(()-> new ResourceNotFoundException("The user wasn't found on database\"")); // Caso não tenha um usuário no banco com o e-mail informado, 80 | // será lançada uma exception. 81 | String id = user.getId(); // Id do usuário. 82 | 83 | UserMonitoringDTO userDTO = userService.findById(id); // Retorna um dto do usuário. 84 | 85 | Assertions.assertEquals(id, userDTO.getId()); // O id do DTO tem que ser o mesmo do usuário no banco. 86 | 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError.StandardError; 5 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 6 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.media.ArraySchema; 9 | import io.swagger.v3.oas.annotations.media.Content; 10 | import io.swagger.v3.oas.annotations.media.Schema; 11 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 12 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 13 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 14 | import io.swagger.v3.oas.annotations.tags.Tag; 15 | import jakarta.validation.Valid; 16 | import org.springframework.data.domain.Page; 17 | import org.springframework.data.domain.Pageable; 18 | import org.springframework.data.domain.Sort; 19 | import org.springframework.data.web.PageableDefault; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.web.bind.annotation.PathVariable; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestParam; 24 | import org.springframework.web.util.UriComponentsBuilder; 25 | 26 | 27 | @Tag(name = "user", description = "Operations about user") 28 | // Como será chamado o nome do "controller" no swagger e a descrição. 29 | public interface UserController { // Controller User deve implementar essa interface 30 | 31 | @Operation(summary = "Sign up in the System.") 32 | @ApiResponses(value = { // Informações relacionadas ao response! 33 | @ApiResponse(responseCode = "201", description = "User registered", 34 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = String.class))}), 35 | @ApiResponse(responseCode = "400", description = "The passwords don't match", 36 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 37 | @ApiResponse(responseCode = "500", description = "The e-mail or nickname entered already exist in the system", 38 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 39 | }) 40 | ResponseEntity register(@RequestBody @Valid RegisterUserDTO registerData, UriComponentsBuilder uriBuilder); // Método para se registrar no sistema 41 | 42 | 43 | @Operation(summary = "Find all the users from the system.", security = {@SecurityRequirement(name = "bearer-key")}) 44 | @ApiResponses(value = { // Informações relacionadas ao response! 45 | @ApiResponse(responseCode = "200", description = "Find users successfully", 46 | content = {@Content(mediaType = "application/json", 47 | array = @ArraySchema(schema = @Schema(implementation = UserMonitoringDTO.class)))}), 48 | @ApiResponse(responseCode = "400", description = "The parameter {role} in URI is invalid", 49 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 50 | }) 51 | ResponseEntity> findAll(@PageableDefault(sort = "email", direction = Sort.Direction.ASC, size = 12) // Método que retorna uma page de users do sistema. 52 | Pageable pageable, @RequestParam(required = false, value = "role") String roleName); 53 | 54 | 55 | @Operation(summary = "Find an user from the system by id.", security = {@SecurityRequirement(name = "bearer-key")}) 56 | @ApiResponses(value = { // Informações relacionadas ao response! 57 | @ApiResponse(responseCode = "200", description = "Find user successfully", 58 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = UserMonitoringDTO.class))}), 59 | @ApiResponse(responseCode = "404", description = "User not found", 60 | content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StandardError.class))}), 61 | }) 62 | ResponseEntity findById(@PathVariable String id); // Método que retorna um usuário específico do banco por ID. 63 | 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SecurityStandard 2 | 3 | [![NPM](https://img.shields.io/npm/l/react)](https://github.com/Almadavic/security-standard/blob/main/LICENSE) 4 | 5 | # O QUE É A APLICAÇÃO : 6 | 7 | Essa é uma aplicação para ser o esqueleto (base) de outras API's futuras que necessitam de autenticação JWT e autorização. Além desses 8 | 2 recursos, a API conta com outras diversas configurações padrões que podem ser aproveitadas em uma futura API, como: 9 | 10 | - Configurações padrões de Segurança no geral. 11 | - Entidades User e Role totalmente prontas. 12 | - Configurações padrões do Swagger. 13 | - Tratamento de Exceptions através de classes Handler e classes personalizadas. 14 | - Diversos Design Patterns. 15 | - Dados iniciais para população do banco. 16 | - Controllers, Services e Repositories padrões pré configurados. 17 | - DTO's de request e response. 18 | - Regras de négocio. 19 | - Exception personalizadas. 20 | - Funcionalidades extras além de autenticação. 21 | - Uma grande cobertura de testes. 22 | 23 | # OBJETIVO : 24 | 25 | Sempre que criamos uma API, temos de fazer várias configurações, até mesmo, configurações padrões, que sempre serão as mesmas 26 | independente da API. Isso é uma coisa que cansa muito e acaba tomando muito tempo, para resolver esse problema, desenvolvi essa 27 | "api esqueleto" para sempre utilizar esses padrões em uma nova api que eu esteja desenvolvendo, desse modo, economizando muito tempo. 28 | Obs: Essa API seria usada de base para outras API caso essa por sua vez seja uma aplicação que precise de autenticação e autorização. 29 | 30 | # FUNCIONALIDADES : 31 | 32 | - O Usuário se registra no sistema. 33 | - O Usuário se autentica no sistema. 34 | - O Usuário pode entrar no seu perfil (ver as informações de sua conta). 35 | - O Usuário pode mudar a sua senha. 36 | - O Administrador pode ver uma página de usuários cadastrados no sistema. 37 | - O Administrador pode ver qualquer usuário registrado no sistema em específico pelo id. 38 | 39 | # VERSIONAMENTO : 40 | 41 | Versão do Java: 17 42 | 43 | Versão do Spring Boot: 3.0.1 44 | 45 | # FERRAMENTAS : 46 | 47 | Back end: 48 | 49 | - Java 50 | - Spring Boot c/ Spring Security e JWT. 51 | - JPA / Hibernate 52 | - Maven 53 | 54 | Dependências: 55 | 56 | Data JPA - > Utilizando o JPA para fazer a ponte entre o banco de dados e a aplicação -> ORM 57 | 58 | Validation -> Utilizado para proibir requisições com dados inválidos ou com um formato inválido. 59 | 60 | Web -> É uma aplicação WEB, Utilizado para receber requisições, devolver uma resposta ... 61 | 62 | H2 - > Utilizado esse banco em MEMÓRIA para fazer testes, ambiente de teste. 63 | 64 | PostgreSQL -> Utilizado esse BANCO no ambiente de desenvolvimento e produção. 65 | 66 | Test -> Foram feitos testes na aplicação com JUNIT ( Testes de integração e Unidade). 67 | 68 | Java-JWT -> A aplicação é stateless, por isso precisamos da biblioteca para gerar e verificar o token. 69 | 70 | Security -> Utilizado para fazer a segurança do sistema - > Autorização e Autenticação. 71 | 72 | OpenAPI -> Utilizado para documentar a API (Swagger). 73 | 74 | LomBok -> Utilizado para evitar muitas linhas de código através de annotations. 75 | 76 | JacksonDatatype - > Utilizado para transformar objetos em JSON. 77 | 78 | Programas Utilizados : 79 | 80 | Postman - Utilizado para fazer as requisições (CONSUMIR) a aplicação. 81 | 82 | IntelliJ - IDE escolhida para desenvolver o projeto. 83 | 84 | pgAdmin4 - Plataforma utilizada para fazer a manipulação e a leitura dos dados de uma base de dados do banco PostgreSQL. 85 | 86 | Git e GitHub - Utilizados para commitar o projeto e subir o código para a nuvem(remoto). 87 | 88 | JWT.IO - Depurar o token e ver o seu formato. 89 | 90 | Bancos de Dados : 91 | 92 | PostgreSQL - Usado em ambiente de desenvolvimento e produção. 93 | 94 | H2 - Usado em ambiente de teste. 95 | 96 | 97 | - Swagger : 98 | 99 | 100 | 101 | - Schema : 102 | 103 | 104 | 105 | # INFORMAÇÕES ADICIONAIS : 106 | 107 | Linhas de Código: 2217 108 | 109 | Classes / Arquivos implementados: 79 110 | 111 | Testes: 40 112 | 113 | Link index do Swagger: /security-api/swagger-ui/index.html 114 | 115 | # LINKS ADICIONAIS : 116 | 117 | Explicação da API em vídeo no YouTube : https://youtu.be/58U1HHhoS2I 118 | 119 | Perfil no Linkedin : https://www.linkedin.com/in/victoralmada/ 120 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/userController/RegistrationUserTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.userController; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 6 | import com.almadavic.securitystandard.service.customException.EmailAlreadyRegisteredException; 7 | import com.almadavic.securitystandard.service.customException.NicknameAlreadyRegisteredException; 8 | import com.almadavic.securitystandard.service.customException.PasswordDoesntMatchRegisterUserException; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | 19 | 20 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 21 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 22 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 23 | public class RegistrationUserTest extends ClassTestParent { // Classe testa a funcionalidade de cadastrar um usuário no banco. 24 | 25 | private final String path = "/users/register"; 26 | 27 | @Test 28 | void nicknameAlreadyRegistered() throws Exception { // Método deve falhar pois já existe um usuário no banco com esse nickname. 29 | 30 | RegisterUserDTO registerDTO = new RegisterUserDTO("admin", "joaomaia@gmail.com", "123456", "123456"); // DTO para se registrar no sistema. 31 | 32 | mockMvc.perform(post(path) 33 | .contentType("application/json") 34 | .content(objectMapper.writeValueAsString(registerDTO))) 35 | .andExpect(status().is(internalServerError)) 36 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof NicknameAlreadyRegisteredException)) 37 | .andExpect(result -> assertEquals("This nickname: " + registerDTO.getNickname() + " already exists in the system" 38 | , result.getResolvedException().getMessage())); 39 | 40 | } 41 | 42 | @Test 43 | void emailAlreadyRegistered() throws Exception { // Método deve falhar pois já existe um usuário no banco com esse e-mail. 44 | 45 | RegisterUserDTO registerDTO = new RegisterUserDTO("almadaV", "admin@hotmail.com", "123456", "123456"); // DTO para se registrar no sistema. 46 | 47 | mockMvc.perform(post(path) // Caminho da requisição. 48 | .contentType("application/json") // // O tipo do conteúdo 49 | .content(objectMapper.writeValueAsString(registerDTO))) // O conteúdo que será enviado. 50 | .andExpect(status().is(internalServerError)) // Erro que deve ocorrer. 51 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof EmailAlreadyRegisteredException)) // Tipo de exception esperada. 52 | .andExpect(result -> assertEquals("This e-mail: " + registerDTO.getEmail() + " already exists in the system" // Mensagem da exception esperada. 53 | , result.getResolvedException().getMessage())); 54 | 55 | } 56 | 57 | @Test 58 | void passwordsDontMatch() throws Exception { // Método deve falhar pois o usuário está passando 2 senhas que não correspondem. 59 | 60 | RegisterUserDTO registerDTO = new RegisterUserDTO("Larissa", "larissa@hotmail.com", "123456", "1234567"); 61 | 62 | mockMvc.perform(post(path) 63 | .contentType("application/json") 64 | .content(objectMapper.writeValueAsString(registerDTO))) 65 | .andExpect(status().is(badRequest)) 66 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof PasswordDoesntMatchRegisterUserException)) 67 | .andExpect(result -> assertEquals("The passwords don't match" 68 | , result.getResolvedException().getMessage())); 69 | 70 | } 71 | 72 | @Test 73 | void registeredSuccess() throws Exception { // O usuário deve conseguir se registrar no sistema com sucesso. 74 | 75 | RegisterUserDTO registerDTO = new RegisterUserDTO("Davi", "davi@hotmail.com", "123456", "123456"); 76 | 77 | mockMvc.perform(post(path) 78 | .contentType("application/json") 79 | .content(objectMapper.writeValueAsString(registerDTO))) 80 | .andExpect(status().is(created)) 81 | .andExpect(result -> assertEquals(registerDTO.getNickname() + ", your account was registered successfully!", 82 | result.getResponse().getContentAsString())); 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.entity; 2 | 3 | 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | import org.hibernate.annotations.CreationTimestamp; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | 10 | import java.time.LocalDateTime; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.UUID; 14 | 15 | 16 | @Entity // Indica que será uma tabela do banco de dados. 17 | @Table(name = "tb_users") // nome da tabela do banco de dados, será uma tabela de usuários. 18 | @NoArgsConstructor // No caso, não está sendo usado pelo programador mas sim pelo JPA debaixo dos panos. 19 | @Getter // Cria os getters dos atributos 20 | @Setter // Cria os setters dos atributos 21 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) // HashCode and equals apenas com os atributos marcados com @Include. 22 | public class User implements UserDetails { // Classe do banco -> User | Representa os usuários do sistema. 23 | 24 | @Id // Indica que será a PRIMARY-KEY 25 | @EqualsAndHashCode.Include // O hashCode irá usar esse atributo para destinguir um objeto de outro. 26 | @Column(name = "ID", nullable = false, unique = true) 27 | // name -> nome da coluna, nullable -> se pode ser nullo, unique -> o valor do campo não pode ser repetido 28 | private final String id = UUID.randomUUID().toString(); 29 | // Representa qual atributo será essa PRIMARY-KEY e a tipagem , no Caso será um UUID devido a segurança. 30 | 31 | @Column(name = "USERNAME", nullable = false, unique = true) 32 | private String nickname; // Representa o nome do usuário. 33 | 34 | @Column(name = "EMAIL", nullable = false, unique = true) 35 | @Getter(AccessLevel.NONE) 36 | // Colocando o AccessLevel.NONE para dizer que não precisa criar o getter automaticamente do Lombok, pois já temos o metodo get padrão do atributo por conta do implements UserDetails. 37 | private String email; // Representa o e-mail do usuário. 38 | 39 | @Column(name = "PASSWORD", nullable = false) 40 | @Getter(AccessLevel.NONE) 41 | // Mesmo caso do email, devido a interface DefaultUser... q implementa o UserDetails, ja temos um get que retorna a senha. 42 | private String password; // Representa a senha do usuário 43 | 44 | @Column(name = "REGISTRATION_MOMENT", nullable = false) 45 | @CreationTimestamp // O momento de registro será dado assim que o objeto for criado. 46 | @Setter(AccessLevel.NONE) 47 | private LocalDateTime registrationMoment; 48 | 49 | @ManyToMany(fetch = FetchType.EAGER) // Relacionamento muito para muitos. 50 | @JoinTable(name = "tb_users_roles", // Nome da tabela que será criada 51 | joinColumns = @JoinColumn(name = "user_id"), // nome da coluna do registro do id do user (PK) 52 | inverseJoinColumns = @JoinColumn(name = "role_id")) // nome da coluna do registro do id da role (PK) 53 | @Getter(AccessLevel.NONE) // Mesmo caso do password e email. 54 | private final List roles = new ArrayList<>(); // Muitos usuários tem muitas permissões, o final foi usado pois não é interessante alterar a lista, e sim adicionar e remover os atributos. 55 | 56 | @Builder // Indica um Builder para construir um user baseado nos parametros abaixo. 57 | public User(@NonNull String nickname, @NonNull String email, @NonNull String password) { 58 | this.nickname = nickname; 59 | this.email = email; 60 | this.password = password; 61 | } 62 | 63 | public void addRole(Role role) { // Método adiciona a role em um usuário. 64 | roles.add(role); 65 | } 66 | 67 | 68 | // -------------------------------------------- MÉTODOS DEFAULT PELA INTERFACE USERDETAILS ----------------------- 69 | 70 | @Override 71 | public String getUsername() { 72 | return this.email; 73 | } 74 | 75 | @Override 76 | public String getPassword() { 77 | return this.password; 78 | } 79 | 80 | @Override 81 | public List getAuthorities() { 82 | return this.roles; 83 | } 84 | 85 | 86 | @Override 87 | public boolean isAccountNonExpired() { 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean isAccountNonLocked() { 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean isCredentialsNonExpired() { 98 | return true; 99 | } 100 | 101 | @Override 102 | public boolean isEnabled() { 103 | return true; 104 | } 105 | 106 | 107 | // MÉTODO toString 108 | 109 | @Override 110 | public String toString() { 111 | StringBuilder sb = new StringBuilder(); 112 | sb.append("\n"); 113 | sb.append("Id: " + id + ", Name: " + getNickname() + ", Email: " + email + "\n"); 114 | sb.append("---Roles---\n"); 115 | for (Role role : roles) { 116 | sb.append(role + "\n"); 117 | } 118 | sb.append("---Roles---\n"); 119 | sb.append("\n"); 120 | return sb.toString(); 121 | 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/authorization/AccessDeniedAuthorizationTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.authorization; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | 17 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 18 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 19 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 20 | public class AccessDeniedAuthorizationTest extends ClassTestParent { // Classe testa o acesso aos recursos do sistema (autorização e autenticação) que estão negados. 21 | 22 | 23 | @Test 24 | void accessMyProfileDeniedNoUserLogged() throws Exception { // Método testa o acesso ao recurso /userarea/myprofile sem nenhum usuário logado.O sistema não deve deixar. 25 | 26 | // Método não tem nenhum usuário logado. 27 | // Qualquer usuário logado tem acesso á esse método. 28 | 29 | mockMvc.perform(get("/userarea/myprofile")) // Tentativa de acessar esse recurso 30 | .andExpect(status().is(unauthorized)); // É para retornar o status de não autorizado. 31 | 32 | } 33 | 34 | @Test 35 | void accessChangePasswordDeniedNoUserLogged() throws Exception { // Método testa o acesso ao recurso /userarea/changepassword sem nenhum uuário logado.O sistema não deve deixar. 36 | 37 | // Método não tem nenhum usuário logado. 38 | // Qualquer usuário logado tem acesso á esse método. 39 | 40 | mockMvc.perform(put("/userarea/changepassword")) 41 | .andExpect(status().is(unauthorized)); 42 | 43 | } 44 | 45 | @Test 46 | void accessFindAllDeniedNoUserLogged() throws Exception { // Método testa o acesso ao recurso /users/findall sem nenhum usuário logado. O sistema não deve deixar. 47 | 48 | // Método não tem nenhum usuário logado. 49 | // Apenas administradores podem acessar esse recurso. 50 | 51 | mockMvc.perform(get("/users")) 52 | .andExpect(status().is(unauthorized)); 53 | 54 | } 55 | 56 | @Test 57 | // Método testa o acesso ao recurso /users/findall com usuário role = user logado. Sistema não deve deixar. 58 | void accessFindAllDeniedRoleUserLogged() throws Exception { 59 | 60 | // Método tem usuário logado. 61 | // Apenas administradores podem acessar esse recurso. 62 | 63 | Login loginData = new Login("user1@hotmail.com", "123456"); // DTO de Login que passamos na requisição para logar. 64 | 65 | String token = authenticate(loginData); // Loga o usuário no sistema apartir do DTO e retorna o token pora ser enviado nas próxima requisição. 66 | 67 | mockMvc.perform(get("/users") // Tentativa de acessar esse recurso 68 | .header("Authorization", token)) // É enviado um token na requisição. 69 | .andExpect(status().is(forbidden)); // É para retornar o status de proibido. 70 | 71 | } 72 | 73 | @Test 74 | void accessFindByIdDeniedNoUserLogged() throws Exception { // Método testa o acesso ao recurso /users/{id} sem usuário logado. O sistema não deve deixar. 75 | 76 | // Método não tem nenhum usuário logado. 77 | // Apenas administradores podem acessar esse recurso. 78 | 79 | mockMvc.perform(get("/users/{id}", "teste")) // Não tem nenhuma variavel para id, mas oq importa é dar o erro unhautorizen. 80 | .andExpect(status().is(unauthorized)); // É para retornar o status de não autorizado. 81 | 82 | } 83 | 84 | @Test 85 | // Método testa o acesso ao recurso /users/{id} com usuário role=user logado. O sistema não deve deixar. 86 | void accessFindByIdDeniedRoleUserLogged() throws Exception { 87 | 88 | // Método tem usuário logado. 89 | // Apenas administradores podem acessar esse recurso. 90 | 91 | Login loginData = new Login("user1@hotmail.com", "123456"); 92 | 93 | String token = authenticate(loginData); 94 | 95 | mockMvc.perform(get("/users/{id}", "teste") // Não tem nenhuma variavel para id, mas oq importa é dar o erro forbidden. 96 | .header("Authorization", token)) 97 | .andExpect(status().is(forbidden)); 98 | 99 | } 100 | 101 | @Test 102 | void accessAnyOtherResourceDeniedNoUserLogged() throws Exception { // Método testa o acesso a algum recurso inválido no sistem, usuario não logado. O sistema não deve deixar. 103 | 104 | // Ninguém pode acessar esse recurso. 105 | 106 | mockMvc.perform(get("/recursoinvalido")) 107 | .andExpect(status().is(unauthorized)); 108 | 109 | } 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/service/serviceAction/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.service.serviceAction; 2 | 3 | 4 | import com.almadavic.securitystandard.dto.request.RegisterUserDTO; 5 | import com.almadavic.securitystandard.dto.response.UserMonitoringDTO; 6 | import com.almadavic.securitystandard.entity.Role; 7 | import com.almadavic.securitystandard.entity.User; 8 | import com.almadavic.securitystandard.enumerated.RoleName; 9 | import com.almadavic.securitystandard.repository.RoleRepository; 10 | import com.almadavic.securitystandard.repository.UserRepository; 11 | import com.almadavic.securitystandard.service.businessRule.findUsersByParameter.*; 12 | import com.almadavic.securitystandard.service.businessRule.registerUser.RegisterUserArgs; 13 | import com.almadavic.securitystandard.service.businessRule.registerUser.RegisterUserVerification; 14 | import com.almadavic.securitystandard.service.customException.ResourceNotFoundException; 15 | import com.almadavic.securitystandard.util.UserMapper; 16 | import lombok.RequiredArgsConstructor; 17 | import org.springframework.cache.annotation.CacheEvict; 18 | import org.springframework.cache.annotation.Cacheable; 19 | import org.springframework.context.annotation.Primary; 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.security.core.userdetails.UserDetails; 23 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 24 | import org.springframework.stereotype.Service; 25 | 26 | import java.util.List; 27 | 28 | 29 | @Service // Indica que é uma camada de serviço , o spring vai gerenciar automaticamente. 30 | @RequiredArgsConstructor 31 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 32 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 33 | public class UserServiceImpl extends ServiceParent implements UserService { // Serviço relacionado ao User como uma especie de CRUD. 34 | 35 | private final UserRepository userRepository; // Injeção de dependencia do UserRepository 36 | 37 | private final RoleRepository roleRepository; // injeção de dependencia de RoleRepository -> buscar uma role do banco de dados. 38 | 39 | private final List registerUserVerifications; // Lista com regras de négocio (verificação) relacinadas ao registro de Usuário. 40 | 41 | @Override 42 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // Método que mostra para o spring security como será feita a autenticação. 43 | 44 | return userRepository.findByEmail(username) // A autenticação será feita através do e-mail. 45 | .orElseThrow(() -> new ResourceNotFoundException("User not found with this e-mail : " + username)); 46 | 47 | } 48 | 49 | @Override 50 | @CacheEvict(value = "usersList", allEntries = true) 51 | public UserMonitoringDTO register(RegisterUserDTO registerData) { // Método para registrar um usuário no banco. 52 | 53 | registerUserVerifications.forEach(v -> v.verification(new RegisterUserArgs(registerData, userRepository))); // verificações se os dados informados estão válidos para o cadastro. 54 | 55 | User user = UserMapper.toUserEntity(registerData, encoder); 56 | user.addRole(returnRole(RoleName.ROLE_USER)); 57 | 58 | return UserMapper.toUserMonitoringDTO(userRepository.save(user)); // retorna o DTO desse usuário salvo para o client. 59 | } 60 | 61 | @Override 62 | @Cacheable(value = "usersList") 63 | public Page findAll(Pageable pageable, String roleName) { // Método que retorna uma página de usuários cadastrados no sistema. 64 | return UserMapper.toUserMonitoringDTO(verifyParametersToReturnCorrectPage(pageable, roleName)); // Método retorna uma página de UsersDTO (mais informações na declaração do método) e a lista é retornada pro controller. 65 | } 66 | 67 | @Override 68 | public UserMonitoringDTO findById(String id) { // Método retorna um usuário do banco de dados pelo ID 69 | return UserMapper.toUserMonitoringDTO(returnUser(id)); // retorna o DTO desse user para o controller. 70 | } 71 | 72 | private Role returnRole(RoleName name) { // Método retorna uma role do banco de dados passando o nome dessa role como parametro 73 | return roleRepository.findByName(name).orElseThrow(() -> 74 | new ResourceNotFoundException("The role : " + name + " wasn't found in database")); // Método retorna essa role, caso a role não exista, lança exception. 75 | } 76 | 77 | private Page verifyParametersToReturnCorrectPage(Pageable pageable, String roleName) { // Método usa o design patterns : Chain of Responsibility 78 | // Método vérifica qual página retornar baseada no parametro roleName. 79 | FindUsersByRoleNameVerification verification = new NoRoleName( // Verifica se não tem parametro 80 | new ValidRoleName( // Verifica se o parametro é válido 81 | new InvalidRoleName())); // Se nada do que for acima for correto, o roleName é inválido. 82 | 83 | return verification.verification(new FindUsersArgs(userRepository, pageable, roleName)); // Retorna a página correta. 84 | 85 | } 86 | 87 | private User returnUser(String id) { // Método retorna um usuário do banco de dados pelo id. 88 | return userRepository.findById(id).orElseThrow(() -> 89 | new ResourceNotFoundException("The user id: " + id + " wasn't found on database")); // Se esse usuário não existir, vai ser lançada uma exception. 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/securityConfig/SecurityConfigurationsImpl.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.securityConfig; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.handler.ForbiddenHandler; 5 | import com.almadavic.securitystandard.config.exceptionConfig.handler.UnauthorizedHandler; 6 | import com.almadavic.securitystandard.filter.AuthenticationJWTFilter; 7 | import com.almadavic.securitystandard.repository.UserRepository; 8 | import com.almadavic.securitystandard.service.serviceAction.TokenService; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.http.SessionCreationPolicy; 18 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 19 | import org.springframework.security.crypto.password.PasswordEncoder; 20 | import org.springframework.security.web.SecurityFilterChain; 21 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 22 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 23 | import org.springframework.web.cors.CorsConfiguration; 24 | import org.springframework.web.cors.CorsConfigurationSource; 25 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 26 | 27 | import java.util.Arrays; 28 | 29 | 30 | @RequiredArgsConstructor 31 | // Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente. 32 | @EnableWebSecurity 33 | // Desabilita as configurações default do Spring Security, permitindo o desenvolvedor configurar as próprias configurações. 34 | @Configuration // Indica que é uma classe de configuração. 35 | @Primary // Essa vai ser a implementação a ser carregada caso tenha mais de 1. 36 | public class SecurityConfigurationsImpl implements SecurityConfigurations { // As classes de Security só são chamadas quando a aplicação sobe! 37 | // Nas proximas requisições, essa classe não é chamada novamente, pois as configurações já estão salvas em memória. 38 | 39 | private final TokenService tokenService; // Classe que contém ações relacionadas á um token: gerar um token, validar um token, recuperar o subject do token... 40 | 41 | private final UserRepository userRepository; // Repositório da entidade Usuário. 42 | 43 | @Override 44 | @Bean 45 | public PasswordEncoder encoder() { // Encoder de senha, codifica a senha. 46 | return new BCryptPasswordEncoder(); 47 | } 48 | 49 | @Override 50 | @Bean 51 | public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { // Autentica o usuário. 52 | return authenticationConfiguration.getAuthenticationManager(); 53 | } 54 | 55 | @Override 56 | @Bean 57 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // Configurações de Autorização. 58 | 59 | http.authorizeHttpRequests() // Autorização de requests. 60 | .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll() // Permitindo os recursos do swagger. (Todos podem acessar). 61 | .requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll() // Permitindo os recursos do H2. (Todos podem acessar). 62 | 63 | .requestMatchers("/auth", "/users/register").permitAll() // Todos podem acessar esses recursos. 64 | .requestMatchers("/users", "/users/**").hasRole("ADMIN") // Apenas ADMINISTRADORES podem acessar esses recursos. 65 | .anyRequest().authenticated() // Qualquer outro recurso, sem ser os de cima, poderão ser acessados apenas se estiver autenticado. 66 | .and().cors() // Libera a integração de aplicações externas como o front-end á essa API. 67 | .and().headers().frameOptions().disable() // É para bloquear a página de login ser colocada em um iFrame. 68 | .and().csrf().disable() // Comentário sobre essa configuração na linha 89. 69 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // DIZ QUE A APLICAÇÃO NÃO TEM ESTADO, OS DADOS SÃO GUARDADOS EM UM TOKEN! 70 | .and().addFilterBefore(new AuthenticationJWTFilter(tokenService, userRepository), UsernamePasswordAuthenticationFilter.class) // Adiciona um FILTRO antes da classe atual ser chamada. 71 | .exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler()) // Essa linha vai chamar a classe Unhautorize... para lídar com o erro 401. 72 | .and().exceptionHandling().accessDeniedHandler(new ForbiddenHandler()); // Essa linha chama a classe Forbidden para lídar com o erro 403. 73 | 74 | return http.build(); 75 | } 76 | 77 | @Override 78 | @Bean 79 | public CorsConfigurationSource corsConfigurationSource() { // Método relacionado á CORS, integração com um meio externo. 80 | CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues(); 81 | configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "OPTIONS")); 82 | final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 83 | source.registerCorsConfiguration("/**", configuration); 84 | return source; 85 | } 86 | 87 | /* 88 | csrf - é uma proteção contra hackers que "roubam a sessão", como estamos usando staless, n temos sessão, e deixar essa conf(padrão habilitada) 89 | não teria sentido, pois é um cenário impossivel, por isso desativamos. 90 | */ 91 | 92 | 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/userController/FindUsersTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.userController; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import com.almadavic.securitystandard.entity.User; 7 | import com.almadavic.securitystandard.repository.UserRepository; 8 | import com.almadavic.securitystandard.service.customException.InvalidParamException; 9 | import com.almadavic.securitystandard.service.customException.ResourceNotFoundException; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.ActiveProfiles; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | 22 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 23 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 24 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 25 | public class FindUsersTest extends ClassTestParent { // Classe testa a busca de usuário no sistema. 26 | 27 | private final String path = "/users"; 28 | 29 | @Autowired 30 | private UserRepository userRepository; // Utilizado para buscar um usuário do banco ( foi utilizado no caso do findById) onde precisava saber o Id do usuário, por causa do UUID. 31 | 32 | @Test 33 | void findUsersInvalidParameter() throws Exception { // Método deve falhar, pois foi passado um nome de uma role inválida. 34 | 35 | Login loginData = new Login("admin@hotmail.com", "123456"); // DTO de Login que passamos na requisição para logar. 36 | 37 | String token = authenticate(loginData); // Loga o usuário no sistema através do DTO e retorna o token pora ser enviado nas próxima requisição. 38 | 39 | String param = "INVALIDO"; // paramêtro 40 | 41 | mockMvc.perform(get(path + "?role={param}", param) // Caminho da requisição. 42 | .header("Authorization", token)) // O token que será enviado na requisição. 43 | .andExpect(status().is(badRequest)) // Erro que deve ocorrer. 44 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof InvalidParamException)) // Tipo de exception esperada. 45 | .andExpect(result -> assertEquals("This parameter (role) : { " + param + " } is invalid" // Mensagem da exception esperada. 46 | , result.getResolvedException().getMessage())); 47 | 48 | } 49 | 50 | @Test 51 | void findUsersParameterUser() throws Exception { // Método deve retornar uma página de usuários do sistema que tem a role USER 52 | 53 | Login loginData = new Login("admin@hotmail.com", "123456"); 54 | 55 | String token = authenticate(loginData); 56 | 57 | String param = "USER"; 58 | 59 | mockMvc.perform(get(path + "?role={param}", param) 60 | .header("Authorization", token)) 61 | .andExpect(status().is(ok)); 62 | 63 | } 64 | 65 | @Test 66 | void findUsersParameterAdministrator() throws Exception { // Método deve retornar uma página de usuários do sistema que tem a role ADMINISTRATOR 67 | 68 | Login loginData = new Login("admin@hotmail.com", "123456"); 69 | 70 | String token = authenticate(loginData); 71 | 72 | String param = "ADMIN"; 73 | 74 | mockMvc.perform(get(path + "?role={param}", param) 75 | .header("Authorization", token)) 76 | .andExpect(status().is(ok)); 77 | 78 | } 79 | 80 | @Test 81 | void findUsersNoParameter() throws Exception { // Método deve retornar uma página de usuários do sistema sem passar parametro na URI. 82 | 83 | Login loginData = new Login("admin@hotmail.com", "123456"); 84 | 85 | String token = authenticate(loginData); 86 | 87 | mockMvc.perform(get(path) 88 | .header("Authorization", token)) 89 | .andExpect(status().is(ok)); 90 | 91 | } 92 | 93 | @Test 94 | void findUserByIdNotFound() throws Exception { // Método deve falhar, pois passei um id cujo não existe nenhum usuário no banco cadastrado com o mesmo. 95 | 96 | Login loginData = new Login("admin@hotmail.com", "123456"); 97 | 98 | String token = authenticate(loginData); 99 | 100 | String id = "2"; 101 | 102 | mockMvc.perform(get(path + "/{id}", id) 103 | .header("Authorization", token)) 104 | .andExpect(status().is(notFound)) 105 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof ResourceNotFoundException)) 106 | .andExpect(result -> assertEquals("The user id: " + id + " wasn't found on database" 107 | , result.getResolvedException().getMessage())); 108 | 109 | } 110 | 111 | @Test 112 | void findUserByIdSuccess() throws Exception { // Método retorna um usuario especifico do banco. 113 | 114 | Login loginData = new Login("admin@hotmail.com", "123456"); 115 | 116 | String token = authenticate(loginData); 117 | 118 | User user = userRepository.findByEmail("user1@hotmail.com").orElseThrow(() -> 119 | new ResourceNotFoundException("User wasn't found on database")); 120 | 121 | String id = user.getId(); 122 | 123 | mockMvc.perform(get(path + "/{id}", id) 124 | .header("Authorization", token)) 125 | .andExpect(status().is(ok)); 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/userAreaController/ChangePasswordTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.userAreaController; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.ChangePassword; 6 | import com.almadavic.securitystandard.dto.request.Login; 7 | import com.almadavic.securitystandard.service.customException.DatabaseException; 8 | import com.almadavic.securitystandard.service.customException.SamePasswordException; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | 21 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 22 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 23 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 24 | public class ChangePasswordTest extends ClassTestParent { // Classe testa a funcionalidade de alterar uma senha no sistema. 25 | 26 | private final String path = "/userarea/changepassword"; 27 | 28 | @Test 29 | void passwordDoenstMatchDataBasePassword() throws Exception { // Alteração de senha deve falhar pois o usuário está passando sua senha incorreta. 30 | 31 | Login loginData = new Login("user2@hotmail.com", "123456"); // DTO de Login que passamos na requisição para logar. 32 | 33 | String token = authenticate(loginData); // Loga o usuário no sistema através do DTO e retorna o token pora ser enviado nas próxima requisição. 34 | 35 | ChangePassword changePasswordDTO = new ChangePassword("12345678", "13553353553"); // DTO para alterar a senha 36 | 37 | mockMvc.perform(put(path) // Caminho da requisição 38 | .contentType("application/json") // O tipo do conteúdo 39 | .header("Authorization", token) // O token que será enviado na requisição. 40 | .content(objectMapper.writeValueAsString(changePasswordDTO))) // O conteúdo que será enviado. 41 | .andExpect(status().is(badRequest)) // Erro que deve ocorrer. 42 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof DatabaseException)) // Tipo de exception esperada. 43 | .andExpect(result -> assertEquals("The password is not correct (not match)" // Mensagem da exception esperada. 44 | , result.getResolvedException().getMessage())); 45 | 46 | } 47 | 48 | @Test 49 | void passwordIsEqualTheLastOne() throws Exception { // Alteração da senha deve falhar pois o usuário está passando o valor da nova senha igual da antiga. 50 | 51 | Login loginData = new Login("admin@hotmail.com", "123456"); 52 | 53 | String token = authenticate(loginData); 54 | 55 | ChangePassword changePasswordDTO = new ChangePassword("123456", "123456"); 56 | 57 | mockMvc.perform(put(path) 58 | .contentType("application/json") 59 | .header("Authorization", token) 60 | .content(objectMapper.writeValueAsString(changePasswordDTO))) 61 | .andExpect(status().is(internalServerError)) 62 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof SamePasswordException)) 63 | .andExpect(result -> assertEquals("Your new password cannot be equal the last one" 64 | , result.getResolvedException().getMessage())); 65 | 66 | } 67 | 68 | @Test 69 | void changePasswordSuccessFlow() throws Exception { // Alteração de senha deve ser feita com sucesso. 70 | 71 | Login firstLogin = new Login("user1@hotmail.com", "123456"); 72 | 73 | String token = authenticate(firstLogin); 74 | 75 | String oldPassword = "123456"; 76 | 77 | String newPassword = "1234567"; 78 | 79 | ChangePassword changePasswordDTO = new ChangePassword(oldPassword, newPassword); 80 | 81 | passwordChanged(changePasswordDTO, token); // Senha alterada de oldPassword para newPassword; 82 | 83 | Login loginDataAttempt1 = new Login("user1@hotmail.com", oldPassword); 84 | 85 | enterSystemFail(loginDataAttempt1); // Usuário tenta logar com a senha incorreta ( Senha antiga) 86 | 87 | Login loginDataAttempt2 = new Login("user1@hotmail.com", newPassword); 88 | 89 | enterSystemSuccess(loginDataAttempt2); // Usuário tenta logar com a senha correta ( Nova senha) 90 | 91 | } 92 | 93 | private void passwordChanged(ChangePassword changePasswordDTO, String token) throws Exception { // Mudança de senha. 94 | 95 | mockMvc.perform(put(path) 96 | .contentType("application/json") 97 | .header("Authorization", token) 98 | .content(objectMapper.writeValueAsString(changePasswordDTO))) 99 | .andExpect(status().is(ok)) 100 | .andExpect(result -> assertEquals("Password changed successfully!", 101 | result.getResponse().getContentAsString())); 102 | } 103 | 104 | private void enterSystemFail(Login loginDataAttempt1) throws Exception { // Usuário tenta logar com a senha antiga. 105 | 106 | mockMvc.perform(post("/auth") 107 | .contentType("application/json") 108 | .content(objectMapper.writeValueAsString(loginDataAttempt1))) 109 | .andExpect(status().is(badRequest)) 110 | .andExpect(result -> assertTrue(result.getResolvedException() instanceof DatabaseException)) 111 | .andExpect(result -> assertEquals("E-mail and / or password is / are wrong!" 112 | , result.getResolvedException().getMessage())); 113 | } 114 | 115 | private void enterSystemSuccess(Login loginDataAttempt2) throws Exception { // Usuário tenta logar com a nova senha. 116 | 117 | mockMvc.perform(post("/auth") 118 | .contentType("application/json") 119 | .content(objectMapper.writeValueAsString(loginDataAttempt2))) 120 | .andExpect(status().is(ok)); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /src/main/java/com/almadavic/securitystandard/config/exceptionConfig/handler/ResourceExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.config.exceptionConfig.handler; 2 | 3 | 4 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.commonStandardError.StandardError; 5 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.validationArgsStandardError.StandardErrorArgsNotValid; 6 | import com.almadavic.securitystandard.config.exceptionConfig.standardError.validationArgsStandardError.ValidationErrorCollection; 7 | import com.almadavic.securitystandard.service.customException.*; 8 | import jakarta.persistence.EntityNotFoundException; 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import lombok.RequiredArgsConstructor; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.context.MessageSource; 14 | import org.springframework.context.i18n.LocaleContextHolder; 15 | import org.springframework.data.mapping.PropertyReferenceException; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.validation.FieldError; 19 | import org.springframework.web.HttpRequestMethodNotSupportedException; 20 | import org.springframework.web.bind.MethodArgumentNotValidException; 21 | import org.springframework.web.bind.annotation.ControllerAdvice; 22 | import org.springframework.web.bind.annotation.ExceptionHandler; 23 | import org.springframework.web.servlet.NoHandlerFoundException; 24 | 25 | import java.util.List; 26 | 27 | 28 | @ControllerAdvice 29 | // Se ocorrer alguma das execções abaixo durante o programa, o Spring vai cair nessa classe e vai retornar o erro de uma forma mais agradável para o cliente. 30 | @RequiredArgsConstructor 31 | // Faz com que quando a classe for instanciada, os atributos com FINAL vão ser passados no construtor automaticamente. 32 | public class ResourceExceptionHandler { 33 | 34 | private final MessageSource messageSource; // pega a mensagem do erro do validation. 35 | 36 | private static final Logger logger = LoggerFactory.getLogger(ResourceExceptionHandler.class); // Para mostrar log no console. 37 | 38 | @ExceptionHandler(value = MethodArgumentNotValidException.class) // Pega os erros de validação, como o @NotBlank! 39 | public ResponseEntity handleValidations(MethodArgumentNotValidException exception, HttpServletRequest request) { 40 | 41 | HttpStatus status = HttpStatus.BAD_REQUEST; 42 | String path = request.getRequestURI(); 43 | String error = "There is one or more parameters invalids"; 44 | 45 | ValidationErrorCollection validationErrs = new ValidationErrorCollection(status.value(), path, error); 46 | 47 | List fieldErrors = exception.getBindingResult().getFieldErrors(); 48 | fieldErrors.forEach(e -> { 49 | String message = messageSource.getMessage(e, LocaleContextHolder.getLocale()); 50 | String field = e.getField(); 51 | validationErrs.addStandardErrorArgsNotValid(new StandardErrorArgsNotValid(field, message)); 52 | }); 53 | log(exception); 54 | return ResponseEntity.status(status).body(validationErrs); 55 | 56 | } 57 | 58 | @ExceptionHandler({EntityNotFoundException.class, NoHandlerFoundException.class}) 59 | // Quando há a tentativa de acessar algum recurso inválido quando o usuário está logado. 60 | public ResponseEntity mappingNotFound(HttpServletRequest request) { 61 | return handlingException(new ResourceNotFoundException("The resource isn't mapped"),request,"Not found", HttpStatus.NOT_FOUND); 62 | } 63 | 64 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) 65 | // Quando há a tentativa de acessar algum recurso com o verbo http errado. 66 | public ResponseEntity methodNotSupportedException(HttpRequestMethodNotSupportedException exception, HttpServletRequest request) { 67 | return handlingException(exception,request,"Method not allowed", HttpStatus.METHOD_NOT_ALLOWED); 68 | } 69 | 70 | @ExceptionHandler(value = PropertyReferenceException.class) 71 | // Quando passa uma referencia de propriedade incorreta, pelo client! 72 | public ResponseEntity propertyReference(PropertyReferenceException exception, HttpServletRequest request) { 73 | return handlingException(exception, request, "Property Reference error", HttpStatus.BAD_REQUEST); 74 | } 75 | 76 | @ExceptionHandler(value = EmailAlreadyRegisteredException.class) 77 | // Quando tenta cadastrar um usuário com um email q já exista no banco! 78 | public ResponseEntity emailAlreadyRegistered(EmailAlreadyRegisteredException exception, HttpServletRequest request) { 79 | return handlingException(exception, request, "Email error", HttpStatus.INTERNAL_SERVER_ERROR); 80 | } 81 | 82 | @ExceptionHandler(value = NicknameAlreadyRegisteredException.class) 83 | // Quando tenta cadastrar um usuário com um nickname q já existe no banco! 84 | public ResponseEntity nicknameAlreadyRegistered(NicknameAlreadyRegisteredException exception, HttpServletRequest request) { 85 | return handlingException(exception, request, "Username error", HttpStatus.INTERNAL_SERVER_ERROR); 86 | } 87 | 88 | @ExceptionHandler(value = DatabaseException.class) // Algum problema com o banco de dados! 89 | public ResponseEntity dataBase(DatabaseException exception, HttpServletRequest request) { 90 | return handlingException(exception, request, "Database error", HttpStatus.BAD_REQUEST); 91 | } 92 | 93 | @ExceptionHandler(value = PasswordDoesntMatchRegisterUserException.class) 94 | // Quando o usuário passa 2 senhas no cadastro que não se correspondem! 95 | public ResponseEntity passwordsDontMatchException(PasswordDoesntMatchRegisterUserException exception, HttpServletRequest request) { 96 | return handlingException(exception, request, "Passwords error", HttpStatus.BAD_REQUEST); 97 | } 98 | 99 | @ExceptionHandler(value = ResourceNotFoundException.class) // Quando o recurso não é encontrado no banco de dados! 100 | public ResponseEntity resourceNotFound(ResourceNotFoundException exception, HttpServletRequest request) { 101 | return handlingException(exception, request, "Resource not found", HttpStatus.NOT_FOUND); 102 | } 103 | 104 | @ExceptionHandler(value = SamePasswordException.class) 105 | // Quando o usuário tenta mudar a senha para igual a passada! 106 | public ResponseEntity samePassword(SamePasswordException exception, HttpServletRequest request) { 107 | return handlingException(exception, request, "Same password", HttpStatus.INTERNAL_SERVER_ERROR); 108 | } 109 | 110 | @ExceptionHandler(InvalidParamException.class) // Quando o usuário passa algum parametro errado! 111 | public ResponseEntity invalidParam(InvalidParamException exception, HttpServletRequest request) { 112 | return handlingException(exception, request, "Invalid Param", HttpStatus.BAD_REQUEST); 113 | } 114 | 115 | @ExceptionHandler(RuntimeException.class) // Quando ocorre alguma exception generica em momento de execução! 116 | public ResponseEntity genericException(RuntimeException exception, HttpServletRequest request) { 117 | return handlingException(exception, request, "Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR); 118 | } 119 | 120 | private ResponseEntity handlingException(Exception exception, HttpServletRequest request, String error, HttpStatus status) { // Método que será reutilizado várias vezes. 121 | StandardError err = new StandardError(status.value(), error, exception.getMessage(), request.getRequestURI()); 122 | log(exception); 123 | return ResponseEntity.status(status).body(err); 124 | } 125 | 126 | private void log(Throwable exception) { // Método para aparecer o log do erro no console 127 | logger.error("error message {}. Details:", exception.getMessage(), exception); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/com/almadavic/securitystandard/controller/authorization/AccessAllowedAuthorizationTest.java: -------------------------------------------------------------------------------- 1 | package com.almadavic.securitystandard.controller.authorization; 2 | 3 | 4 | import com.almadavic.securitystandard.controller.ClassTestParent; 5 | import com.almadavic.securitystandard.dto.request.Login; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | 17 | @ActiveProfiles(value = "test") // Quando o teste for rodado, ele será rodado em ambiente de teste. 18 | @SpringBootTest // Indica que estamos fazendo testes com spring, onde a aplicação sobe. 19 | @AutoConfigureMockMvc // Utilizaremos mocks nos testes 20 | public class AccessAllowedAuthorizationTest extends ClassTestParent { // Classe testa o acesso aos recursos do sistema (autorização e autenticação) que estão autorizados. 21 | 22 | @Test 23 | void accessSwagger() throws Exception { // Método testa o acesso ao recurso /swagger-ui/index.html. O sistema deve deixar. 24 | 25 | // Método não precisa de autenticação e nema autorização. 26 | 27 | mockMvc.perform(get("/swagger-ui/index.html")) 28 | .andExpect(result -> assertEquals(ok, result.getResponse().getStatus())); // Tem que retornar esse status, é o único status que deve ser retornado desse recurso. 29 | 30 | } 31 | 32 | @Test 33 | void accessAuthentication() throws Exception { // Método testa o acesso ao recurso /auth. O sistema deve deixar. 34 | 35 | // Método não tem nenhum usuário logado. 36 | // Qualquer um pode acessar esse recurso. 37 | 38 | mockMvc.perform(post("/auth")) 39 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 40 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 41 | 42 | } 43 | 44 | @Test 45 | void accessMyProfileAllowedRoleAdmin() throws Exception { // Método testa o acesso ao recurso /userarea/myprofile , usuario role = administrador. O sistema deve deixar. 46 | 47 | // Método tem administrador logado. 48 | // Qualquer um pode (logado) pode acessar esse método. 49 | 50 | Login loginData = new Login("admin@hotmail.com", "123456"); 51 | 52 | String token = authenticate(loginData); 53 | 54 | 55 | mockMvc.perform(get("/userarea/myprofile") 56 | .header("Authorization", token)) 57 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 58 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 59 | 60 | 61 | } 62 | 63 | @Test 64 | void accessMyProfileAllowedRoleUser() throws Exception { // Método testa o acesso ao recurso /userarea/myprofile , usuario role = user. O sistema deve deixar. 65 | 66 | // Método tem usuário logado. 67 | // Qualquer um pode (logado) pode acessar esse método. 68 | 69 | Login loginData = new Login("user1@hotmail.com", "123456"); 70 | 71 | String token = authenticate(loginData); 72 | 73 | mockMvc.perform(get("/userarea/myprofile") 74 | .header("Authorization", token)) 75 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 76 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 77 | 78 | } 79 | 80 | @Test 81 | void accessChangePasswordAllowedRoleAdmin() throws Exception { // Método testa o acesso ao recurso /userarea/changepassword , usuario role = administrador. O sistema deve deixar. 82 | 83 | // Método tem administrador logado. 84 | // Qualquer um pode (logado) pode acessar esse método. 85 | 86 | Login loginData = new Login("admin@hotmail.com", "123456"); 87 | 88 | String token = authenticate(loginData); 89 | 90 | mockMvc.perform(put("/userarea/changepassword") 91 | .header("Authorization", token)) 92 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 93 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 94 | 95 | 96 | } 97 | 98 | @Test 99 | void accessChangePasswordAllowedRoleUser() throws Exception { // Método testa o acesso ao recurso /userarea/changepassword , usuario role = user. O sistema deve deixar. 100 | 101 | // Método tem usuário logado. 102 | // Qualquer um pode (logado) pode acessar esse método. 103 | 104 | Login loginData = new Login("user1@hotmail.com", "123456"); 105 | 106 | String token = authenticate(loginData); 107 | 108 | mockMvc.perform(put("/userarea/changepassword") 109 | .header("Authorization", token)) 110 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 111 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 112 | 113 | } 114 | 115 | @Test 116 | void accessRegisterUser() throws Exception { // Método testa o acesso ao recurso /auth. O sistema deve deixar. 117 | 118 | 119 | // Método não tem nenhum usuário logado. 120 | // Qualquer um pode acessar esse recurso. 121 | 122 | mockMvc.perform(post("/users/register")) 123 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 124 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 125 | 126 | } 127 | 128 | @Test 129 | void accessFindAllAllowedRoleAdminLogged() throws Exception { // Método testa o acesso ao recurso /users/findall , usuario role = administrador. O sistema deve deixar. 130 | 131 | // Método tem administrador logado. 132 | // Apenas administradores podem acessar esse recurso. 133 | 134 | Login loginData = new Login("admin@hotmail.com", "123456"); 135 | 136 | String token = authenticate(loginData); 137 | 138 | mockMvc.perform(get("/users") 139 | .header("Authorization", token)) 140 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 141 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 142 | 143 | } 144 | 145 | @Test 146 | void accessFindByIdAllowedRoleAdminLogged() throws Exception { // Método testa o acesso ao recurso /userarea/{id} , usuario role = administrador. O sistema deve deixar. 147 | 148 | // Método tem administrador logado. 149 | // Apenas administradores podem acessar esse recurso. 150 | 151 | Login loginData = new Login("admin@hotmail.com", "123456"); 152 | 153 | String token = authenticate(loginData); 154 | 155 | mockMvc.perform(get("/users/{id} ", "teste") 156 | .header("Authorization", token)) 157 | .andExpect(result -> assertNotEquals(unauthorized, result.getResponse().getStatus())) 158 | .andExpect(result -> assertNotEquals(forbidden, result.getResponse().getStatus())); 159 | 160 | } 161 | 162 | @Test 163 | void accessAnyOtherResourcedRoleAdminLogged() throws Exception { // Método testa o acesso a algum recurso inválido no sistema, usuário role=administrador logado. Deve retornar status not found. 164 | 165 | 166 | Login loginData = new Login("admin@hotmail.com", "123456"); 167 | 168 | String token = authenticate(loginData); 169 | 170 | 171 | mockMvc.perform(get("/recursoinvalido") 172 | .header("Authorization", token)) 173 | .andExpect(status().is(notFound)); 174 | 175 | } 176 | 177 | @Test 178 | void accessAnyOtherResourceRoleUserLogged() throws Exception { // Método testa o acesso a algum recurso inválido no sistema, usuário role=user logado. Deve retornar status not faund 179 | 180 | 181 | Login loginData = new Login("user1@hotmail.com", "123456"); 182 | 183 | String token = authenticate(loginData); 184 | 185 | mockMvc.perform(get("/recursoinvalido") 186 | .header("Authorization", token)) 187 | .andExpect(status().is(notFound)); 188 | 189 | } 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | --------------------------------------------------------------------------------