Skip to content

Commit

Permalink
feat: creating MS to authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmoraist committed Oct 1, 2024
1 parent 89c6500 commit f2f7574
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 2 deletions.
36 changes: 36 additions & 0 deletions gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@
<artifactId>spring-boot-starter-test</artifactId>
<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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
<version>42.6.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.lucasmoraist.gateway;
package com.lucasmoraist.gateway.config.rest;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.lucasmoraist.gateway.config.security;

import org.springframework.beans.factory.annotation.Autowired;
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.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
public class SecurityConfig {

@Autowired
SecurityFilter securityFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
.requestMatchers(HttpMethod.POST, "/auth/register").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.lucasmoraist.gateway.config.security;

import com.lucasmoraist.gateway.domain.entity.User;
import com.lucasmoraist.gateway.repository.UserRepository;
import com.lucasmoraist.gateway.service.TokenService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

@Component
public class SecurityFilter extends OncePerRequestFilter {

@Autowired
private TokenService tokenService;
@Autowired
private UserRepository userRepository;

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
var token = this.recoverToken(req);
var login = tokenService.validateToken(token);
if(login != null){
User user = userRepository.findByEmail(login).orElseThrow(() -> new RuntimeException("User Not Found"));
var authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
var authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(req, res);
}

private String recoverToken(HttpServletRequest request){
var authHeader = request.getHeader("Authorization");
if(authHeader == null) return null;
return authHeader.replace("Bearer ", "");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.lucasmoraist.gateway.controller;

import com.lucasmoraist.gateway.domain.dto.TokenDTO;
import com.lucasmoraist.gateway.domain.model.LoginRequest;
import com.lucasmoraist.gateway.domain.model.RegisterRequest;
import com.lucasmoraist.gateway.service.UserService;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;

@Slf4j
@RestController
@RequestMapping("/auth")
public class UserController {

@Autowired
private UserService service;

@PostMapping("register")
public ResponseEntity<TokenDTO> register(@Valid @RequestBody RegisterRequest request) throws Exception {
this.service.register(request);
log.info("Registering");
return ResponseEntity.ok().build();
}

@PostMapping("login")
public ResponseEntity<TokenDTO> login(@Valid @RequestBody LoginRequest request) throws Exception {
var response = this.service.login(request);
log.info("Logging in");
return ResponseEntity.ok().body(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.lucasmoraist.gateway.domain.dto;

public record TokenDTO(String token) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.lucasmoraist.gateway.domain.entity;

import com.lucasmoraist.gateway.domain.model.LoginRequest;
import com.lucasmoraist.gateway.domain.model.RegisterRequest;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity(name = "t_user")
@Table(name = "t_user")
public class User {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 80)
private String name;

@Column(nullable = false, length = 255, unique = true)
private String email;

@Column(nullable = false, length = 20)
private String password;

public User(RegisterRequest request) {
this.name = request.getName();
this.email = request.getEmail();
this.password = request.getPassword();
}

public User(LoginRequest request) {
this.email = request.getEmail();
this.password = request.getPassword();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.lucasmoraist.gateway.domain.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {

@NotBlank
@Email
private String email;

@NotBlank
private String password;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.lucasmoraist.gateway.domain.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {

@Size(min = 3, max = 80)
private String name;

@NotBlank
@Email(message = "Invalid email")
@Size(max = 255)
private String email;

@NotBlank
@Size(min = 8, max = 20)
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
message = "Password must contain at least one uppercase letter, one lowercase letter and one digit")
private String password;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lucasmoraist.gateway.repository;

import com.lucasmoraist.gateway.domain.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.lucasmoraist.gateway.service;

import com.lucasmoraist.gateway.domain.entity.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,8 @@
package com.lucasmoraist.gateway.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lucasmoraist.gateway.service;

import com.lucasmoraist.gateway.domain.dto.TokenDTO;
import com.lucasmoraist.gateway.domain.model.LoginRequest;
import com.lucasmoraist.gateway.domain.model.RegisterRequest;

public interface UserService {
void register(RegisterRequest request) throws Exception;
TokenDTO login(LoginRequest request) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.lucasmoraist.gateway.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.lucasmoraist.gateway.domain.entity.User;
import com.lucasmoraist.gateway.service.TokenService;
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 {

@Value("${jwt.secret}")
private String secret;

@Override
public String generateToken(User user) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);

return JWT.create()
.withIssuer("task-list")
.withSubject(user.getEmail())
.withExpiresAt(this.generateExpirationDate())
.sign(algorithm);
} catch(JWTCreationException e){
throw new RuntimeException("Error while authentication");
}
}

@Override
public String validateToken(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.require(algorithm)
.withIssuer("task-list")
.build()
.verify(token)
.getSubject();
} catch(JWTVerificationException e) {
return null;
}
}

private Instant generateExpirationDate(){
return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
}
}
Loading

0 comments on commit f2f7574

Please sign in to comment.