Skip to content

Commit

Permalink
Merge pull request #173 from UMC5th-bias/refactor/#171
Browse files Browse the repository at this point in the history
[REFACTOR] JWT 인증 리팩
  • Loading branch information
JungYoonShin authored Jan 22, 2025
2 parents 2091069 + 0aa3ed9 commit b59498a
Show file tree
Hide file tree
Showing 32 changed files with 554 additions and 442 deletions.
93 changes: 81 additions & 12 deletions logs/logfile.log

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.favoriteplace.app.item.controller;

import com.favoriteplace.global.auth.resolver.UserEmail;
import com.favoriteplace.app.item.controller.dto.ItemDto;
import com.favoriteplace.app.item.service.ShopService;
import com.favoriteplace.global.util.SecurityUtil;
Expand Down Expand Up @@ -33,7 +34,8 @@ public ResponseEntity<ItemDto.ItemListResDto> getAlwaysSellProduct(HttpServletRe
}

@GetMapping("/new")
public ResponseEntity<ItemDto.NewItemListResDto> getNewItemList() {
public ResponseEntity<ItemDto.NewItemListResDto> getNewItemList(@UserEmail String email) {
System.out.println("email = " + email);
return ResponseEntity.ok(shopService.getNewItemList());
}

Expand All @@ -45,7 +47,10 @@ public ResponseEntity<ItemDto.ItemDetailResDto> getItemDetail(
}

@PostMapping("/purchase/{item_id}")
public ResponseEntity<ItemDto.ItemPurchaseRes> buyItem(@PathVariable("item_id") Long itemId) {
public ResponseEntity<ItemDto.ItemPurchaseRes> buyItem(
@PathVariable("item_id") Long itemId,
@UserEmail String userEmail
) {
return ResponseEntity.ok(shopService.buyItem(itemId));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.favoriteplace.app.member.controller;

import com.favoriteplace.app.member.controller.dto.TokenInfoDto;
import com.favoriteplace.global.auth.provider.JwtTokenProvider;
import com.favoriteplace.app.member.controller.dto.KaKaoSignUpRequestDto;
import com.favoriteplace.app.member.controller.dto.MemberDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailCheckReqDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailDuplicateResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailSendResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.MemberSignUpReqDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.TokenInfo;
import com.favoriteplace.app.member.service.MailSendService;
import com.favoriteplace.app.member.service.MemberService;
import com.favoriteplace.global.security.provider.JwtTokenProvider;
import com.favoriteplace.global.util.SecurityUtil;

import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -40,7 +40,7 @@ public class MemberController {
private final SecurityUtil securityUtil;

@PostMapping("/login/kakao")
public ResponseEntity<TokenInfo> kakaoLogin(
public ResponseEntity<TokenInfoDto> kakaoLogin(
@RequestHeader("Authorization") final String token
) {
return ResponseEntity.ok(memberService.kakaoLogin(token));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.favoriteplace.app.member.domain.enums.LoginType;
import com.favoriteplace.app.member.domain.enums.MemberStatus;
import com.favoriteplace.app.item.domain.Item;

import jakarta.validation.constraints.*;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -50,14 +52,14 @@ public static class MemberSignUpResDto {
private String accessToken;
private String refreshToken;

public static MemberSignUpResDto from(Member member, TokenInfo tokenInfo) {
public static MemberSignUpResDto from(Member member, TokenInfoDto tokenInfo) {
return MemberSignUpResDto.builder()
.nickname(member.getNickname())
.introduction(member.getDescription())
.profileImage(member.getProfileImageUrl())
.profileTitleItem(member.getProfileTitle().getDefaultImage().getUrl())
.accessToken(tokenInfo.accessToken)
.refreshToken(tokenInfo.refreshToken)
.accessToken(tokenInfo.accessToken())
.refreshToken(tokenInfo.refreshToken())
.build();
}
}
Expand Down Expand Up @@ -107,16 +109,6 @@ public static class EmailCheckReqDto {
private Integer authNum;
}

@Builder
@Getter
@AllArgsConstructor
public static class TokenInfo {

private String grantType;
private String accessToken;
private String refreshToken;
}

@Builder
@Getter
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.favoriteplace.app.member.controller.dto;


public record TokenInfoDto(
String grantType,
String accessToken,
String refreshToken
) {
public static TokenInfoDto of(
final String accessToken,
final String refreshToken
) {
return new TokenInfoDto("Bearer", accessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
package com.favoriteplace.app.member.service;

import static com.favoriteplace.global.exception.ErrorCode.NOT_SIGNUP_WITH_KAKAO;
import static com.favoriteplace.global.exception.ErrorCode.TOKEN_NOT_VALID;
import static com.favoriteplace.global.exception.ErrorCode.USER_ALREADY_EXISTS;
import static com.favoriteplace.global.exception.ErrorCode.USER_NOT_FOUND;

import com.favoriteplace.app.member.domain.Member;
import com.favoriteplace.app.item.domain.Item;
import com.favoriteplace.app.member.controller.dto.UserInfoResponseDto;
import com.favoriteplace.app.item.repository.ItemRepository;
import com.favoriteplace.app.member.controller.dto.AuthKakaoLoginDto;
import com.favoriteplace.app.member.controller.dto.KaKaoSignUpRequestDto;
import com.favoriteplace.app.member.controller.dto.MemberDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailDuplicateResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailSendReqDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.MemberSignUpReqDto;
import com.favoriteplace.app.item.repository.ItemRepository;
import com.favoriteplace.app.member.controller.dto.TokenInfoDto;
import com.favoriteplace.app.member.controller.dto.UserInfoResponseDto;
import com.favoriteplace.app.member.domain.Member;
import com.favoriteplace.app.member.repository.MemberRepository;
import com.favoriteplace.global.exception.RestApiException;
import com.favoriteplace.global.s3Image.AmazonS3ImageManager;
import com.favoriteplace.global.security.kakao.KakaoClient;
import com.favoriteplace.global.security.provider.JwtTokenProvider;
import com.favoriteplace.global.auth.kakao.KakaoClient;
import com.favoriteplace.global.auth.provider.JwtTokenProvider;
import com.favoriteplace.global.util.SecurityUtil;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
Expand All @@ -38,6 +36,7 @@
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class MemberService {

private final MemberRepository memberRepository;
Expand All @@ -49,7 +48,7 @@ public class MemberService {
private final RedisTemplate redisTemplate;
private final KakaoClient kakaoClient;

public MemberDto.TokenInfo kakaoLogin(final String token) {
public TokenInfoDto kakaoLogin(final String token) {
AuthKakaoLoginDto userInfo = kakaoClient.getUserInfo(token);

// 최초 로그인이라면 회원가입 API로 통신하도록
Expand Down Expand Up @@ -83,16 +82,16 @@ public MemberDto.MemberSignUpResDto kakaoSignUp(
Member member = memberSignUpReqDto.toEntity(profileImageUrl, titleItem, userEmail);
memberRepository.save(member);

MemberDto.TokenInfo tokenInfo = jwtTokenProvider.generateToken(userEmail);
member.updateRefreshToken(tokenInfo.getRefreshToken());
TokenInfoDto tokenInfo = jwtTokenProvider.generateToken(userEmail);
member.updateRefreshToken(tokenInfo.refreshToken());

return MemberDto.MemberSignUpResDto.from(member, tokenInfo);

}

@Transactional
public MemberDto.MemberSignUpResDto signup(
final MemberSignUpReqDto memberSignUpReqDto,
final MemberDto.MemberSignUpReqDto memberSignUpReqDto,
final List<MultipartFile> images
) throws IOException {

Expand All @@ -115,8 +114,8 @@ public MemberDto.MemberSignUpResDto signup(
Member member = memberSignUpReqDto.toEntity(password, profileImageUrl, titleItem);
memberRepository.save(member);

MemberDto.TokenInfo tokenInfo = jwtTokenProvider.generateToken(member.getEmail());
member.updateRefreshToken(tokenInfo.getRefreshToken());
TokenInfoDto tokenInfo = jwtTokenProvider.generateToken(member.getEmail());
member.updateRefreshToken(tokenInfo.refreshToken());

return MemberDto.MemberSignUpResDto.from(member, tokenInfo);
}
Expand All @@ -126,11 +125,11 @@ public String uploadProfileImage(MultipartFile profileImage) throws IOException
}

@Transactional
public MemberDto.EmailDuplicateResDto emailDuplicateCheck(EmailSendReqDto emailSendReqDto) {
public MemberDto.EmailDuplicateResDto emailDuplicateCheck(MemberDto.EmailSendReqDto emailSendReqDto) {
String email = emailSendReqDto.getEmail();
Boolean isExists = memberRepository.findByEmail(email).isPresent();

return new EmailDuplicateResDto(isExists);
return new MemberDto.EmailDuplicateResDto(isExists);
}

@Transactional
Expand All @@ -150,23 +149,20 @@ public UserInfoResponseDto getUserInfo(Member member) {
}

@Transactional
public void logout(String accessToken) {
/*1. Access Token 검증 */
if (!jwtTokenProvider.validateToken(accessToken)) {
new RestApiException(TOKEN_NOT_VALID);
}

Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
public void logout(String token){
Authentication authentication = jwtTokenProvider.getAuthentication(token);
Member member = findMember(authentication.getName());

if (member.getRefreshToken() != null && !member.getRefreshToken().isEmpty()) {
member.deleteRefreshToken(member.getRefreshToken());
}

/* 해당 asscess token 유효시간을 계산해서 blacklist로 저장 */
Long expriation = jwtTokenProvider.getExpiration(accessToken);
Long expriation = jwtTokenProvider.getExpiration(token);
log.info(String.valueOf(expriation));
redisTemplate.opsForValue()
.set(accessToken, "logout", expriation, TimeUnit.MICROSECONDS);
.set(token, "logout", expriation, TimeUnit.MILLISECONDS);

}

public Member findMember(final String email) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.favoriteplace.app.pilgrimage.service.PilgrimageCommandService;
import com.favoriteplace.global.exception.ErrorCode;
import com.favoriteplace.global.exception.RestApiException;
import com.favoriteplace.global.security.CustomUserDetails;
import com.favoriteplace.global.auth.CustomUserDetails;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.favoriteplace.global.security;
package com.favoriteplace.global.auth;

import com.favoriteplace.app.member.domain.Member;
import java.util.Collection;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.favoriteplace.global.auth;

import lombok.RequiredArgsConstructor;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

@RequiredArgsConstructor
public class JwtAuthenticationNeededPath {
private final String pathPattern;
private final String method;

public boolean matches(String requestURI, String method) {
String regex = pathPattern.replaceAll("\\*\\*", ".*");
return Pattern.matches(regex, requestURI) && this.method.equalsIgnoreCase(method);
}

public static final List<JwtAuthenticationNeededPath> NEEDED_JWT_AUTHENTICATION_PATHS = Arrays.asList(
new JwtAuthenticationNeededPath("/auth/logout", "POST"),
new JwtAuthenticationNeededPath("/pilgrimage/**", "POST"),
new JwtAuthenticationNeededPath("/pilgrimage/**", "DELETE"),
new JwtAuthenticationNeededPath("/posts/free/my-posts", "GET"),
new JwtAuthenticationNeededPath("/posts/free/my-comments", "GET"),
new JwtAuthenticationNeededPath("/posts/free", "POST"),
new JwtAuthenticationNeededPath("/posts/free/**", "DELETE"),
new JwtAuthenticationNeededPath("/posts/free/**", "POST"),
new JwtAuthenticationNeededPath("/posts/free/**", "PUT"),
new JwtAuthenticationNeededPath("/posts/free/**", "PATCH"),
new JwtAuthenticationNeededPath("/posts/guestbooks/my-comments", "GET"),
new JwtAuthenticationNeededPath("/posts/guestbooks/my-posts", "GET"),
new JwtAuthenticationNeededPath("/my", "GET"),
new JwtAuthenticationNeededPath("/my/**", "GET"),
new JwtAuthenticationNeededPath("/my/**", "PUT"),
new JwtAuthenticationNeededPath("/my/**", "PATCH"),
new JwtAuthenticationNeededPath("/posts/guestbooks/**", "PATCH"),
new JwtAuthenticationNeededPath("/posts/guestbooks/**", "DELETE"),
new JwtAuthenticationNeededPath("/posts/guestbooks/**", "POST"),
new JwtAuthenticationNeededPath("/my/blocks/**", "POST"),
new JwtAuthenticationNeededPath("/posts/free/comments/**", "PUT"),
new JwtAuthenticationNeededPath("/posts/free/comments/**", "DELETE"),
new JwtAuthenticationNeededPath("/posts/guestbooks/comments/**", "PUT"),
new JwtAuthenticationNeededPath("/posts/guestbooks/comments/**", "DELETE"),
new JwtAuthenticationNeededPath("/shop/purchase/**", "POST"),
new JwtAuthenticationNeededPath("/notifications", "PATCH"),
new JwtAuthenticationNeededPath("/notifications", "GET"),
new JwtAuthenticationNeededPath("/notifications/**", "PATCH"),
new JwtAuthenticationNeededPath("/notifications/**", "DELETE")
);

public static List<JwtAuthenticationNeededPath> getNeededPaths() {
return NEEDED_JWT_AUTHENTICATION_PATHS;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.favoriteplace.global.security.config;
package com.favoriteplace.global.auth.config;

import java.util.Properties;
import lombok.RequiredArgsConstructor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.favoriteplace.global.security.config;
package com.favoriteplace.global.auth.config;


import com.favoriteplace.global.exception.RestApiException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.favoriteplace.global.security.config;
package com.favoriteplace.global.auth.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.favoriteplace.global.security.config;
package com.favoriteplace.global.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -10,7 +10,6 @@
@Configuration
public class RedisConfig {

// Bean 이름 지정
@Bean(name = "customRedisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.favoriteplace.global.security.config;
package com.favoriteplace.global.auth.config;

import com.favoriteplace.global.auth.filter.ExceptionHandlerFilter;
import com.favoriteplace.global.auth.filter.JwtAuthenticationFilter;
import com.favoriteplace.global.auth.filter.LoginFilter;
import com.favoriteplace.global.auth.handler.CustomAuthenticationFailHandler;
import com.favoriteplace.global.auth.handler.CustomAuthenticationSuccessHandler;
import com.favoriteplace.global.auth.provider.JwtTokenProvider;

import com.favoriteplace.global.security.filter.ExceptionHandlerFilter;
import com.favoriteplace.global.security.filter.JwtAuthenticationEntryPoint;
import com.favoriteplace.global.security.filter.JwtAuthenticationFilter;
import com.favoriteplace.global.security.filter.LoginFilter;
import com.favoriteplace.global.security.handler.CustomAuthenticationFailHandler;
import com.favoriteplace.global.security.handler.CustomAuthenticationSuccessHandler;
import com.favoriteplace.global.security.handler.JwtAccessDeniedHandler;
import com.favoriteplace.global.security.provider.JwtTokenProvider;
import lombok.RequiredArgsConstructor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -31,9 +31,6 @@ public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
private final RedisTemplate redisTemplate;
private final ExceptionHandlerFilter exceptionHandlerFilter;

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final AuthenticationManagerBuilder authManagerBuilder;
private final CustomAuthenticationSuccessHandler successHandler;
private final CustomAuthenticationFailHandler failureHandler;
Expand Down
Loading

0 comments on commit b59498a

Please sign in to comment.