Skip to content

Commit

Permalink
Merge pull request #103 from youcastle03/weekly/10
Browse files Browse the repository at this point in the history
Feat: blackList구현
  • Loading branch information
jjh4450 authored Nov 11, 2024
2 parents d2d97da + d4717e2 commit 6e158a6
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 16 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/spring_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ jobs:
DB_CONTAINER_NAME=super_poo_container
JWT_SECRET_KEY=top_secret_key_for_ci
SERVICE_KEY = test_key
REDIS_PORT = 1111
REDIS_URL = jdbc:redis://aaa/superfoo:1111
REDIS_HOST = yeah
REDIS_ID = hello
REDIS_PASSWORD = redis
EOF
- name: Set up JDK 21
Expand Down
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ dependencies {

//redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
}

tasks.withType<Test> {
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/jeje/work/aeatbe/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package jeje.work.aeatbe.config;

import org.springframework.beans.factory.annotation.Value;
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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

@Value("${spring.data.redis.password}")
private String password;

@Value("${spring.data.redis.username}")
private String username;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
config.setUsername(username);
config.setPassword(password);
return new LettuceConnectionFactory(config);

}


@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());

redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());

redisTemplate.setDefaultSerializer(new StringRedisSerializer());

return redisTemplate;
}



}
5 changes: 3 additions & 2 deletions src/main/java/jeje/work/aeatbe/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public void addInterceptors(InterceptorRegistry registry) {
.addPathPatterns("/api/article/likes/**")
.addPathPatterns("/api/users/logout/**")
. addPathPatterns("/api/wishlist/**")
.addPathPatterns("/api/reviews/my/**");
.addPathPatterns("/api/reviews/my/**")
.addPathPatterns("/api/users/info/**");
}

