feat(user-service): logout 기능, cookie 기능 추가, redis 설정 추가
- logout 기능 추가 - cookie 기능 추가 - redis 설정 추가
This commit is contained in:
@@ -38,6 +38,7 @@ dependencies {
|
|||||||
compileOnly 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
compileOnly 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
||||||
implementation 'org.springframework.cloud:spring-cloud-starter-config'
|
implementation 'org.springframework.cloud:spring-cloud-starter-config'
|
||||||
/*implementation 'org.springframework.kafka:spring-kafka'*/
|
/*implementation 'org.springframework.kafka:spring-kafka'*/
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.github.gavlyukovskiy/p6spy-spring-boot-starter
|
// https://mvnrepository.com/artifact/com.github.gavlyukovskiy/p6spy-spring-boot-starter
|
||||||
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0'
|
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0'
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package com.justpickup.userservice.domain.jwt.exception;
|
|||||||
import com.justpickup.userservice.global.exception.CustomException;
|
import com.justpickup.userservice.global.exception.CustomException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
public class TokenRefreshException extends CustomException {
|
public class AccessTokenNotValidException extends CustomException {
|
||||||
|
|
||||||
public TokenRefreshException(String message) {
|
public AccessTokenNotValidException(String message) {
|
||||||
super(HttpStatus.FORBIDDEN, message);
|
super(HttpStatus.FORBIDDEN, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.justpickup.userservice.domain.jwt.exception;
|
||||||
|
|
||||||
|
import com.justpickup.userservice.global.dto.Result;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class RefreshTokenNotValidException extends RuntimeException {
|
||||||
|
|
||||||
|
private Result result;
|
||||||
|
|
||||||
|
public RefreshTokenNotValidException(String message) {
|
||||||
|
this.result = Result.createErrorResult(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.justpickup.userservice.domain.jwt.redis;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.redis.core.RedisHash;
|
||||||
|
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RedisHash("refresh_token")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
public class RefreshToken {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String userId;
|
||||||
|
private String refreshTokenId;
|
||||||
|
|
||||||
|
public static RefreshToken of(String userId, String refreshTokenId) {
|
||||||
|
RefreshToken refreshToken = new RefreshToken();
|
||||||
|
refreshToken.userId = userId;
|
||||||
|
refreshToken.refreshTokenId = refreshTokenId;
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.justpickup.userservice.domain.jwt.repository;
|
||||||
|
|
||||||
|
import com.justpickup.userservice.domain.jwt.redis.RefreshToken;
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface RefreshTokenRedisRepository extends CrudRepository<RefreshToken, String> {
|
||||||
|
}
|
||||||
@@ -5,4 +5,5 @@ import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
|||||||
public interface RefreshTokenService {
|
public interface RefreshTokenService {
|
||||||
void updateRefreshToken(Long id, String refreshToken);
|
void updateRefreshToken(Long id, String refreshToken);
|
||||||
JwtTokenDto refreshJwtToken(String accessToken, String refreshToken);
|
JwtTokenDto refreshJwtToken(String accessToken, String refreshToken);
|
||||||
|
void logoutToken(String accessToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
package com.justpickup.userservice.domain.jwt.service;
|
package com.justpickup.userservice.domain.jwt.service;
|
||||||
|
|
||||||
import com.justpickup.userservice.domain.jwt.exception.TokenRefreshException;
|
import com.justpickup.userservice.domain.jwt.exception.AccessTokenNotValidException;
|
||||||
|
import com.justpickup.userservice.domain.jwt.exception.RefreshTokenNotValidException;
|
||||||
|
import com.justpickup.userservice.domain.jwt.redis.RefreshToken;
|
||||||
|
import com.justpickup.userservice.domain.jwt.repository.RefreshTokenRedisRepository;
|
||||||
|
import com.justpickup.userservice.global.utils.JwtTokenProvider;
|
||||||
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
||||||
import com.justpickup.userservice.domain.user.entity.User;
|
import com.justpickup.userservice.domain.user.entity.User;
|
||||||
import com.justpickup.userservice.domain.user.exception.NotExistUserException;
|
import com.justpickup.userservice.domain.user.exception.NotExistUserException;
|
||||||
import com.justpickup.userservice.domain.user.repository.UserRepository;
|
import com.justpickup.userservice.domain.user.repository.UserRepository;
|
||||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -21,16 +27,18 @@ import java.util.stream.Collectors;
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RefreshTokenServiceImpl implements RefreshTokenService {
|
public class RefreshTokenServiceImpl implements RefreshTokenService {
|
||||||
private final UserRepository userRepository;
|
private final UserDetailsService userDetailsService;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final RefreshTokenRedisRepository refreshTokenRedisRepository;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public void updateRefreshToken(Long id, String refreshToken) {
|
public void updateRefreshToken(Long id, String uuid) {
|
||||||
User user = userRepository.findById(id)
|
User user = userRepository.findById(id)
|
||||||
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + id + "는 없는 사용자입니다."));
|
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + id + "는 없는 사용자입니다."));
|
||||||
|
|
||||||
user.changeRefreshToken(refreshToken);
|
refreshTokenRedisRepository.save(RefreshToken.of(user.getId().toString(), uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -38,36 +46,51 @@ public class RefreshTokenServiceImpl implements RefreshTokenService {
|
|||||||
public JwtTokenDto refreshJwtToken(String accessToken, String refreshToken) {
|
public JwtTokenDto refreshJwtToken(String accessToken, String refreshToken) {
|
||||||
String userId = jwtTokenProvider.getUserId(accessToken);
|
String userId = jwtTokenProvider.getUserId(accessToken);
|
||||||
|
|
||||||
User user = userRepository.findById(Long.valueOf(userId))
|
RefreshToken findRefreshToken = refreshTokenRedisRepository.findById(userId)
|
||||||
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + userId + "는 없는 사용자입니다."));
|
.orElseThrow(()
|
||||||
|
-> new RefreshTokenNotValidException("사용자 고유번호 : " + userId + "는 등록된 리프레쉬 토큰이 없습니다.")
|
||||||
|
);
|
||||||
|
|
||||||
// refresh token 검증
|
// refresh token 검증
|
||||||
if (!jwtTokenProvider.validateJwtToken(refreshToken)) {
|
String findRefreshTokenId = findRefreshToken.getRefreshTokenId();
|
||||||
// 익셉션 발생 - 로그 아웃 후 로그인 페이지로 이동 처리
|
if (!jwtTokenProvider.validateJwtToken(refreshToken) ||
|
||||||
user.deleteRefreshToken();
|
!jwtTokenProvider.equalRefreshTokenId(findRefreshTokenId, refreshToken)) {
|
||||||
throw new TokenRefreshException("Not validate jwt token = " + refreshToken);
|
|
||||||
|
refreshTokenRedisRepository.delete(findRefreshToken);
|
||||||
|
throw new RefreshTokenNotValidException("Not validate jwt token = " + refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
String userRefreshTokenId = user.getRefreshTokenId();
|
User findUser = userRepository.findById(Long.valueOf(userId))
|
||||||
if (!jwtTokenProvider.equalRefreshTokenId(userRefreshTokenId, refreshToken)) {
|
.orElseThrow(() -> new NotExistUserException("유저 고유 번호 : " + userId + "는 없는 유저입니다."));
|
||||||
// 익셉션 발생 - 로그인 아웃 후 로그인 페이지로 이동 처리
|
|
||||||
user.deleteRefreshToken();
|
|
||||||
throw new TokenRefreshException("Not equal jwt token! user = " + userRefreshTokenId +
|
|
||||||
", refreshToken = " + refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication authentication = jwtTokenProvider.getAuthentication(user.getEmail());
|
// access token 생성
|
||||||
|
Authentication authentication = getAuthentication(findUser.getEmail());
|
||||||
List<String> roles = authentication.getAuthorities()
|
List<String> roles = authentication.getAuthorities()
|
||||||
.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
|
.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
|
||||||
|
|
||||||
String newAccessToken = jwtTokenProvider.createJwtAccessToken(userId, "/refreshToken", roles);
|
String newAccessToken = jwtTokenProvider.createJwtAccessToken(userId, "/refreshToken", roles);
|
||||||
String newRefreshToken = jwtTokenProvider.createJwtRefreshToken();
|
|
||||||
|
|
||||||
user.changeRefreshToken(newRefreshToken);
|
|
||||||
|
|
||||||
return JwtTokenDto.builder()
|
return JwtTokenDto.builder()
|
||||||
.accessToken(newAccessToken)
|
.accessToken(newAccessToken)
|
||||||
.refreshToken(newRefreshToken)
|
.refreshToken(refreshToken)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logoutToken(String accessToken) {
|
||||||
|
if (!jwtTokenProvider.validateJwtToken(accessToken)) {
|
||||||
|
// 예외 발생
|
||||||
|
throw new AccessTokenNotValidException("access token is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshToken refreshToken = refreshTokenRedisRepository.findById(jwtTokenProvider.getUserId(accessToken))
|
||||||
|
.orElseThrow(() -> new RefreshTokenNotValidException("refresh Token is not exist"));
|
||||||
|
|
||||||
|
refreshTokenRedisRepository.delete(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Authentication getAuthentication(String email) {
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
|
||||||
|
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,29 @@ package com.justpickup.userservice.domain.jwt.web;
|
|||||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||||
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
||||||
import com.justpickup.userservice.global.dto.Result;
|
import com.justpickup.userservice.global.dto.Result;
|
||||||
|
import com.justpickup.userservice.global.utils.CookieProvider;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||||
|
private final CookieProvider cookieProvider;
|
||||||
|
|
||||||
@GetMapping("/refreshToken")
|
@GetMapping("/refreshToken")
|
||||||
public ResponseEntity<Result> refreshToken(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
public ResponseEntity<Result> refreshToken(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
||||||
@@ -27,7 +33,11 @@ public class AuthController {
|
|||||||
|
|
||||||
JwtTokenDto jwtTokenDto = refreshTokenServiceImpl.refreshJwtToken(accessToken, refreshToken);
|
JwtTokenDto jwtTokenDto = refreshTokenServiceImpl.refreshJwtToken(accessToken, refreshToken);
|
||||||
|
|
||||||
return ResponseEntity.ok(Result.createSuccessResult(new RefreshTokenResponse(jwtTokenDto)));
|
ResponseCookie responseCookie = cookieProvider.createRefreshTokenCookie(refreshToken);
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.OK)
|
||||||
|
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
|
||||||
|
.body(Result.createSuccessResult(new RefreshTokenResponse(jwtTokenDto)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -35,19 +45,22 @@ public class AuthController {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
static class RefreshTokenResponse {
|
static class RefreshTokenResponse {
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
private String refreshToken;
|
|
||||||
|
|
||||||
public RefreshTokenResponse(JwtTokenDto jwtTokenDto) {
|
public RefreshTokenResponse(JwtTokenDto jwtTokenDto) {
|
||||||
this.accessToken = jwtTokenDto.getAccessToken();
|
this.accessToken = jwtTokenDto.getAccessToken();
|
||||||
this.refreshToken = jwtTokenDto.getRefreshToken();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<Result> logout(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
public ResponseEntity<Result> logout(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
||||||
@RequestHeader("REFRESH-TOKEN") String refreshToken) {
|
@RequestHeader("REFRESH-TOKEN") String refreshToken) {
|
||||||
log.info("########### logout!");
|
|
||||||
// TODO: 2022/02/16 logout 구현 필요
|
refreshTokenServiceImpl.logoutToken(accessToken);
|
||||||
return ResponseEntity.ok(Result.createSuccessResult("success"));
|
|
||||||
|
ResponseCookie refreshCookie = cookieProvider.removeRefreshTokenCookie();
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.OK)
|
||||||
|
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
|
||||||
|
.body(Result.createErrorResult(""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.justpickup.userservice.domain.user.entity;
|
package com.justpickup.userservice.domain.user.entity;
|
||||||
|
|
||||||
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -15,8 +14,8 @@ public class StoreOwner extends User {
|
|||||||
private String businessNumber;
|
private String businessNumber;
|
||||||
|
|
||||||
public StoreOwner(String email, String password, String name, String phoneNumber,
|
public StoreOwner(String email, String password, String name, String phoneNumber,
|
||||||
String businessNumber, String refreshTokenId) {
|
String businessNumber) {
|
||||||
super(email, password, name, phoneNumber, refreshTokenId);
|
super(email, password, name, phoneNumber);
|
||||||
this.businessNumber = businessNumber;
|
this.businessNumber = businessNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import javax.persistence.*;
|
|||||||
@Inheritance(strategy = InheritanceType.JOINED)
|
@Inheritance(strategy = InheritanceType.JOINED)
|
||||||
@DiscriminatorColumn(name = "DTYPE")
|
@DiscriminatorColumn(name = "DTYPE")
|
||||||
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
|
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class User extends BaseEntity {
|
public abstract class User extends BaseEntity {
|
||||||
|
|
||||||
@Id @GeneratedValue
|
@Id @GeneratedValue
|
||||||
@Column(name = "user_id")
|
@Column(name = "user_id")
|
||||||
@@ -26,8 +26,6 @@ public class User extends BaseEntity {
|
|||||||
|
|
||||||
private String phoneNumber;
|
private String phoneNumber;
|
||||||
|
|
||||||
private String refreshTokenId;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private Role role;
|
private Role role;
|
||||||
|
|
||||||
@@ -42,19 +40,11 @@ public class User extends BaseEntity {
|
|||||||
this.role = role;
|
this.role = role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User(String email, String password, String name, String phoneNumber, String refreshTokenId) {
|
public User(String email, String password, String name, String phoneNumber) {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.phoneNumber = phoneNumber;
|
this.phoneNumber = phoneNumber;
|
||||||
this.refreshTokenId = refreshTokenId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeRefreshToken(String refreshToken) {
|
|
||||||
this.refreshTokenId = refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteRefreshToken() {
|
|
||||||
this.refreshTokenId = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.justpickup.userservice.global.config;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RedisConfig {
|
||||||
|
|
||||||
|
private final RedisProperties redisProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisConnectionFactory redisConnectionFactory() {
|
||||||
|
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<?, ?> redisTemplate() {
|
||||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
|
redisTemplate.setConnectionFactory(redisConnectionFactory());
|
||||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
|
redisTemplate.setValueSerializer(new StringRedisSerializer());
|
||||||
|
return redisTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.justpickup.userservice.global.exception;
|
package com.justpickup.userservice.global.exception;
|
||||||
|
|
||||||
|
import com.justpickup.userservice.domain.jwt.exception.RefreshTokenNotValidException;
|
||||||
import com.justpickup.userservice.global.dto.Result;
|
import com.justpickup.userservice.global.dto.Result;
|
||||||
|
import com.justpickup.userservice.global.utils.CookieProvider;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
@@ -11,9 +16,12 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
private final CookieProvider cookieProvider;
|
||||||
|
|
||||||
@ExceptionHandler(CustomException.class)
|
@ExceptionHandler(CustomException.class)
|
||||||
public ResponseEntity customExceptionHandler(CustomException ce) {
|
public ResponseEntity customExceptionHandler(CustomException ce) {
|
||||||
HttpStatus status = ce.getStatus();
|
HttpStatus status = ce.getStatus();
|
||||||
@@ -23,6 +31,16 @@ public class GlobalExceptionHandler {
|
|||||||
.body(errorResult);
|
.body(errorResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(RefreshTokenNotValidException.class)
|
||||||
|
public ResponseEntity customJwtExceptionHandler(RefreshTokenNotValidException e) {
|
||||||
|
// 쿠키 삭제
|
||||||
|
ResponseCookie responseCookie = cookieProvider.removeRefreshTokenCookie();
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||||
|
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
|
||||||
|
.body(e.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
public ResponseEntity bindExceptionHandler(BindException exception) {
|
public ResponseEntity bindExceptionHandler(BindException exception) {
|
||||||
return getValidationErrorBody(exception);
|
return getValidationErrorBody(exception);
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package com.justpickup.userservice.global.security;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
import com.justpickup.userservice.global.utils.JwtTokenProvider;
|
||||||
import com.justpickup.userservice.global.dto.LoginRequest;
|
import com.justpickup.userservice.global.dto.LoginRequest;
|
||||||
|
import com.justpickup.userservice.global.dto.Result;
|
||||||
|
import com.justpickup.userservice.global.utils.CookieProvider;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -16,6 +19,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -32,6 +36,7 @@ public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFil
|
|||||||
private final AuthenticationManager authenticationManager;
|
private final AuthenticationManager authenticationManager;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||||
|
private final CookieProvider cookieProvider;
|
||||||
|
|
||||||
// login 리퀘스트 패스로 오는 요청을 판단
|
// login 리퀘스트 패스로 오는 요청을 판단
|
||||||
@Override
|
@Override
|
||||||
@@ -69,14 +74,20 @@ public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFil
|
|||||||
|
|
||||||
refreshTokenServiceImpl.updateRefreshToken(Long.valueOf(userId), jwtTokenProvider.getRefreshTokenId(refreshToken));
|
refreshTokenServiceImpl.updateRefreshToken(Long.valueOf(userId), jwtTokenProvider.getRefreshTokenId(refreshToken));
|
||||||
|
|
||||||
Map<String, String> tokens = Map.of(
|
// 쿠키 설정
|
||||||
"access_token", accessToken,
|
ResponseCookie refreshTokenCookie = cookieProvider.createRefreshTokenCookie(refreshToken);
|
||||||
"refresh_token", refreshToken
|
|
||||||
);
|
Cookie cookie = cookieProvider.of(refreshTokenCookie);
|
||||||
|
|
||||||
response.setContentType(APPLICATION_JSON_VALUE);
|
response.setContentType(APPLICATION_JSON_VALUE);
|
||||||
|
response.addCookie(cookie);
|
||||||
|
|
||||||
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
|
// body 설정
|
||||||
|
Map<String, String> tokens = Map.of(
|
||||||
|
"access_token", accessToken
|
||||||
|
);
|
||||||
|
|
||||||
|
new ObjectMapper().writeValue(response.getOutputStream(), Result.createSuccessResult(tokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.justpickup.userservice.global.security;
|
package com.justpickup.userservice.global.security;
|
||||||
|
|
||||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
import com.justpickup.userservice.global.utils.JwtTokenProvider;
|
||||||
import com.justpickup.userservice.domain.user.service.UserService;
|
import com.justpickup.userservice.domain.user.service.UserService;
|
||||||
|
import com.justpickup.userservice.global.utils.CookieProvider;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
@@ -23,6 +24,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||||
|
private final CookieProvider cookieProvider;
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
LoginAuthenticationFilter loginAuthenticationFilter =
|
LoginAuthenticationFilter loginAuthenticationFilter =
|
||||||
new LoginAuthenticationFilter(authenticationManagerBean(), jwtTokenProvider, refreshTokenServiceImpl);
|
new LoginAuthenticationFilter(authenticationManagerBean(), jwtTokenProvider, refreshTokenServiceImpl, cookieProvider);
|
||||||
loginAuthenticationFilter.setFilterProcessesUrl("/login");
|
loginAuthenticationFilter.setFilterProcessesUrl("/login");
|
||||||
|
|
||||||
http.csrf().disable();
|
http.csrf().disable();
|
||||||
@@ -45,7 +47,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
|
|
||||||
http.logout()
|
http.logout()
|
||||||
.logoutUrl("/logout")
|
.logoutUrl("/logout")
|
||||||
.deleteCookies("");
|
.deleteCookies("refresh-token");
|
||||||
|
|
||||||
http.oauth2Login()
|
http.oauth2Login()
|
||||||
.defaultSuccessUrl("http://just-pickup.com:8000/customer-frontend-service/")
|
.defaultSuccessUrl("http://just-pickup.com:8000/customer-frontend-service/")
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.justpickup.userservice.global.utils;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CookieProvider {
|
||||||
|
|
||||||
|
@Value("${token.refresh-expired-time}")
|
||||||
|
private String refreshTokenExpiredTime;
|
||||||
|
|
||||||
|
public ResponseCookie createRefreshTokenCookie(String refreshToken) {
|
||||||
|
return ResponseCookie.from("refresh-token", refreshToken)
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(true)
|
||||||
|
.path("/")
|
||||||
|
.maxAge(Long.parseLong(refreshTokenExpiredTime)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseCookie removeRefreshTokenCookie() {
|
||||||
|
return ResponseCookie.from("refresh-token", null)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cookie of(ResponseCookie responseCookie) {
|
||||||
|
Cookie cookie = new Cookie(responseCookie.getName(), responseCookie.getValue());
|
||||||
|
cookie.setPath(responseCookie.getPath());
|
||||||
|
cookie.setSecure(responseCookie.isSecure());
|
||||||
|
cookie.setHttpOnly(responseCookie.isHttpOnly());
|
||||||
|
cookie.setMaxAge((int) responseCookie.getMaxAge().getSeconds());
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.justpickup.userservice.domain.jwt.utils;
|
package com.justpickup.userservice.global.utils;
|
||||||
|
|
||||||
|
|
||||||
import io.jsonwebtoken.*;
|
import io.jsonwebtoken.*;
|
||||||
@@ -16,12 +16,9 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtTokenProvider {
|
public class JwtTokenProvider {
|
||||||
|
|
||||||
private final UserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
@Value("${token.access-expired-time}")
|
@Value("${token.access-expired-time}")
|
||||||
private long ACCESS_EXPIRED_TIME;
|
private long ACCESS_EXPIRED_TIME;
|
||||||
|
|
||||||
@@ -60,29 +57,24 @@ public class JwtTokenProvider {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Authentication getAuthentication(String email) {
|
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
|
|
||||||
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserId(String token) {
|
public String getUserId(String token) {
|
||||||
try {
|
return getClaimsFromJwtToken(token).getSubject();
|
||||||
return getClaimsFromJwtToken(token).getBody().getSubject();
|
|
||||||
} catch (ExpiredJwtException expiredJwtException) {
|
|
||||||
return expiredJwtException.getClaims().getSubject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Jws<Claims> getClaimsFromJwtToken(String token) {
|
private Claims getClaimsFromJwtToken(String token) {
|
||||||
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
|
try {
|
||||||
|
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
return e.getClaims();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRefreshTokenId(String token) {
|
public String getRefreshTokenId(String token) {
|
||||||
try {
|
return getClaimsFromJwtToken(token).get("value").toString();
|
||||||
return getClaimsFromJwtToken(token).getBody().get("value").toString();
|
}
|
||||||
} catch (ExpiredJwtException expiredJwtException) {
|
|
||||||
return expiredJwtException.getClaims().get("value").toString();
|
public List<String> getRoles(String token) {
|
||||||
}
|
return (List<String>) getClaimsFromJwtToken(token).get("roles");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateJwtToken(String token) {
|
public boolean validateJwtToken(String token) {
|
||||||
@@ -26,6 +26,10 @@ spring:
|
|||||||
hibernate:
|
hibernate:
|
||||||
default_batch_fetch_size: 1000
|
default_batch_fetch_size: 1000
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
|
||||||
eureka:
|
eureka:
|
||||||
client:
|
client:
|
||||||
service-url:
|
service-url:
|
||||||
@@ -39,8 +43,6 @@ logging:
|
|||||||
level:
|
level:
|
||||||
com.justpickup: DEBUG
|
com.justpickup: DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# jpa query, parameter 로그 (p6spy)
|
# jpa query, parameter 로그 (p6spy)
|
||||||
decorator.datasource.p6spy:
|
decorator.datasource.p6spy:
|
||||||
enable-logging: true
|
enable-logging: true
|
||||||
@@ -48,4 +50,6 @@ decorator.datasource.p6spy:
|
|||||||
token:
|
token:
|
||||||
access-expired-time: 3600000
|
access-expired-time: 3600000
|
||||||
refresh-expired-time: 604800000
|
refresh-expired-time: 604800000
|
||||||
secret: my-secret
|
secret: my-secret
|
||||||
|
refresh-token-name: refresh-token
|
||||||
|
access-token-name: access-token
|
||||||
Reference in New Issue
Block a user