Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[로그인, 로그아웃] 글로벌 캐시Redis에서 로컬 캐시로 변경 #108

Merged
merged 8 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ dependencies {
// spatial type
implementation 'org.hibernate:hibernate-spatial:6.3.1.Final'

// Spring cache
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache', version: '3.2.0'

// Caffeine
implementation group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '3.1.8'

// Netty/Resolver/DNS/Native/MacOS
implementation("io.netty:netty-resolver-dns-native-macos:4.1.79.Final:osx-aarch_64")

Expand All @@ -114,6 +120,7 @@ dependencies {

// Spring Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.guzzing.studayserver.domain.auth.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class JwtTokenCacheConfig {

private static final int REFRESH_TOKEN_EXPIRED_DURATION = 30;
private static final int ACCESS_TOKEN_EXPIRED_DURATION = 1;
private static final int MAXIMUM_CACHE_SIZE = 10000;

@Bean
public Cache<String, String> refreshCacheConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(REFRESH_TOKEN_EXPIRED_DURATION, TimeUnit.DAYS)
.maximumSize(MAXIMUM_CACHE_SIZE)
.build();
}

@Bean
public Cache<String, String> logoutCacheConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(ACCESS_TOKEN_EXPIRED_DURATION, TimeUnit.DAYS)
.maximumSize(MAXIMUM_CACHE_SIZE)
.build();
}

}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
package org.guzzing.studayserver.domain.auth.repository;

import com.github.benmanes.caffeine.cache.Cache;
import org.guzzing.studayserver.domain.auth.config.JwtTokenCacheConfig;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import org.guzzing.studayserver.domain.auth.jwt.logout.LogoutToken;
import org.springframework.data.repository.CrudRepository;