@Override
Expand All @@ -38,7 +39,7 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("https://aeat.jeje.work", "http://localhost")
.allowedOriginPatterns("https://aeat.jeje.work", "http://localhost","*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import jeje.work.aeatbe.dto.user.TokenResponseDTO;
import jeje.work.aeatbe.dto.user.LoginUserInfo;
import jeje.work.aeatbe.service.KakaoService;
import jeje.work.aeatbe.service.TokenService;
import jeje.work.aeatbe.service.UserService;
import jeje.work.aeatbe.utility.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -29,6 +31,7 @@ public class KakaoAuthController {
private final JwtUtil jwtUtil;
private final KakaoProperties kakaoProperties;
private final KakaoService kakaoService;
private final TokenService tokenService;

/**
* 카카오 로그인페이지로 리다이렉션
Expand All @@ -49,11 +52,11 @@ public void redirectKakaoLogin(HttpServletResponse response) throws IOException
* @return httpHeader(Cookie)
*/
@GetMapping("/callback")
public ResponseEntity<?> getAccessToken(@RequestParam String code) {
public ResponseEntity<TokenResponseDTO> getAccessToken(@RequestParam String code) {
KakaoTokenResponsed token = kakaoService.getKakaoTokenResponse(code);
TokenResponseDTO tokenResponseDto = kakaoService.login(token.accessToken(), token.refreshToken());
HttpHeaders httpHeaders = userService.setCookie(tokenResponseDto);
return ResponseEntity.ok().headers(httpHeaders).build();
return ResponseEntity.ok().body(tokenResponseDto);
}

/**
Expand All @@ -63,9 +66,10 @@ public ResponseEntity<?> getAccessToken(@RequestParam String code) {
* @throws IOException
*/
@PostMapping("/logout")
public void logout(HttpServletResponse response, @LoginUser LoginUserInfo loginUserInfo) throws IOException{
public void logout(HttpServletResponse response, @RequestHeader("Authorization") String token, @LoginUser LoginUserInfo loginUserInfo) throws IOException{
String url = kakaoProperties.logoutUrl() +
"?client_id=" + kakaoProperties.clientId() + "&logout_redirect_uri=" + kakaoProperties.logoutRedirectUrl();
tokenService.addBlackList(tokenService.removePrefix(token), loginUserInfo.userId().toString());
LogoutResponseDto logoutResponseDto = kakaoService.logout(loginUserInfo.userId());
response.sendRedirect(url);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public class TokenController {
public ResponseEntity<TokenResponseDTO> refreshToken(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO){
TokenResponseDTO tokenResponseDto = userService.reissueAccessToken(refreshTokenRequestDTO.refreshToken());
HttpHeaders httpHeaders = userService.setCookie(tokenResponseDto);
return ResponseEntity.ok().headers(httpHeaders).build();
return ResponseEntity.ok().body(tokenResponseDto);
}
}
20 changes: 20 additions & 0 deletions src/main/java/jeje/work/aeatbe/entity/BlackList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package jeje.work.aeatbe.entity;


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@RedisHash(value = "blackList" , timeToLive = 3600)
public class BlackList {

@Id
private String token;

private String userId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ public ResponseEntity illegalArgumentException(IllegalArgumentException e) {
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity illegalStateException(IllegalStateException e) {
public ResponseEntity<String> illegalStateException(IllegalStateException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}

@ExceptionHandler(WishlistNotFoundException.class)
public ResponseEntity<String> wishlistNotFoundException(WishlistNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}

@ExceptionHandler(TokenExpException.class)
public ResponseEntity tokenExpException(TokenExpException e) {
public ResponseEntity<String> tokenExpException(TokenExpException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.servlet.http.HttpServletResponse;
import jeje.work.aeatbe.exception.TokenExpException;
import jeje.work.aeatbe.exception.TokenException;
import jeje.work.aeatbe.service.TokenService;
import jeje.work.aeatbe.service.UserService;
import jeje.work.aeatbe.utility.JwtUtil;
import lombok.AllArgsConstructor;
Expand All @@ -17,6 +18,7 @@ public class JwtInterceptor implements HandlerInterceptor {

private JwtUtil jwtUtil;
private UserService userService;
private TokenService tokenService;

@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
Expand All @@ -29,6 +31,9 @@ public boolean preHandle(final HttpServletRequest request, final HttpServletResp

String token = header.substring(7);

if(tokenService.isInBlackList(token)){
throw new TokenException("이미로그아웃 된 사용자의 토큰입니다. 다시 로그인 해주세요");
}

if(jwtUtil.validTokenExpiration(token, true)) {
throw new TokenExpException("만료된 토큰입니다.");
Expand All @@ -38,6 +43,8 @@ public boolean preHandle(final HttpServletRequest request, final HttpServletResp
if(!userService.validateToken(token)){
throw new TokenException("올바르지 않은 토큰입니다.");
}


return true;


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package jeje.work.aeatbe.repository;

import java.util.Optional;
import jeje.work.aeatbe.entity.BlackList;
import org.springframework.data.repository.CrudRepository;

public interface BlackListRepository extends CrudRepository<BlackList, String> {

}
52 changes: 52 additions & 0 deletions src/main/java/jeje/work/aeatbe/service/TokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package jeje.work.aeatbe.service;

import java.util.Optional;
import jeje.work.aeatbe.entity.BlackList;
import jeje.work.aeatbe.repository.BlackListRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class TokenService {

private final BlackListRepository blackListRepository;
private final RedisTemplate<String, Object> redisTemplate;

/**
* 로그아웃된 엑세스 토큰을 블랙 리스트에 등록
* @param accessToken
* @param userId
*/
public void addBlackList (String accessToken, String userId){
accessToken = removePrefix(accessToken);
BlackList blackList = new BlackList(accessToken, userId);
blackListRepository.save(blackList);
}

/**
* 토큰의 Bearer prefix 제거
* @param token
* @return prefix없는 순수 토큰
*/
public String removePrefix(String token){
if(token.startsWith("Bearer ")){
return token.substring(7);
}
return token;
}

/**
* 블랙시스트에 있는지 확인
* @param accessToken
* @return 유무
*/
public boolean isInBlackList(String accessToken){
Optional<BlackList> blackList = blackListRepository.findById(accessToken);

return !blackList.isEmpty();
}


}
10 changes: 5 additions & 5 deletions src/main/java/jeje/work/aeatbe/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,17 @@ public HttpHeaders setCookie(TokenResponseDTO tokenResponseDTO){
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.domain(".aeat.jeje.work")
.sameSite("LAX")
.maxAge(3600*24*14)
.domain(".jeje.work")
.sameSite("None")
.build();
ResponseCookie accessCookie = ResponseCookie.from("Authorization-accessToken", tokenResponseDTO.accessToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.domain(".aeat.jeje.work")
.sameSite("LAX")
.domain(".jeje.work")
.sameSite("None")
.build();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, accessCookie.toString());
Expand Down
11 changes: 9 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,28 @@ spring:
file: ./docker/compose.yml
skip:
in-tests: false


data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
url: ${REDIS_URL}
password: ${REDIS_PASSWORD}
username: ${REDIS_ID}


kakao:
client_id: ${KAKAO_CLIENT_ID}
redirect_url: https://${FRONTEND_DOMAIN}/login/redirect
redirect_url: http://localhost:8080/api/users/callback
auth_url: https://kauth.kakao.com/oauth/authorize
logout_url: https://kauth.kakao.com/oauth/logout
logout_redirect_url: https://${FRONTEND_DOMAIN}/logout/redirect
logout_redirect_url: http://localhost:8080/api/users/logoutWithKakao/callback

haccp:
service_key : ${SERVICE_KEY}
base_url : https://apis.data.go.kr/B553748/CertImgListServiceV3

default:
image: https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fbafebf27-4e21-4ed6-99e1-983eb90ad9c0%2F75c38d1f-215b-4536-a401-18cf179bf119%2Fimage.png?table=block&id=137a63f1-9420-8046-9cb6-d218151fd876&cache=v2

8 changes: 8 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ spring:
sql:
init:
mode: never
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
url: ${REDIS_URL}
password: ${REDIS_PASSWORD}
username: ${REDIS_ID}


kakao:
client_id: ${KAKAO_CLIENT_ID}
Expand Down

0 comments on commit 6e158a6

Please sign in to comment.