// 설치
sudo apt-get install redis-server
// 실행
sudo service redis-server start
//실행 확인
redis-cli ping
설치과정에서
Which services should be restarted?
라는 메세지가 나오면 그냥 종료하면 된다. (굳이 재시작할 필요 x)
local환경에서 redis를 실행하면 일반적으로 외부에서 접근할 수 없어 비밀번호 설정이 굳이 필요는 없지만 보안을 강화하려면 아래와 같이 접근 비밀번호를 설정할 수 있다.
//redis 설정파일 open
sudo nano /etc/redis/redis.conf
//설정파일에 아래 내용 추가
requirepass yourpassword
spring:
redis:
host: localhost
port: 6379
Lettuce와 Jedis는 모두 자바에서 레디스를 사용하기 위한 클라이언트 라이브러리다.
참고 블로그의 내용을 보면 아래와 같은 이유들로 Lettuce를 사용하는 것이 좋을 것 같다.
아래와 같이 redis 의존성을 추가하면 별도의 의존성 설정 없이 Lettuce를 사용할 수 있다. 반면 Jedis는 별도의 설정이 필요하다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
스프링부트에서 Redis를 사용하는 방법에는 Repository 인터페이스를 정의하는 방법
과 Redis Template을 사용하는 방법
두가지가 있다.
@RedisHash(value = "refreshToken", timeToLive = 25200)
public class RefreshToken {
@Id
private String rtk;
private Long memberId;
public RefreshToken(final String rtk, final Long memberId) {
this.rtk = rtk;
this.memberId = memberId;
}
public String getRefreshToken() {
return rtk;
}
public Long getMemberId() {
return memberId;
}
}
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
Optional<RefreshToken> findById(String refreshToken);
}
@ID
애너테이션이 붙은 필드값을 기준으로 데이터를 가져오고자 할 때 컬럼명을 기준으로 메서드명을 작성하는게 아니라 ID를 기준으로 작성해야한다.findByRtk
X findById
O@Service
@RequiredArgsConstructor
public class TokenService {
private final RefreshTokenRepository refreshTokenRepository;
private final JwtTokenizer jwtTokenizer;
// Access Token을 생성하는 구체적인 로직
public String delegateAccessToken(Member member) {
String email = member.getEmail();
Map<String, Object> claims = new HashMap<>();
claims.put("email", email);
claims.put("roles", member.getRoles());
claims.put("nickName", member.getNickName());
Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getAccessTokenExpirationMinutes());
String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
String accessToken = jwtTokenizer.generateAccessToken(claims, email, expiration, base64EncodedSecretKey);
return "Bearer " + accessToken;
}
// Refresh Token을 생성하는 구체적인 로직
public String delegateRefreshToken(Member member) {
String subject = member.getEmail();
Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getRefreshTokenExpirationMinutes());
String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
String refreshToken = jwtTokenizer.generateRefreshToken(subject, expiration, base64EncodedSecretKey);
RefreshToken rtk = new RefreshToken(refreshToken, member.getId());
refreshTokenRepository.save(rtk);
return "Bearer " + refreshToken;
}
}
@RestController
@RequestMapping("/refresh")
@RequiredArgsConstructor
public class RefreshController {
private final JwtTokenizer jwtTokenizer;
private final MemberRepository memberRepository;
private final TokenService tokenService;
private final RefreshTokenRepository refreshTokenRepository;
/**
* 리프레쉬 토큰 받으면 엑세스 토큰 재발급
*/
@PostMapping
public ResponseEntity<String> refreshAccessToken(HttpServletRequest request) {
String refreshTokenHeader = request.getHeader("Refresh");
if (refreshTokenHeader != null && refreshTokenHeader.startsWith("Bearer ")) {
String refreshToken = refreshTokenHeader.substring(7);
try {
Optional<RefreshToken> refreshTokenObj = refreshTokenRepository.findById(refreshToken);
if (!refreshTokenObj.isPresent()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token [redis]");
}
Jws<Claims> claims = jwtTokenizer.getClaims(refreshToken, jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey()));
String email = claims.getBody().getSubject();
Optional<Member> optionalMember = memberRepository.findByEmail(email);
if (optionalMember.isPresent()) {
Member member = optionalMember.get();
String accessToken = tokenService.delegateAccessToken(member);
return ResponseEntity.ok().header("Authorization", "Bearer " + accessToken).body("Access token refreshed");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid member email");
}
} catch (JwtException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token");
}
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing refresh token");
}
}
/**
* 로그아웃할 때 리프래쉬 토큰을 삭제
*/
@DeleteMapping
public ResponseEntity<String> logout(HttpServletRequest request) {
String refreshTokenHeader = request.getHeader("Refresh");
if (refreshTokenHeader != null && refreshTokenHeader.startsWith("Bearer ")) {
String refreshToken = refreshTokenHeader.substring(7);
try {
Optional<RefreshToken> refreshTokenObj = refreshTokenRepository.findById(refreshToken);
if (!refreshTokenObj.isPresent()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token [redis]");
}
refreshTokenRepository.deleteById(refreshToken);
return ResponseEntity.ok().body("Logged out successfully, refresh token deleted");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred while deleting refresh token");
}
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing refresh token");
}
}
}