diff --git a/pom.xml b/pom.xml index 1f7ab15..f26c95b 100644 --- a/pom.xml +++ b/pom.xml @@ -26,8 +26,9 @@ spring-boot-starter-web - org.springframework.boot - spring-boot-starter-security + com.auth0 + java-jwt + 4.2.1 com.mysql diff --git a/src/main/java/me/jweissen/aeticket/dto/response/LoginResponseDto.java b/src/main/java/me/jweissen/aeticket/dto/response/LoginResponseDto.java new file mode 100644 index 0000000..07444a4 --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/dto/response/LoginResponseDto.java @@ -0,0 +1,4 @@ +package me.jweissen.aeticket.dto.response; + +public record LoginResponseDto(String token) { +} diff --git a/src/main/java/me/jweissen/aeticket/model/User.java b/src/main/java/me/jweissen/aeticket/model/User.java index 95b563e..9432d09 100644 --- a/src/main/java/me/jweissen/aeticket/model/User.java +++ b/src/main/java/me/jweissen/aeticket/model/User.java @@ -4,21 +4,17 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collection; import java.util.List; @Entity @Table @Getter @Setter -public class User implements UserDetails { +public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; + private Long id; @Column(nullable = false) @NonNull @@ -36,45 +32,16 @@ public class User implements UserDetails { @NonNull private String password; - @Column(nullable = false) + @Column @NonNull - private Boolean admin; + private String token; + + @Column(nullable = false) + @Enumerated(EnumType.ORDINAL) + @NonNull + private Role role; @OneToMany(mappedBy = "user") @JoinColumn(nullable = false) private List carts; - - @Enumerated(EnumType.STRING) - private Role role; - - @Override - public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority(role.name())); - } - - @Override - public String getUsername() { - return email; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} -} +} \ No newline at end of file diff --git a/src/main/java/me/jweissen/aeticket/repository/UserRepository.java b/src/main/java/me/jweissen/aeticket/repository/UserRepository.java index 6be1940..24cd7df 100644 --- a/src/main/java/me/jweissen/aeticket/repository/UserRepository.java +++ b/src/main/java/me/jweissen/aeticket/repository/UserRepository.java @@ -4,4 +4,5 @@ import me.jweissen.aeticket.model.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { + User findByEmail(String email); } diff --git a/src/main/java/me/jweissen/aeticket/service/AuthService.java b/src/main/java/me/jweissen/aeticket/service/AuthService.java new file mode 100644 index 0000000..e8487a9 --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/service/AuthService.java @@ -0,0 +1,35 @@ +package me.jweissen.aeticket.service; + +import me.jweissen.aeticket.dto.response.LoginResponseDto; +import me.jweissen.aeticket.model.User; +import me.jweissen.aeticket.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class AuthService { + private final JwtService jwtService; + private final UserService userService; + private final UserRepository userRepository; + + public AuthService(JwtService jwtService, UserService userService, UserRepository userRepository) { + this.jwtService = jwtService; + this.userService = userService; + this.userRepository = userRepository; + } + + public Optional login(String username, String password) { + User user = userRepository.findByEmail(username); + // user not found or passwords don't match + if (user == null || !user.getPassword().equals(password)) { + return Optional.empty(); + } + String token = jwtService.generateToken(user.getId()); + user.setToken(token); + userRepository.save(user); + return Optional.of(new LoginResponseDto(username)); + } + + +} diff --git a/src/main/java/me/jweissen/aeticket/service/JwtService.java b/src/main/java/me/jweissen/aeticket/service/JwtService.java new file mode 100644 index 0000000..866605d --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/service/JwtService.java @@ -0,0 +1,57 @@ +package me.jweissen.aeticket.service; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.Optional; + +@Service +public class JwtService { + @Value("${token.secret}") + private String secret; + @Value("${token.validForHours}") + private Long tokenValidForHours; + + private final JWTVerifier jwtVerifier; + private final Algorithm algorithm; + private final String userIdClaimKey = "userId"; + private final Long tokenValidForMillis; + + public JwtService() { + tokenValidForMillis = 1000L * 3600 * tokenValidForHours; + algorithm = Algorithm.HMAC256(secret); + jwtVerifier = JWT.require(algorithm).build(); + } + + public String generateToken(Long userId) { + return JWT.create() + .withSubject("aeticket user token") + .withClaim(userIdClaimKey , userId) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + tokenValidForMillis)) + .sign(algorithm); + } + + public Optional getUserId(String token) { + DecodedJWT decodedJWT; + try { + decodedJWT = jwtVerifier.verify(token); + } catch (JWTVerificationException e) { + // token not valid + return Optional.empty(); + } + // token expired + if (decodedJWT.getExpiresAt().before(new Date())) { + return Optional.empty(); + } + Claim claim = decodedJWT.getClaim(userIdClaimKey); + return Optional.of(claim.asLong()); + } +} diff --git a/src/main/java/me/jweissen/aeticket/service/UserService.java b/src/main/java/me/jweissen/aeticket/service/UserService.java index d22f693..7da5f4d 100644 --- a/src/main/java/me/jweissen/aeticket/service/UserService.java +++ b/src/main/java/me/jweissen/aeticket/service/UserService.java @@ -10,9 +10,11 @@ import java.util.List; @Service public class UserService { private final UserRepository userRepository; + private final JwtService jwtService; - public UserService(UserRepository userRepository) { + public UserService(UserRepository userRepository, JwtService jwtService) { this.userRepository = userRepository; + this.jwtService = jwtService; } public static UserResponseDto toDto(User user) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e69de29..09a8c35 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -0,0 +1,8 @@ +spring: + datasource: + username: my + password: ticket + url: 'jdbc:mysql://localhost:4306/myticket' +token: + secret: "RATP loves Laravel more than Symfony" + validForHours: 24 \ No newline at end of file