Skip to content

Commit

Permalink
Merge pull request #8 from Hangar-Tech/feat-authentication
Browse files Browse the repository at this point in the history
Feat authentication
  • Loading branch information
MatheusVict authored Mar 6, 2024
2 parents d04b24f + f554e17 commit 57b6597
Show file tree
Hide file tree
Showing 26 changed files with 819 additions and 13 deletions.
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
Expand Down Expand Up @@ -79,6 +88,11 @@
<version>1.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.institutosemprealerta.semprealerta;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@OpenAPIDefinition(servers = {@Server(url = "/", description = "Default server url")})
public class SempreAlertaApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.institutosemprealerta.semprealerta.application.controllers;

import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO;
import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse;
import com.institutosemprealerta.semprealerta.domain.service.AuthenticationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
@Tag(name = "auth")
public class AuthenticationController {

private final AuthenticationService authenticationService;

public AuthenticationController(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}

@PostMapping("/login")
@Operation(summary = "Login", description = "You can login with your email and password")

public ResponseEntity<?> login(@RequestBody @Valid LoginDTO loginRequestBody) {
LoginResponse token = authenticationService.login(loginRequestBody);
return ResponseEntity.ok(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public ResponseEntity<ExceptionPattern> handlerUserNotFoundException(UserNotFoun
ExceptionPattern.builder()
.title("User Not Found Exception")
.status(HttpStatus.NOT_FOUND.value())
.details("User not found")
.details(exception.getMessage())
.timestamp(timestamp)
.developerMessage(exception.getClass().getName())
.build()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.institutosemprealerta.semprealerta.domain.ports.out.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

public record LoginDTO(
@NotBlank(message = "email is mandatory")
@Schema(description = "email do usuário", example = "[email protected]")
String email,
@NotBlank(message = "password is mandatory")
@Schema(description = "Senha do usuário", example = "12345")
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.institutosemprealerta.semprealerta.domain.ports.out.responses;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

public record LoginResponse(
@Schema(description = "token de autorização para acessar os recursos", example = "eyn32nklafjçj354335g35")
@JsonProperty("access_token")
String accessToken
) {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.institutosemprealerta.semprealerta.domain.service;

import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO;
import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse;
import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User;

public interface AuthenticationService {
LoginResponse login(LoginDTO login);
void register(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.institutosemprealerta.semprealerta.domain.service;

import com.institutosemprealerta.semprealerta.domain.model.UserDTO;
import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User;

public interface TokenService {
String generateToken(User user);
String validateToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.institutosemprealerta.semprealerta.domain.service.impl;

import com.institutosemprealerta.semprealerta.domain.ports.out.request.LoginDTO;
import com.institutosemprealerta.semprealerta.domain.ports.out.responses.LoginResponse;
import com.institutosemprealerta.semprealerta.domain.service.AuthenticationService;
import com.institutosemprealerta.semprealerta.domain.service.TokenService;
import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {

private final AuthenticationManager authenticationManager;

private final TokenService tokenService;

public AuthenticationServiceImpl(AuthenticationManager authenticationManager, TokenService tokenService) {
this.authenticationManager = authenticationManager;
this.tokenService = tokenService;
}

@Override
public LoginResponse login(LoginDTO login) {
UsernamePasswordAuthenticationToken userNamePassword =
new UsernamePasswordAuthenticationToken(login.email(), login.password());
Authentication auth = this.authenticationManager.authenticate(userNamePassword);

String token = tokenService.generateToken((User) auth.getPrincipal());

return new LoginResponse(token);
}

@Override
public void register(User user) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.institutosemprealerta.semprealerta.domain.service.impl;

import com.institutosemprealerta.semprealerta.domain.service.UserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class AuthorizationService implements UserDetailsService {
private final UserService userService;

public AuthorizationService(UserService userService) {
this.userService = userService;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userService.findByEmail(username);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.institutosemprealerta.semprealerta.domain.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.institutosemprealerta.semprealerta.domain.model.UserDTO;
import com.institutosemprealerta.semprealerta.domain.service.TokenService;
import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Service
public class TokenServiceImpl implements TokenService {

private String secret = "secret";

private final Algorithm encryptionAlgorithm = Algorithm.HMAC256(secret);

private final String issuer = "sempre-alerta";

@Override
public String generateToken(User user) {
try {
return JWT.create()
.withIssuer(issuer)
.withSubject(user.getContact().getEmail())
.withExpiresAt(generationExpirationDate())
.sign(encryptionAlgorithm);
} catch (JWTCreationException e) {
throw new JWTCreationException("Error while generating token", e);
}
}

@Override
public String validateToken(String token) {
try {
return JWT.require(encryptionAlgorithm)
.withIssuer(issuer)
.build()
.verify(token)
.getSubject();
} catch (JWTVerificationException e) {
return "";
}
}

private Instant generationExpirationDate() {
return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.institutosemprealerta.semprealerta.domain.ports.out.exceptions.user.UserNotFoundException;
import com.institutosemprealerta.semprealerta.infrastructure.entity.user.User;
import com.institutosemprealerta.semprealerta.domain.ports.out.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -17,6 +18,9 @@ public UserServiceImpl(UserRepository userRepository) {

@Override
public void save(User user) {
String newPassword = new BCryptPasswordEncoder().encode(user.getPassword());

user.setPassword(newPassword);
this.userRepository.save(user);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.institutosemprealerta.semprealerta.infrastructure.config.security;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfigurations {

private final securtityFilter securtityFilter;

private final String[] AUTH_SWAGGER_WHITELIST = {
"/swagger-ui/**",
"/swagger-ui",
"/swagger-resources/**",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui.html"
};

private final String[] ACTUATOR_WHITELIST = {
"/actuator",
"/actuator/health",
"/actuator/health/**"
};

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(AUTH_SWAGGER_WHITELIST).permitAll()
.requestMatchers(ACTUATOR_WHITELIST).permitAll()
.requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
.requestMatchers("/api/v1/user/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/api/v1/files/upload").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/api/v1/posts/").hasRole("ADMIN")
.requestMatchers(HttpMethod.PUT, "/api/v1/posts/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/v1/posts/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(securtityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}

@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration
) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Loading

0 comments on commit 57b6597

Please sign in to comment.