feat: 로그인 시 refreshToken 추가 발행

This commit is contained in:
dongHyo
2022-06-06 18:10:56 +09:00
parent 270edc20b5
commit d80828026c
12 changed files with 57 additions and 31 deletions

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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일

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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