feat: 로그인 시 refreshToken 추가 발행
This commit is contained in:
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<UserChangePasswordResponse> changePassword(@RequestBody @Valid UserModifyPasswordRequest request) {
|
||||
if (request.oldEqualNew()) {
|
||||
@@ -64,10 +64,11 @@ public class UserController {
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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일
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user