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

Feat: blackList구현 #103

Merged
merged 32 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ee67cb7
Merge pull request #69 from kakao-tech-campus-2nd-step3/weekly/9
nimunsang Nov 3, 2024
d7e1408
feat:sameSite:None
youcastle03 Nov 9, 2024
5a98cc6
fix: 리프레시토큰 쿠키 만료싲간 재설정
youcastle03 Nov 9, 2024
abdf3eb
fix:ExceptionHandler Return raw type
youcastle03 Nov 9, 2024
16b5f7d
fix:wishListNotFoundException httpStatus type
youcastle03 Nov 9, 2024
2e304f3
feat: redis config
youcastle03 Nov 9, 2024
2a0d0de
feat: redis config
youcastle03 Nov 9, 2024
ff5e368
fix:오타 수정
youcastle03 Nov 10, 2024
662ecdc
Merge pull request #97 from kakao-tech-campus-2nd-step3/weekly/10
nimunsang Nov 10, 2024
50c38c8
feat:BlackListRepository
youcastle03 Nov 10, 2024
c8b0a0f
feat:TokenService
youcastle03 Nov 10, 2024
f9dc815
feat:로그아웃시 블랙리스트 등록
youcastle03 Nov 10, 2024
ee0507e
refactor: cookie 반환 취소
youcastle03 Nov 10, 2024
2a02980
feat: blackList
youcastle03 Nov 10, 2024
06cedc0
feat:sameSite:None
youcastle03 Nov 9, 2024
6af155f
fix: 리프레시토큰 쿠키 만료싲간 재설정
youcastle03 Nov 9, 2024
0e3eba3
fix:ExceptionHandler Return raw type
youcastle03 Nov 9, 2024
362c3b6
fix:wishListNotFoundException httpStatus type
youcastle03 Nov 9, 2024
5d28de9
feat: redis config
youcastle03 Nov 9, 2024
7590486
feat: redis config
youcastle03 Nov 9, 2024
4e8205e
fix:오타 수정
youcastle03 Nov 10, 2024
a217d0c
feat:BlackListRepository
youcastle03 Nov 10, 2024
bd74d07
feat:TokenService
youcastle03 Nov 10, 2024
5b7670c
feat:로그아웃시 블랙리스트 등록
youcastle03 Nov 10, 2024
ac02e1a
refactor: cookie 반환 취소
youcastle03 Nov 10, 2024
1f526f6
feat: blackList
youcastle03 Nov 10, 2024
ead10d9
chore: 빈칸 수정
youcastle03 Nov 10, 2024
ce7b582
충돌 해결
youcastle03 Nov 10, 2024
7823f9f
fix: test용 url수정
youcastle03 Nov 11, 2024
c89b328
fix: url수정
youcastle03 Nov 11, 2024
b340dd2
feat:testyml에 redis정보 수정
youcastle03 Nov 11, 2024
d4717e2
fix:test yml수정
youcastle03 Nov 11, 2024
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
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();
}


}
6 changes: 3 additions & 3 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)
.maxAge(3600*24*14)
.domain(".aeat.jeje.work")
Copy link
Contributor

Choose a reason for hiding this comment

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

.jeje.work로 수정 부탁 드립니다.

  • 인증 방식에 인증 헤더 대신 쿠키 사용 예정인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

쿠키 사용 안할 것 같습니다

.sameSite("LAX")
.sameSite("None")
.build();
ResponseCookie accessCookie = ResponseCookie.from("Authorization-accessToken", tokenResponseDTO.accessToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.domain(".aeat.jeje.work")
.sameSite("LAX")
.sameSite("None")
.build();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, accessCookie.toString());
Expand Down
13 changes: 11 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,30 @@ 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: 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: https://${FRONTEND_DOMAIN}/logout/redirect
logout_redirect_url: http://localhost:8080/api/users/logoutWithKakao/callback
Copy link
Contributor

Choose a reason for hiding this comment

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

엔드포인트 완전히 바뀐건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 테스트 한다고 바꿔놨다가 안바꿨네요 수정하겠습니다.


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

Loading