diff --git a/server/src/main/java/com/ticketing/server/global/jwt/JwtProperties.java b/server/src/main/java/com/ticketing/server/global/jwt/JwtProperties.java index c727cdb..c40941e 100644 --- a/server/src/main/java/com/ticketing/server/global/jwt/JwtProperties.java +++ b/server/src/main/java/com/ticketing/server/global/jwt/JwtProperties.java @@ -15,8 +15,10 @@ import org.springframework.context.annotation.PropertySource; public class JwtProperties { private final String accessHeader; + private final String refreshHeader; private final String prefix; private final String secretKey; - private final Integer tokenValidityInSeconds; + private final Integer accessTokenValidityInSeconds; + private final Integer refreshTokenValidityInSeconds; } diff --git a/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java b/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java index ac5fe7c..26442a7 100644 --- a/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java +++ b/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java @@ -31,29 +31,43 @@ public class JwtProvider { private static final String AUTHORITIES_KEY = "auth"; private static final String AUTHORITIES_DELIMITER = ","; - private final long tokenValidityInMilliseconds; + private final long accessTokenValidityInMilliseconds; + private final long refreshTokenValidityInMilliseconds; private final Key key; public JwtProvider(JwtProperties jwtProperties) { - this.tokenValidityInMilliseconds = jwtProperties.getTokenValidityInSeconds(); + this.accessTokenValidityInMilliseconds = jwtProperties.getAccessTokenValidityInSeconds() * 1000L; + this.refreshTokenValidityInMilliseconds = jwtProperties.getRefreshTokenValidityInSeconds() * 1000L; byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getSecretKey()); this.key = Keys.hmacShaKeyFor(keyBytes); } - public String createToken(Authentication authentication) { + public String createAccessToken(Authentication authentication) { + // 만료시간 계산 + long now = (new Date()).getTime(); + Date accessTokenExpiresIn = new Date(now + this.accessTokenValidityInMilliseconds); + + return createToken(authentication, accessTokenExpiresIn); + } + + public String createRefreshToken(Authentication authentication) { + // 만료시간 계산 + long now = (new Date()).getTime(); + Date refreshTokenExpiresIn = new Date(now + this.refreshTokenValidityInMilliseconds); + + return createToken(authentication, refreshTokenExpiresIn); + } + + private String createToken(Authentication authentication, Date expiration) { // 권한 정보 가져오기 String authorities = generateStringToAuthorities(authentication); - // 만료시간 계산 - long now = new Date().getTime(); - Date accessTokenExpiresIn = new Date(now + this.tokenValidityInMilliseconds); - // JWT 생성 return Jwts.builder() .setSubject(authentication.getName()) // email .claim(AUTHORITIES_KEY, authorities) // payload - .setExpiration(accessTokenExpiresIn) // 만료일 + .setExpiration(expiration) // 만료일 .signWith(key, SignatureAlgorithm.HS512) // 서명 키 값 .compact(); } diff --git a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java index 2fceedd..9d88b04 100644 --- a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java +++ b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java @@ -52,8 +52,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .and() .authorizeRequests() - .antMatchers(HttpMethod.POST, "/user").permitAll() - .antMatchers(HttpMethod.POST, "/user/login").permitAll() + .antMatchers(HttpMethod.POST, "/api/user/login").permitAll() + .antMatchers(HttpMethod.POST, "/api/user").permitAll() .antMatchers("/l7check").permitAll() .antMatchers("/actuator/health").permitAll() .anyRequest().authenticated() diff --git a/server/src/main/java/com/ticketing/server/user/application/UserController.java b/server/src/main/java/com/ticketing/server/user/application/UserController.java index 178e191..070686a 100644 --- a/server/src/main/java/com/ticketing/server/user/application/UserController.java +++ b/server/src/main/java/com/ticketing/server/user/application/UserController.java @@ -20,8 +20,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -50,7 +50,7 @@ public class UserController { return ResponseEntity.status(HttpStatus.OK).body(UserDeleteResponse.of(user)); } - @PatchMapping("/password") + @PutMapping("/password") @Secured("ROLE_GUEST") public ResponseEntity changePassword(@RequestBody @Valid UserModifyPasswordRequest request) { if (request.oldEqualNew()) { @@ -64,10 +64,11 @@ public class UserController { @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) { - String accessToken = userService.login(loginRequest.toAuthentication()); + LoginResponse tokenDto = userService.login(loginRequest.toAuthentication()); - response.setHeader(jwtProperties.getAccessHeader(), accessToken); - return ResponseEntity.status(HttpStatus.OK).body(LoginResponse.of(accessToken)); + response.setHeader(jwtProperties.getAccessHeader(), tokenDto.getAccessToken()); + response.setHeader(jwtProperties.getRefreshHeader(), tokenDto.getRefreshToken()); + return ResponseEntity.status(HttpStatus.OK).body(tokenDto); } } diff --git a/server/src/main/java/com/ticketing/server/user/application/response/LoginResponse.java b/server/src/main/java/com/ticketing/server/user/application/response/LoginResponse.java index 004173b..2e459c9 100644 --- a/server/src/main/java/com/ticketing/server/user/application/response/LoginResponse.java +++ b/server/src/main/java/com/ticketing/server/user/application/response/LoginResponse.java @@ -9,9 +9,10 @@ import lombok.Getter; public class LoginResponse { private String accessToken; + private String refreshToken; - public static LoginResponse of(String accessToken) { - return new LoginResponse(accessToken); + public static LoginResponse of(String accessToken, String refreshToken) { + return new LoginResponse(accessToken, refreshToken); } } diff --git a/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java b/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java index 1b35cb7..136ff02 100644 --- a/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java +++ b/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java @@ -2,6 +2,7 @@ package com.ticketing.server.user.service; import com.ticketing.server.global.exception.NotFoundEmailException; import com.ticketing.server.global.jwt.JwtProvider; +import com.ticketing.server.user.application.response.LoginResponse; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.domain.repository.UserRepository; import com.ticketing.server.user.service.dto.ChangePasswordDTO; @@ -31,9 +32,9 @@ public class UserServiceImpl implements UserService { private final JwtProvider jwtProvider; @Override - public String login(UsernamePasswordAuthenticationToken authenticationToken) { + public LoginResponse login(UsernamePasswordAuthenticationToken authenticationToken) { Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); - return jwtProvider.createToken(authentication); + return LoginResponse.of(jwtProvider.createAccessToken(authentication), jwtProvider.createRefreshToken(authentication)); } @Override diff --git a/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java b/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java index 02a6097..d6eb85f 100644 --- a/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java +++ b/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java @@ -1,16 +1,16 @@ package com.ticketing.server.user.service.interfaces; +import com.ticketing.server.user.application.response.LoginResponse; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.service.dto.ChangePasswordDTO; import com.ticketing.server.user.service.dto.DeleteUserDTO; import com.ticketing.server.user.service.dto.SignUpDTO; import javax.validation.Valid; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; public interface UserService { - String login(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken); + LoginResponse login(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken); User register(@Valid SignUpDTO signUpDto); diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index af6de3d..f916b53 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -21,6 +21,8 @@ jasypt: jwt: access-header: ACCESS_TOKEN + refresh-header: REFRESH_TOKEN prefix: Bearer secret-key: Zi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXktZi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXkK - token-validity-in-seconds: 86400 + access-token-validity-in-seconds: 60 # 1분 + refresh-token-validity-in-seconds: 259200 # 3일 diff --git a/server/src/test/java/com/ticketing/server/global/jwt/JwtPropertiesTest.java b/server/src/test/java/com/ticketing/server/global/jwt/JwtPropertiesTest.java index 65fd266..7df31f6 100644 --- a/server/src/test/java/com/ticketing/server/global/jwt/JwtPropertiesTest.java +++ b/server/src/test/java/com/ticketing/server/global/jwt/JwtPropertiesTest.java @@ -28,8 +28,10 @@ class JwtPropertiesTest { // then assertAll( () -> assertThat(jwtProperties.getAccessHeader()).isEqualTo("ACCESS_TOKEN") + , () -> assertThat(jwtProperties.getRefreshHeader()).isEqualTo("REFRESH_TOKEN") , () -> assertThat(jwtProperties.getPrefix()).isEqualTo("Bearer") - , () -> assertThat(jwtProperties.getTokenValidityInSeconds()).isEqualTo(86400) + , () -> assertThat(jwtProperties.getAccessTokenValidityInSeconds()).isEqualTo(60) + , () -> assertThat(jwtProperties.getRefreshTokenValidityInSeconds()).isEqualTo(259200) , () -> assertThat(jwtProperties.getSecretKey()).isNotEmpty()); } diff --git a/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java b/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java index 1d25679..707a28e 100644 --- a/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java +++ b/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java @@ -42,7 +42,7 @@ class TokenProviderTest { new UsernamePasswordAuthenticationToken("ticketing@gmail.com", "123456", Collections.singleton(grantedAuthority)); // when - String token = tokenProvider.createToken(authenticationToken); + String token = tokenProvider.createAccessToken(authenticationToken); // then assertThat(token).isNotEmpty(); @@ -57,7 +57,7 @@ class TokenProviderTest { new UsernamePasswordAuthenticationToken("ticketing@gmail.com", "123456", Collections.singleton(grantedAuthority)); // when - String token = tokenProvider.createToken(authenticationToken); + String token = tokenProvider.createAccessToken(authenticationToken); Authentication authentication = tokenProvider.getAuthentication(token); // then diff --git a/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java b/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java index deaac47..a88f1e5 100644 --- a/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java +++ b/server/src/test/java/com/ticketing/server/user/application/UserControllerTest.java @@ -51,7 +51,7 @@ class UserControllerTest { SignUpRequest signUpRequest = new SignUpRequest("ticketing", "ticketing@gmail.com", "qwe123", "010-2240-7920"); - mvc.perform(post("/user") + mvc.perform(post("/api/user") .content(asJsonString(signUpRequest)) .contentType(MediaType.APPLICATION_JSON)); } @@ -63,14 +63,15 @@ class UserControllerTest { LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe123"); // when - ResultActions actions = mvc.perform(post("/user/login") + ResultActions actions = mvc.perform(post("/api/user/login") .content(asJsonString(request)) .contentType(MediaType.APPLICATION_JSON)); // then actions.andDo(print()) .andExpect(status().isOk()) - .andExpect(header().exists("ACCESS_TOKEN")); + .andExpect(header().exists("ACCESS_TOKEN")) + .andExpect(header().exists("REFRESH_TOKEN")); } @Test @@ -80,7 +81,7 @@ class UserControllerTest { LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe1234"); // when - ResultActions actions = mvc.perform(post("/user/login") + ResultActions actions = mvc.perform(post("/api/user/login") .content(asJsonString(request)) .contentType(MediaType.APPLICATION_JSON)); diff --git a/server/src/test/resources/application.yml b/server/src/test/resources/application.yml index a846681..80291c6 100644 --- a/server/src/test/resources/application.yml +++ b/server/src/test/resources/application.yml @@ -19,6 +19,8 @@ jasypt: jwt: access-header: ACCESS_TOKEN + refresh-header: REFRESH_TOKEN prefix: Bearer secret-key: Zi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXktZi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXkK - token-validity-in-seconds: 86400 + access-token-validity-in-seconds: 60 + refresh-token-validity-in-seconds: 259200