public interface LogoutTokenRepository extends CrudRepository<LogoutToken, String> {
@Repository
public class LogoutTokenRepository {

private final JwtTokenCacheConfig jwtTokenCacheConfig;
private final Cache<String, String> logoutCache;
Comment on lines +9 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그아웃 토근도 따로 있나보네요??
JWT 랑 같은 형식인가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 맞습니다.
로그아웃 했지만 만료되지 않은 AccessToken으로 요청을 보낼 위험이 있어서
아직 만료기간이 남은 로그아웃 한 AccessToken을 보관하고 있습니다.
JWT와 같은 형식입니다.


public LogoutTokenRepository(JwtTokenCacheConfig jwtTokenCacheConfig) {
this.jwtTokenCacheConfig = jwtTokenCacheConfig;
this.logoutCache = jwtTokenCacheConfig.logoutCacheConfig();
}

public String save(String logoutToken) {
logoutCache.put(logoutToken, logoutToken);
return logoutToken;
}

public Optional<String> findByLogoutToken(String logoutToken) {
return Optional.ofNullable(logoutCache.getIfPresent(logoutToken));
}

public void delete(String logoutToken) {
logoutCache.invalidate(logoutToken);
}

Optional<LogoutToken> findById(String accessToken);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
package org.guzzing.studayserver.domain.auth.repository;

import java.util.Optional;
import org.guzzing.studayserver.domain.auth.jwt.JwtToken;
import org.springframework.data.repository.CrudRepository;
import com.github.benmanes.caffeine.cache.Cache;
import org.guzzing.studayserver.domain.auth.config.JwtTokenCacheConfig;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.Optional;

@Repository
public interface RefreshTokenRepository extends CrudRepository<JwtToken, String> {
public class RefreshTokenRepository {

private final JwtTokenCacheConfig jwtTokenCacheConfig;
private final Cache<String, String> refreshCache;

public RefreshTokenRepository(JwtTokenCacheConfig jwtTokenCacheConfig) {
this.jwtTokenCacheConfig = jwtTokenCacheConfig;
this.refreshCache = jwtTokenCacheConfig.refreshCacheConfig();
}

public String save(String accessToken, String refreshToken) {
refreshCache.put(accessToken, refreshToken);
return refreshToken;
}

public Optional<String> findByAccessToken(String accessToken) {
return Optional.ofNullable(refreshCache.getIfPresent(accessToken));
}

public List<Map.Entry<String, String>> findAll() {
return refreshCache.asMap()
.entrySet()
.stream()
.toList();
}

Optional<JwtToken> findById(String accessToken);
public void deleteByAccessToken(String accessToken) {
refreshCache.invalidate(accessToken);
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package org.guzzing.studayserver.domain.auth.service;

import io.jsonwebtoken.Claims;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.guzzing.studayserver.domain.auth.exception.TokenExpiredException;
import org.guzzing.studayserver.domain.auth.exception.TokenIsLogoutException;
import org.guzzing.studayserver.domain.auth.jwt.AuthToken;
import org.guzzing.studayserver.domain.auth.jwt.AuthTokenProvider;
import org.guzzing.studayserver.domain.auth.jwt.JwtToken;
import org.guzzing.studayserver.domain.auth.jwt.logout.LogoutToken;
import org.guzzing.studayserver.domain.auth.repository.LogoutTokenRepository;
import org.guzzing.studayserver.domain.auth.repository.RefreshTokenRepository;
import org.guzzing.studayserver.domain.auth.service.dto.AuthLogoutResult;
Expand All @@ -22,15 +23,15 @@
public class AuthService {

private final AuthTokenProvider authTokenProvider;
private final LogoutTokenRepository logoutTokenRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final LogoutTokenRepository logoutTokenRepository;

public AuthService(AuthTokenProvider authTokenProvider,
LogoutTokenRepository logoutTokenRepository,
RefreshTokenRepository refreshTokenRepository) {
RefreshTokenRepository refreshTokenCacheRepository,
LogoutTokenRepository logoutTokenCacheRepository) {
this.authTokenProvider = authTokenProvider;
this.logoutTokenRepository = logoutTokenRepository;
this.refreshTokenRepository = refreshTokenRepository;
this.refreshTokenRepository = refreshTokenCacheRepository;
this.logoutTokenRepository = logoutTokenCacheRepository;
}

@Transactional
Expand All @@ -47,16 +48,16 @@ public AuthRefreshResult updateToken(AuthToken authToken, Long memberId) {

@Transactional
public AuthLogoutResult logout(AuthToken authToken) {
refreshTokenRepository.deleteById(authToken.getToken());
logoutTokenRepository.save(LogoutToken.of(authToken.getToken()));
refreshTokenRepository.deleteByAccessToken(authToken.getToken());
logoutTokenRepository.save(authToken.getToken());

return new AuthLogoutResult(true);
}

@Transactional(readOnly = true)
public boolean isLogout(String accessToken) {
Optional<LogoutToken> byAccessToken = logoutTokenRepository.findById(accessToken);
if (byAccessToken.isPresent()) {
Optional<String> logoutToken = logoutTokenRepository.findByLogoutToken(accessToken);
if (logoutToken.isPresent()) {
throw new TokenIsLogoutException(ErrorCode.IS_LOGOUT_TOKEN);
}

Expand All @@ -68,7 +69,7 @@ public AuthToken saveNewAccessTokenInfo(Long memberId, String socialId, String a
AuthToken refreshToken = findRefreshToken(accessToken);
AuthToken newAccessToken = authTokenProvider.createAccessToken(socialId, memberId);

refreshTokenRepository.save(new JwtToken(refreshToken.getToken(), newAccessToken.getToken()));
refreshTokenRepository.save(newAccessToken.getToken(), refreshToken.getToken());
Comment on lines -71 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 리프레시로 새로 발급하는 거고 , 아래는 아예 모든 토큰을 새로 만드는 거군요!!


return newAccessToken;
}
Expand All @@ -78,7 +79,7 @@ public AuthToken saveAccessTokenCache(Long memberId, String socialId) {
AuthToken newAccessToken = authTokenProvider.createAccessToken(socialId, memberId);
AuthToken newRefreshToken = authTokenProvider.createRefreshToken();

refreshTokenRepository.save(new JwtToken(newRefreshToken.getToken(), newAccessToken.getToken()));
refreshTokenRepository.save(newAccessToken.getToken(), newRefreshToken.getToken());

return newAccessToken;
}
Expand All @@ -88,9 +89,13 @@ private boolean isNotExpiredRefreshToken(String accessToken) {
}

private AuthToken findRefreshToken(String accessToken) {
JwtToken jwtToken = refreshTokenRepository.findById(accessToken)
String refreshToken = refreshTokenRepository.findByAccessToken(accessToken)
.orElseThrow(() -> new TokenExpiredException(ErrorCode.EXPIRED_REFRESH_TOKEN));
return authTokenProvider.convertAuthToken(jwtToken.getRefreshToken());
return authTokenProvider.convertAuthToken(refreshToken);
}

public List<Map.Entry<String, String>> findAll() {
return refreshTokenRepository.findAll();
}

}

This file was deleted.