diff --git a/pom.xml b/pom.xml index 1ddbb9c..48d608a 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,25 @@ + + io.jsonwebtoken + jjwt-api + 0.10.7 + + + + io.jsonwebtoken + jjwt-impl + 0.10.7 + runtime + + + + io.jsonwebtoken + jjwt-orgjson + 0.10.7 + runtime + org.flywaydb flyway-core diff --git a/src/main/java/com/example/vue/config/SecurityConfiguration.java b/src/main/java/com/example/vue/config/SecurityConfiguration.java index e821d34..9b884ff 100644 --- a/src/main/java/com/example/vue/config/SecurityConfiguration.java +++ b/src/main/java/com/example/vue/config/SecurityConfiguration.java @@ -2,7 +2,9 @@ package com.example.vue.config; import com.example.vue.config.security.UserDetailsAuthenticationProvider; import com.example.vue.domain.user.UserDetailsServiceImpl; +import com.example.vue.util.JwtUtil; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; @@ -20,6 +22,9 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; @RequiredArgsConstructor public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Value("${jwt.secret}") + private String secret; + private final UserDetailsServiceImpl userDetailService; @Override @@ -50,4 +55,9 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public AuthenticationProvider authenticationProvider() { return new UserDetailsAuthenticationProvider(userDetailService, bCryptPasswordEncoder()); } + + @Bean + public JwtUtil jwtUtil() { + return new JwtUtil(secret); + } } diff --git a/src/main/java/com/example/vue/domain/auth/AuthController.java b/src/main/java/com/example/vue/domain/auth/AuthController.java index 581ac39..af59c3c 100644 --- a/src/main/java/com/example/vue/domain/auth/AuthController.java +++ b/src/main/java/com/example/vue/domain/auth/AuthController.java @@ -17,8 +17,7 @@ public class AuthController { @PostMapping(value = "/login") public LoginResponseDto login(@RequestBody @Valid LoginRequestDto loginRequestDto) { - authService.login(loginRequestDto); - return null; + return authService.login(loginRequestDto); } @PostMapping(value = "/register") diff --git a/src/main/java/com/example/vue/domain/auth/AuthException.java b/src/main/java/com/example/vue/domain/auth/AuthException.java index 28e1c36..0872f1c 100644 --- a/src/main/java/com/example/vue/domain/auth/AuthException.java +++ b/src/main/java/com/example/vue/domain/auth/AuthException.java @@ -11,4 +11,11 @@ public class AuthException { super("존재하지 않는 이메일입니다. [email=" + email + "]"); } } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + public static class PasswordNotMatched extends RuntimeException { + public PasswordNotMatched() { + super("패스워드가 일치하지 않습니다."); + } + } } diff --git a/src/main/java/com/example/vue/domain/auth/AuthService.java b/src/main/java/com/example/vue/domain/auth/AuthService.java index db57dfb..be33b36 100644 --- a/src/main/java/com/example/vue/domain/auth/AuthService.java +++ b/src/main/java/com/example/vue/domain/auth/AuthService.java @@ -1,19 +1,38 @@ package com.example.vue.domain.auth; +import com.example.vue.domain.user.User; import com.example.vue.domain.user.UserRepository; +import com.example.vue.util.JwtUtil; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class AuthService { private final UserRepository userRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final JwtUtil jwtUtil; - public void login(LoginRequestDto loginRequestDto) { + public LoginResponseDto login(LoginRequestDto loginRequestDto) { String email = loginRequestDto.getEmail(); - if (userRepository.findByEmail(email).size() < 1) { + String password = loginRequestDto.getPassword(); + + List users = userRepository.findByEmail(email); + if (users.size() < 1) { throw new AuthException.NoExistEmail(email); } + + User user = users.get(0); + + if (!bCryptPasswordEncoder.matches(password, user.getPassword())) { + throw new AuthException.PasswordNotMatched(); + } + + String token = jwtUtil.createToken(user.getId(), user.getName(), "ROLE_USER"); + return new LoginResponseDto(token); } } diff --git a/src/main/java/com/example/vue/domain/auth/LoginResponseDto.java b/src/main/java/com/example/vue/domain/auth/LoginResponseDto.java index a00bfd8..fa34694 100644 --- a/src/main/java/com/example/vue/domain/auth/LoginResponseDto.java +++ b/src/main/java/com/example/vue/domain/auth/LoginResponseDto.java @@ -5,4 +5,8 @@ import lombok.Data; @Data public class LoginResponseDto { private String token; + + public LoginResponseDto(String token) { + this.token = token; + } } diff --git a/src/main/java/com/example/vue/domain/user/User.java b/src/main/java/com/example/vue/domain/user/User.java index 2c1477e..04ee8b4 100644 --- a/src/main/java/com/example/vue/domain/user/User.java +++ b/src/main/java/com/example/vue/domain/user/User.java @@ -1,5 +1,6 @@ package com.example.vue.domain.user; +import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; @@ -7,12 +8,16 @@ import javax.persistence.*; import java.time.LocalDateTime; @Entity +@Getter @NamedQuery(name = "findByEmail", query = "select u from User u where u.email = :email") public class User { @Id @GeneratedValue private Long id; + @Column(name = "password") + private String password; + @Column(name = "email") private String email; diff --git a/src/main/java/com/example/vue/util/JwtUtil.java b/src/main/java/com/example/vue/util/JwtUtil.java new file mode 100644 index 0000000..0d2bbdd --- /dev/null +++ b/src/main/java/com/example/vue/util/JwtUtil.java @@ -0,0 +1,63 @@ +package com.example.vue.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +import java.security.Key; +import java.util.Date; + +public class JwtUtil { + + private Key key; + + public static long PLUS_MILLS = (1000 * 60 * 60 * 24) * 30L; + + public JwtUtil(String secret) { + this.key = Keys.hmacShaKeyFor(secret.getBytes()); + } + + public String createToken(Long userId, String name, String role) { + + JwtBuilder builder = Jwts.builder() + .claim("userId", userId) + .claim("name", name) + .claim("role", role); + + return builder + .signWith(key, SignatureAlgorithm.HS256) + .setExpiration(expireTime()) + .compact(); + + } + + private Date expireTime() { + Date expireTime = new Date(); + expireTime.setTime(expireTime.getTime() + PLUS_MILLS); + return expireTime; + } + + public Claims getClaims(String token) { + return Jwts.parser() + .setSigningKey(key) + .parseClaimsJws(token) + .getBody(); + + } + + public boolean isUsable(String token) { + try { + Jwts.parser() + .setSigningKey(key) + .parseClaimsJws(token) + .getBody(); + + return true; + + } catch (Exception e) { + throw new RuntimeException("권한이 유효하지 않습니다."); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 839fb06..cf35d6d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,6 @@ spring.jpa.properties.hibernate.format_sql=true server.error.include-stacktrace=never -server.port=7070 \ No newline at end of file +server.port=7070 + +jwt.secret=12345678901234567890123456789012 \ No newline at end of file