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

πŸ”€ λ‘œκ·Έμ•„μ›ƒ κΈ°λŠ₯ κ΅¬ν˜„ #10

Merged
merged 9 commits into from
Nov 7, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
Optional<RefreshToken> findByToken(String token);

RefreshToken findByEmail(String email);
Optional<RefreshToken> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto;
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto;
import com.gcms.v3.domain.auth.service.LogoutService;
import com.gcms.v3.domain.auth.service.ReissueTokenService;
import com.gcms.v3.domain.auth.service.SignInService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -15,6 +17,7 @@ public class AuthController {

private final SignInService signInService;
private final ReissueTokenService reissueTokenService;
private final LogoutService logoutService;

@PostMapping
public ResponseEntity<TokenInfoResponseDto> signIn (@RequestBody SignInRequestDto signInRequestDto) {
Expand All @@ -27,4 +30,10 @@ public ResponseEntity<TokenInfoResponseDto> reissueToken (@RequestBody String re
TokenInfoResponseDto res = reissueTokenService.execute(refreshToken);
return ResponseEntity.ok(res);
}

@DeleteMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request) {
logoutService.execute(request);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.gcms.v3.domain.auth.service;

import jakarta.servlet.http.HttpServletRequest;

public interface LogoutService {
void execute(HttpServletRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.gcms.v3.domain.auth.service.impl;

import com.gcms.v3.domain.auth.domain.entity.RefreshToken;
import com.gcms.v3.domain.auth.domain.repository.RefreshTokenRepository;
import com.gcms.v3.domain.auth.exception.UserNotFoundException;
import com.gcms.v3.domain.auth.service.LogoutService;
import com.gcms.v3.domain.user.domain.entity.User;
import com.gcms.v3.domain.user.util.UserUtil;
import com.gcms.v3.global.redis.RedisUtil;
import com.gcms.v3.global.security.jwt.JwtTokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
@Transactional
public class LogoutServiceImpl implements LogoutService {

private final UserUtil userUtil;
private final RefreshTokenRepository refreshTokenRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RedisUtil redisUtil;

public void execute(HttpServletRequest request) {
String accessToken = jwtTokenProvider.resolveToken(request);

User user = userUtil.getCurrentUser();

RefreshToken refreshToken = refreshTokenRepository.findByEmail(user.getEmail())
.orElseThrow(UserNotFoundException::new);

refreshTokenRepository.delete(refreshToken);

redisUtil.setBlackList(accessToken, "access_token", jwtTokenProvider.getExpiration(accessToken));
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/gcms/v3/domain/user/util/UserUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.gcms.v3.domain.user.util;

import com.gcms.v3.domain.auth.exception.UserNotFoundException;
import com.gcms.v3.domain.user.domain.entity.User;
import com.gcms.v3.domain.user.domain.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserUtil {

private final UserRepository userRepository;

public User getCurrentUser() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return userRepository.findByEmail(email)
.orElseThrow(UserNotFoundException::new);
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/gcms/v3/global/redis/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.gcms.v3.global.redis;

import lombok.RequiredArgsConstructor;
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.host(), redisProperties.port());
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setValueSerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory());
Copy link

Choose a reason for hiding this comment

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

여기에 template.setEnableTransactionSupport(true); ν•˜μ‹  λ‹€μŒμ— Service ν΄λž˜μŠ€μ— @transactional μ–΄λ…Έν…Œμ΄μ…˜ λΆ™μ—¬μ£ΌλŠ”κ²Œ μ’‹μ•„λ³΄μ—¬μš”

Copy link
Contributor Author

Choose a reason for hiding this comment

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

4350cd3

μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€

Copy link

Choose a reason for hiding this comment

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

@transaction을 Config λ©”μ„œλ“œκ°€ μ•„λ‹ˆλΌ LogOutServiceService ν΄λž˜μŠ€μ— λΆ™μ—¬μ£Όμ„Έμš”!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

916008b

μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€!

template.setEnableTransactionSupport(true);
return template;
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/gcms/v3/global/redis/RedisProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gcms.v3.global.redis;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.data.redis")
KimTaeO marked this conversation as resolved.
Show resolved Hide resolved
public record RedisProperties (
String host,
int port
){
}
32 changes: 32 additions & 0 deletions src/main/java/com/gcms/v3/global/redis/RedisUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.gcms.v3.global.redis;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
public class RedisUtil {

private final RedisTemplate<String, Object> redisBlackListTemplate;

public boolean checkExistsValue(String value) {
return !value.equals("false");
}

public void setBlackList(String key, Object o, Long milliSeconds) {
redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(o.getClass()));
redisBlackListTemplate.opsForValue().set(key, o, milliSeconds, TimeUnit.MILLISECONDS);
}

public Object getBlackList(String key) {
return redisBlackListTemplate.opsForValue().get(key);
}

public boolean hasKeyBlackList(String key) {
Copy link

Choose a reason for hiding this comment

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

λ©”μ„œλ“œλͺ…... λ­”κ°€ κ΅¬λ¦½λ‹ˆλ‹€

return redisBlackListTemplate.hasKey(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
authorizeRequests
.requestMatchers(HttpMethod.POST, "/v3/auth").permitAll()
.requestMatchers(HttpMethod.POST, "/v3/auth/reissueToken").authenticated()
.requestMatchers(HttpMethod.DELETE, "/v3/auth/logout").authenticated()
)

.addFilterBefore(new ExceptionFilter(objectMapper), UsernamePasswordAuthenticationFilter.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gcms.v3.global.security.jwt;

import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto;
import com.gcms.v3.global.redis.RedisUtil;
import com.gcms.v3.global.security.exception.InvalidAuthTokenException;
import com.gcms.v3.global.security.auth.AuthDetailsService;
import io.jsonwebtoken.*;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class JwtTokenProvider {
private static Key refreshtokenkey;
private final AuthDetailsService authDetailsService;
private final JwtProperties jwtProperties;
private final RedisUtil redisUtil;

@PostConstruct
public void init() {
Expand Down Expand Up @@ -116,6 +118,11 @@ public String resolveToken(HttpServletRequest request) {
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(accessTokenkey).build().parseClaimsJws(token);

if (redisUtil.hasKeyBlackList(token)) {
throw new InvalidAuthTokenException();
}

return true;
} catch (SecurityException | MalformedJwtException e) {
throw new InvalidAuthTokenException();
Expand Down Expand Up @@ -148,4 +155,14 @@ private Claims getTokenBody(String token, Key secret) {
.parseClaimsJws(token)
.getBody();
}

public Long getExpiration(String accessToken) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(accessTokenkey)
.build()
.parseClaimsJws(accessToken)
.getBody();

return claims.getExpiration().getTime();
}
}
Loading