From 9b65ad632e5066254889509167aabd3a0f08952e Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 13:53:18 +0900 Subject: [PATCH 01/19] =?UTF-8?q?rename:=20BaseEntity=20entity=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/{ => entity}/BaseEntity.java | 2 +- .../module/auth/entity/VerificationCode.java | 2 +- .../integrated_feed_backend/module/post/entity/Hashtag.java | 4 +--- .../integrated_feed_backend/module/post/entity/Post.java | 4 +--- .../module/post/entity/PostHashtag.java | 2 +- .../integrated_feed_backend/module/user/entity/Member.java | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) rename src/main/java/team05/integrated_feed_backend/common/{ => entity}/BaseEntity.java (94%) diff --git a/src/main/java/team05/integrated_feed_backend/common/BaseEntity.java b/src/main/java/team05/integrated_feed_backend/common/entity/BaseEntity.java similarity index 94% rename from src/main/java/team05/integrated_feed_backend/common/BaseEntity.java rename to src/main/java/team05/integrated_feed_backend/common/entity/BaseEntity.java index caa4702..fa29f93 100644 --- a/src/main/java/team05/integrated_feed_backend/common/BaseEntity.java +++ b/src/main/java/team05/integrated_feed_backend/common/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package team05.integrated_feed_backend.common; +package team05.integrated_feed_backend.common.entity; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.persistence.Column; diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java b/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java index 9fd73eb..2083bec 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java @@ -2,7 +2,7 @@ import jakarta.persistence.*; import lombok.*; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; import team05.integrated_feed_backend.module.user.entity.Member; import java.time.LocalDateTime; diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java index ba69d99..9778d68 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java @@ -2,12 +2,10 @@ import jakarta.persistence.*; import lombok.*; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; @Entity @Getter diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java index 84aa3e3..e1f71ee 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java @@ -3,12 +3,10 @@ import jakarta.persistence.*; import lombok.*; import team05.integrated_feed_backend.common.enums.SocialMediaType; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; @Entity diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/PostHashtag.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/PostHashtag.java index 3ecf625..c8b61b9 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/PostHashtag.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/PostHashtag.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.module.post.entity; import jakarta.persistence.*; import lombok.*; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; @Entity @Getter diff --git a/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java b/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java index d80fd2a..ddf91c0 100644 --- a/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java +++ b/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java @@ -2,7 +2,7 @@ import jakarta.persistence.*; import lombok.*; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; @Entity @Getter From eb5b98106643d9b218b30ff703077c18ae252026 Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 14:19:14 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20jwt=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=84=A4=EC=A0=95=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ .../team05/integrated_feed_backend/common/util/JwtUtil.java | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java diff --git a/build.gradle b/build.gradle index 487a046..708e9f9 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.springframework.boot:spring-boot-starter-security' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java new file mode 100644 index 0000000..b4a8095 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java @@ -0,0 +1,5 @@ +package team05.integrated_feed_backend.common.util; + +public class JwtUtil { + +} From eb23ac59aa483ef1d6166fd326411c267635226c Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 15:32:54 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20`JwtUtil`=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=ED=8B=80=20=EC=9E=91=EC=84=B1=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 작업자 이해를 돕기 위한 주석 포함 --- .../common/util/JwtUtil.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java index b4a8095..1c90211 100644 --- a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java @@ -1,5 +1,60 @@ package team05.integrated_feed_backend.common.util; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component public class JwtUtil { + @Value("${jwt.secret}") + // jwt.secret으로 설정된 값을 가져옴 : application.yml + private String secretKey; + + @Value("${jwt.token-validity-in-seconds}") + // JWT 토큰의 유효 시간 설정 : application.yml + private long tokenValidityInseconds; + + // JWT 토큰 생성 (우선 Id, account 직접 받아오는 걸로 가정) + public String generateToken(Long memberId, String account) { + // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체를 memberId로 설정 + Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); + claims.put("memberId", memberId); // memberId 클레임에 포함 + claims.put("account", account); // account를 추가적인 클레임으로 포함 + + Date now = new Date(); + Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) // 발급 시간 + .setExpiration(validity) // 만료 시간 + .signWith(SignatureAlgorithm.HS256, secretKey) // 서명 + .compact(); + } + + // JWT 토큰에서 memberId 추출 (Body: 페이로드) + public Long getMemberId(String token) { + return Long.parseLong(Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject()); + } + + // JWT 토큰에서 account 추출 + public String getAccount(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("account", String.class); + } + + // JWT 토큰 유효성 검증 + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + } From 938d8e85c563fa76cc9d75bc64a4e209ec821a78 Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 18:34:37 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20jwt=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecurityConfig 작성 - JwtAuthenticationFilter 작성 - JwtUtil 보충 - 작업자 이해를 돕기 위한 주석 포함 --- .../common/util/JwtUtil.java | 19 +++++++ .../core/config/SecurityConfig.java | 53 +++++++++++++++++++ .../auth/jwt/JwtAuthenticationFilter.java | 50 +++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java create mode 100644 src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java diff --git a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java index 1c90211..05e55c9 100644 --- a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java @@ -3,7 +3,11 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; @@ -37,6 +41,21 @@ public String generateToken(Long memberId, String account) { .compact(); } + // HTTP 요청 헤더에서 JWT 토큰을 추출 + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // "Bearer " 이후의 토큰 실제부분만 추출 + } + return null; + } + + // JWT 토큰에서 Authentication(인증된 사용자) 가져오기 + public Authentication getAuthentication(String token, UserDetails userDetails) { + return new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + } + // JWT 토큰에서 memberId 추출 (Body: 페이로드) public Long getMemberId(String token) { return Long.parseLong(Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject()); diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java new file mode 100644 index 0000000..f456a6b --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -0,0 +1,53 @@ +package team05.integrated_feed_backend.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import team05.integrated_feed_backend.common.util.JwtUtil; +import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; + +//보안 필터 체인을 구성하고, 어떤 경로에 대해 인증이 필요한지, 어떤 경로는 인증 없이 접근할 수 있는지 설정 +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; + + public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtUtil, userDetailsService); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + http + .csrf(csrf -> csrf.disable()) // CSRF 보호를 비활성화 (JWT를 사용하기 때문에) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용X + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정하기 (회원가입, 로그인 등) + .anyRequest().authenticated() // 그 외의 모든 요청은 인증필요 + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } +} diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f1d0e70 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,50 @@ +package team05.integrated_feed_backend.module.auth.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; +import team05.integrated_feed_backend.common.util.JwtUtil; + +import java.io.IOException; + +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; + + public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String token = jwtUtil.resolveToken(request); + + if (token != null && jwtUtil.validateToken(token)) { + String account = jwtUtil.getAccount(token); + + // UserDetailsService를 통해 UserDetails를 로드 + var userDetails = userDetailsService.loadUserByUsername(account); + + // 토큰이 유효한 경우 Authentication 생성 + UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) jwtUtil.getAuthentication(token, userDetails); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // SecurityContext에 객체 설정 + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 다음 필터로 요청 전달 + filterChain.doFilter(request, response); + } + } +} From d0d57959e48608770dc44c323c5f845414881338 Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 18:35:49 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20jwt=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecurityConfig 작성 - JwtAuthenticationFilter 작성 - JwtUtil 보충 - 작업자 이해를 돕기 위한 주석 포함 --- .../integrated_feed_backend/core/config/SecurityConfig.java | 1 - .../module/auth/jwt/JwtAuthenticationFilter.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index f456a6b..e4517e5 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -13,7 +13,6 @@ import team05.integrated_feed_backend.common.util.JwtUtil; import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; -//보안 필터 체인을 구성하고, 어떤 경로에 대해 인증이 필요한지, 어떤 경로는 인증 없이 접근할 수 있는지 설정 @Configuration @EnableWebSecurity public class SecurityConfig { diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index f1d0e70..e92a816 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -5,7 +5,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; From bc5db5956e206db355d1a4895f7f1331fa8c4cd6 Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 18:45:17 +0900 Subject: [PATCH 06/19] =?UTF-8?q?rename:=20`jwtUtil`=20auth.jwt=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integrated_feed_backend/core/config/SecurityConfig.java | 2 +- .../module/auth/jwt/JwtAuthenticationFilter.java | 1 - .../{common/util => module/auth/jwt}/JwtUtil.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) rename src/main/java/team05/integrated_feed_backend/{common/util => module/auth/jwt}/JwtUtil.java (98%) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index e4517e5..c032243 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -10,7 +10,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import team05.integrated_feed_backend.common.util.JwtUtil; +import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; @Configuration diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index e92a816..69d0720 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -9,7 +9,6 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; -import team05.integrated_feed_backend.common.util.JwtUtil; import java.io.IOException; diff --git a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java similarity index 98% rename from src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java rename to src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index 05e55c9..d6e4366 100644 --- a/src/main/java/team05/integrated_feed_backend/common/util/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -1,4 +1,4 @@ -package team05.integrated_feed_backend.common.util; +package team05.integrated_feed_backend.module.auth.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; From 20ca51435bba4f77f44b29b24d3648b1baaa34ba Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 19:11:53 +0900 Subject: [PATCH 07/19] =?UTF-8?q?feat:=20application.yml=EC=97=90=20jwt=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-template.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application-template.yml b/src/main/resources/application-template.yml index 63ecef8..86feb84 100644 --- a/src/main/resources/application-template.yml +++ b/src/main/resources/application-template.yml @@ -15,3 +15,7 @@ spring: show-sql: true hibernate: ddl-auto: validate + +jwt: + secret: ${JWT_SECRET} # secret key, 만료 시간 환경 변수로 관리 + token-validity-in-seconds: ${JWT_EXPIRATION} \ No newline at end of file From ea88983a185c0a493d9fe395050365d9d97c416b Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 21:38:20 +0900 Subject: [PATCH 08/19] =?UTF-8?q?feat:=20CustomUserDetails=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserDetails에서 객체를 받아오게 JwtUtil 변경 - jjwt 최신 변경 방식에 따라 코드 수정 --- .../module/auth/CustomUserDetails.java | 66 +++++++++++++++++++ .../module/auth/jwt/JwtUtil.java | 54 ++++++++++----- 2 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java b/src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java new file mode 100644 index 0000000..35a246c --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java @@ -0,0 +1,66 @@ +package team05.integrated_feed_backend.module.auth; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import team05.integrated_feed_backend.module.user.entity.Member; + +import java.util.Collection; +import java.util.Collections; + +@Getter +public class CustomUserDetails implements UserDetails { + + private final Member member; + + public CustomUserDetails(Member member) { + this.member = member; + } + + @Override + public String getUsername() { + return member.getAccount(); + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + // 계정 만료 여부 확인 로직 + @Override + public boolean isAccountNonExpired() { + return true; + } + + // 계정 잠김 여부 확인 로직 + @Override + public boolean isAccountNonLocked() { + return true; + } + + // 자격 증명(비밀번호) 만료 여부 확인 로직 + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return "verified".equals(member.getStatus()); // 인증된 상태만 활성화 + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + public Long getMemberId() { + return member.getMemberId(); + } + + public String getEmail() { + return member.getEmail(); + } +} diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index d6e4366..d2f8f46 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -9,26 +9,37 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; +import team05.integrated_feed_backend.module.auth.CustomUserDetails; +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Base64; import java.util.Date; @Component public class JwtUtil { - @Value("${jwt.secret}") - // jwt.secret으로 설정된 값을 가져옴 : application.yml - private String secretKey; + private final Key secretKey; + + //jjwt: String secretKey -> Key 객체 방식으로 대체됨 + public JwtUtil(@Value("${jwt.secret}") String secret) { + byte[] keyBytes = Base64.getDecoder().decode(secret); + this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); + } @Value("${jwt.token-validity-in-seconds}") - // JWT 토큰의 유효 시간 설정 : application.yml private long tokenValidityInseconds; - // JWT 토큰 생성 (우선 Id, account 직접 받아오는 걸로 가정) - public String generateToken(Long memberId, String account) { - // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체를 memberId로 설정 + // JWT 토큰 생성 + public String generateToken(Authentication authentication) { + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + Long memberId = userDetails.getMemberId(); + String account = userDetails.getUsername(); + + // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체 memberId로 Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); - claims.put("memberId", memberId); // memberId 클레임에 포함 - claims.put("account", account); // account를 추가적인 클레임으로 포함 + claims.put("memberId", memberId); + claims.put("account", account); Date now = new Date(); Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); @@ -37,7 +48,7 @@ public String generateToken(Long memberId, String account) { .setClaims(claims) .setIssuedAt(now) // 발급 시간 .setExpiration(validity) // 만료 시간 - .signWith(SignatureAlgorithm.HS256, secretKey) // 서명 + .signWith(secretKey, SignatureAlgorithm.HS256) // 서명 .compact(); } @@ -50,30 +61,43 @@ public String resolveToken(HttpServletRequest request) { return null; } - // JWT 토큰에서 Authentication(인증된 사용자) 가져오기 public Authentication getAuthentication(String token, UserDetails userDetails) { return new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); } + // jjwt : parser() 메서드가 parserBuilder()로 대체됨 // JWT 토큰에서 memberId 추출 (Body: 페이로드) public Long getMemberId(String token) { - return Long.parseLong(Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject()); + return Long.parseLong(Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject()); } // JWT 토큰에서 account 추출 public String getAccount(String token) { - return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("account", String.class); + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .get("account", String.class); } + // JWT 토큰 유효성 검증 // JWT 토큰 유효성 검증 public boolean validateToken(String token) { try { - Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token); return true; } catch (Exception e) { return false; } } - } From 35d94a7ca6d54cd021141ad135d4799320694d68 Mon Sep 17 00:00:00 2001 From: Doeun Date: Fri, 23 Aug 2024 23:21:19 +0900 Subject: [PATCH 09/19] =?UTF-8?q?rename:=20UserDetails=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20auth/security=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/auth/entity/VerificationCode.java | 2 +- .../integrated_feed_backend/module/auth/jwt/JwtUtil.java | 2 +- .../module/auth/{ => security}/CustomUserDetails.java | 6 ++---- .../module/{user => member}/controller/.gitkeep | 0 .../module/{user => member}/dto/.gitkeep | 0 .../module/{user => member}/entity/.gitkeep | 0 .../module/{user => member}/entity/Member.java | 2 +- .../module/{user => member}/repository/.gitkeep | 0 .../module/{user => member}/service/.gitkeep | 0 9 files changed, 5 insertions(+), 7 deletions(-) rename src/main/java/team05/integrated_feed_backend/module/auth/{ => security}/CustomUserDetails.java (88%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/controller/.gitkeep (100%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/dto/.gitkeep (100%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/entity/.gitkeep (100%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/entity/Member.java (91%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/repository/.gitkeep (100%) rename src/main/java/team05/integrated_feed_backend/module/{user => member}/service/.gitkeep (100%) diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java b/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java index 2083bec..1d339b0 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/entity/VerificationCode.java @@ -3,7 +3,7 @@ import jakarta.persistence.*; import lombok.*; import team05.integrated_feed_backend.common.entity.BaseEntity; -import team05.integrated_feed_backend.module.user.entity.Member; +import team05.integrated_feed_backend.module.member.entity.Member; import java.time.LocalDateTime; diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index d2f8f46..d8b244f 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -9,7 +9,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import team05.integrated_feed_backend.module.auth.CustomUserDetails; +import team05.integrated_feed_backend.module.auth.security.CustomUserDetails; import javax.crypto.spec.SecretKeySpec; import java.security.Key; diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java similarity index 88% rename from src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java rename to src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java index 35a246c..fe1f831 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/CustomUserDetails.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java @@ -1,11 +1,9 @@ -package team05.integrated_feed_backend.module.auth; +package team05.integrated_feed_backend.module.auth.security; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import team05.integrated_feed_backend.module.user.entity.Member; +import team05.integrated_feed_backend.module.member.entity.Member; import java.util.Collection; import java.util.Collections; diff --git a/src/main/java/team05/integrated_feed_backend/module/user/controller/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/member/controller/.gitkeep similarity index 100% rename from src/main/java/team05/integrated_feed_backend/module/user/controller/.gitkeep rename to src/main/java/team05/integrated_feed_backend/module/member/controller/.gitkeep diff --git a/src/main/java/team05/integrated_feed_backend/module/user/dto/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/member/dto/.gitkeep similarity index 100% rename from src/main/java/team05/integrated_feed_backend/module/user/dto/.gitkeep rename to src/main/java/team05/integrated_feed_backend/module/member/dto/.gitkeep diff --git a/src/main/java/team05/integrated_feed_backend/module/user/entity/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/member/entity/.gitkeep similarity index 100% rename from src/main/java/team05/integrated_feed_backend/module/user/entity/.gitkeep rename to src/main/java/team05/integrated_feed_backend/module/member/entity/.gitkeep diff --git a/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java b/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java similarity index 91% rename from src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java rename to src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java index ddf91c0..84b907e 100644 --- a/src/main/java/team05/integrated_feed_backend/module/user/entity/Member.java +++ b/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java @@ -1,4 +1,4 @@ -package team05.integrated_feed_backend.module.user.entity; +package team05.integrated_feed_backend.module.member.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/team05/integrated_feed_backend/module/user/repository/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/member/repository/.gitkeep similarity index 100% rename from src/main/java/team05/integrated_feed_backend/module/user/repository/.gitkeep rename to src/main/java/team05/integrated_feed_backend/module/member/repository/.gitkeep diff --git a/src/main/java/team05/integrated_feed_backend/module/user/service/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/member/service/.gitkeep similarity index 100% rename from src/main/java/team05/integrated_feed_backend/module/user/service/.gitkeep rename to src/main/java/team05/integrated_feed_backend/module/member/service/.gitkeep From 4a12c875624309a3e125c472249c91e1b0b891a2 Mon Sep 17 00:00:00 2001 From: Doeun Date: Sat, 24 Aug 2024 02:45:51 +0900 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UsernameNotFoundException 처리 핸들러 추가 - StatudsCode에 상태코드 추가 - CustomUserDetailsService 구현 진행 중 --- .../exception/GlobalExceptionHandler.java | 14 ++++++++++ .../exception/code/StatusCode.java | 1 + .../security/CustomUserDetailsService.java | 26 +++++++++++++++++++ .../member/repository/MemberRepository.java | 11 ++++++++ 4 files changed, 52 insertions(+) create mode 100644 src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java create mode 100644 src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java diff --git a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java index 3bd9293..1b1ba87 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; @@ -147,6 +148,18 @@ public BaseApiResponse handleUnknownException(Exception e) { return BaseApiResponse.of(statusCode); } + /** + * 요청된 사용자를 찾을 수 없는 경우 + * ex) 로그인 시 존재하지 않는 사용자를 입력한 경우 + **/ + @ResponseStatus(NOT_FOUND) + @ExceptionHandler({UsernameNotFoundException.class}) + public BaseApiResponse handleUsernameNotFoundException(UsernameNotFoundException e) { + log.warn(e.getMessage(), e); + + return BaseApiResponse.of(StatusCode.USER_NOT_FOUND); + } + /** * validation 검사 실패한 항목 에러 메세지 만드는 메서드 */ @@ -192,4 +205,5 @@ private static StatusCode getStatusCodeFromException(BusinessException e) { return e.getStatusCode(); } + } diff --git a/src/main/java/team05/integrated_feed_backend/exception/code/StatusCode.java b/src/main/java/team05/integrated_feed_backend/exception/code/StatusCode.java index 7b408a0..0f16e57 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/code/StatusCode.java +++ b/src/main/java/team05/integrated_feed_backend/exception/code/StatusCode.java @@ -17,6 +17,7 @@ public enum StatusCode { * 400 번대 CODE **/ METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "요청 경로가 지원되지 않습니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "요청된 사용자를 찾을 수 없습니다."), /** * 500 번대 CODE diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java new file mode 100644 index 0000000..9d0a8cf --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java @@ -0,0 +1,26 @@ +package team05.integrated_feed_backend.module.auth.security; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.module.member.entity.Member; +import team05.integrated_feed_backend.module.member.repository.MemberRepository; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + private final MemberRepository memberRepository; + + public CustomUserDetailsService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { + Member member = memberRepository.findByAccount(account) + .orElseThrow(() -> new UsernameNotFoundException(StatusCode.USER_NOT_FOUND.name())); + + return new CustomUserDetails(member); + } +} diff --git a/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java b/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java new file mode 100644 index 0000000..c8211e5 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java @@ -0,0 +1,11 @@ +package team05.integrated_feed_backend.module.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import team05.integrated_feed_backend.module.member.entity.Member; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByAccount(String account); +} + From 1d4990ae1a40f9d14728f2e758962c4a92f927af Mon Sep 17 00:00:00 2001 From: Doeun Date: Sat, 24 Aug 2024 03:09:31 +0900 Subject: [PATCH 11/19] =?UTF-8?q?feat:=20CustomUserDetailsService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20SecurityConfig=20=EC=88=98=EC=A0=95=20(#2?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/config/SecurityConfig.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index c032243..747a305 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -3,11 +3,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; @@ -38,15 +41,24 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용X .authorizeHttpRequests(authorize -> authorize .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정하기 (회원가입, 로그인 등) - .anyRequest().authenticated() // 그 외의 모든 요청은 인증필요 + .anyRequest().authenticated() // 그 외의 모든 요청 인증 필요 ) - .addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 return http.build(); } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { - return authenticationConfiguration.getAuthenticationManager(); + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); + authManagerBuilder + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + return authManagerBuilder.build(); } } From 74fa689a8d2fde7fd9def324318a21a739f979b0 Mon Sep 17 00:00:00 2001 From: Doeun Date: Sat, 24 Aug 2024 11:50:21 +0900 Subject: [PATCH 12/19] =?UTF-8?q?style:=20=EC=BD=94=EB=94=A9=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=A4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/config/SecurityConfig.java | 72 +++---- .../auth/jwt/JwtAuthenticationFilter.java | 60 +++--- .../module/auth/jwt/JwtUtil.java | 182 +++++++++--------- .../auth/security/CustomUserDetails.java | 92 ++++----- .../security/CustomUserDetailsService.java | 21 +- .../module/member/entity/Member.java | 34 ++-- .../member/repository/MemberRepository.java | 7 +- 7 files changed, 242 insertions(+), 226 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index 747a305..e4748e4 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -4,7 +4,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -13,52 +12,53 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; + import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; +import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; @Configuration @EnableWebSecurity public class SecurityConfig { - private final JwtUtil jwtUtil; - private final UserDetailsService userDetailsService; + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; - public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) { - this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; - } + public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } - @Bean - public JwtAuthenticationFilter jwtAuthenticationFilter() { - return new JwtAuthenticationFilter(jwtUtil, userDetailsService); - } + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtUtil, userDetailsService); + } - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .csrf(csrf -> csrf.disable()) // CSRF 보호를 비활성화 (JWT를 사용하기 때문에) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용X - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정하기 (회원가입, 로그인 등) - .anyRequest().authenticated() // 그 외의 모든 요청 인증 필요 - ) - .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 + http + .csrf(csrf -> csrf.disable()) // CSRF 보호를 비활성화 (JWT를 사용하기 때문에) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용X + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정하기 (회원가입, 로그인 등) + .anyRequest().authenticated() // 그 외의 모든 요청 인증 필요 + ) + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 - return http.build(); - } + return http.build(); + } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } - @Bean - public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { - AuthenticationManagerBuilder authManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); - authManagerBuilder - .userDetailsService(userDetailsService) - .passwordEncoder(passwordEncoder()); - return authManagerBuilder.build(); - } + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); + authManagerBuilder + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + return authManagerBuilder.build(); + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index 69d0720..a6711d6 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -1,48 +1,50 @@ package team05.integrated_feed_backend.module.auth.jwt; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtUtil jwtUtil; - private final UserDetailsService userDetailsService; + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; + + public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } - public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { - this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; - } + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + String token = jwtUtil.resolveToken(request); - String token = jwtUtil.resolveToken(request); + if (token != null && jwtUtil.validateToken(token)) { + String account = jwtUtil.getAccount(token); - if (token != null && jwtUtil.validateToken(token)) { - String account = jwtUtil.getAccount(token); + // UserDetailsService를 통해 UserDetails를 로드 + var userDetails = userDetailsService.loadUserByUsername(account); - // UserDetailsService를 통해 UserDetails를 로드 - var userDetails = userDetailsService.loadUserByUsername(account); - - // 토큰이 유효한 경우 Authentication 생성 - UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) jwtUtil.getAuthentication(token, userDetails); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + // 토큰이 유효한 경우 Authentication 생성 + UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)jwtUtil.getAuthentication( + token, userDetails); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - // SecurityContext에 객체 설정 - SecurityContextHolder.getContext().setAuthentication(authentication); + // SecurityContext에 객체 설정 + SecurityContextHolder.getContext().setAuthentication(authentication); - // 다음 필터로 요청 전달 - filterChain.doFilter(request, response); - } - } + // 다음 필터로 요청 전달 + filterChain.doFilter(request, response); + } + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index d8b244f..66e397a 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -1,103 +1,105 @@ package team05.integrated_feed_backend.module.auth.jwt; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import jakarta.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +import javax.crypto.spec.SecretKeySpec; + import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import team05.integrated_feed_backend.module.auth.security.CustomUserDetails; -import javax.crypto.spec.SecretKeySpec; -import java.security.Key; -import java.util.Base64; -import java.util.Date; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.HttpServletRequest; +import team05.integrated_feed_backend.module.auth.security.CustomUserDetails; @Component public class JwtUtil { - private final Key secretKey; - - //jjwt: String secretKey -> Key 객체 방식으로 대체됨 - public JwtUtil(@Value("${jwt.secret}") String secret) { - byte[] keyBytes = Base64.getDecoder().decode(secret); - this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); - } - - @Value("${jwt.token-validity-in-seconds}") - private long tokenValidityInseconds; - - // JWT 토큰 생성 - public String generateToken(Authentication authentication) { - CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); - Long memberId = userDetails.getMemberId(); - String account = userDetails.getUsername(); - - // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체 memberId로 - Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); - claims.put("memberId", memberId); - claims.put("account", account); - - Date now = new Date(); - Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); - - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) // 발급 시간 - .setExpiration(validity) // 만료 시간 - .signWith(secretKey, SignatureAlgorithm.HS256) // 서명 - .compact(); - } - - // HTTP 요청 헤더에서 JWT 토큰을 추출 - public String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (bearerToken != null && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); // "Bearer " 이후의 토큰 실제부분만 추출 - } - return null; - } - - public Authentication getAuthentication(String token, UserDetails userDetails) { - return new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - } - - // jjwt : parser() 메서드가 parserBuilder()로 대체됨 - // JWT 토큰에서 memberId 추출 (Body: 페이로드) - public Long getMemberId(String token) { - return Long.parseLong(Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody() - .getSubject()); - } - - // JWT 토큰에서 account 추출 - public String getAccount(String token) { - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody() - .get("account", String.class); - } - - // JWT 토큰 유효성 검증 - // JWT 토큰 유효성 검증 - public boolean validateToken(String token) { - try { - Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token); - return true; - } catch (Exception e) { - return false; - } - } + private final Key secretKey; + + //jjwt: String secretKey -> Key 객체 방식으로 대체됨 + public JwtUtil(@Value("${jwt.secret}") String secret) { + byte[] keyBytes = Base64.getDecoder().decode(secret); + this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); + } + + @Value("${jwt.token-validity-in-seconds}") + private long tokenValidityInseconds; + + // JWT 토큰 생성 + public String generateToken(Authentication authentication) { + CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal(); + Long memberId = userDetails.getMemberId(); + String account = userDetails.getUsername(); + + // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체 memberId로 + Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); + claims.put("memberId", memberId); + claims.put("account", account); + + Date now = new Date(); + Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) // 발급 시간 + .setExpiration(validity) // 만료 시간 + .signWith(secretKey, SignatureAlgorithm.HS256) // 서명 + .compact(); + } + + // HTTP 요청 헤더에서 JWT 토큰을 추출 + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // "Bearer " 이후의 토큰 실제부분만 추출 + } + return null; + } + + public Authentication getAuthentication(String token, UserDetails userDetails) { + return new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + } + + // jjwt : parser() 메서드가 parserBuilder()로 대체됨 + // JWT 토큰에서 memberId 추출 (Body: 페이로드) + public Long getMemberId(String token) { + return Long.parseLong(Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject()); + } + + // JWT 토큰에서 account 추출 + public String getAccount(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .get("account", String.class); + } + + // JWT 토큰 유효성 검증 + // JWT 토큰 유효성 검증 + public boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java index fe1f831..ae6225c 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetails.java @@ -1,64 +1,66 @@ package team05.integrated_feed_backend.module.auth.security; -import lombok.Getter; +import java.util.Collection; +import java.util.Collections; + import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import team05.integrated_feed_backend.module.member.entity.Member; -import java.util.Collection; -import java.util.Collections; +import lombok.Getter; +import team05.integrated_feed_backend.module.member.entity.Member; @Getter public class CustomUserDetails implements UserDetails { - private final Member member; + private final Member member; + + public CustomUserDetails(Member member) { + this.member = member; + } - public CustomUserDetails(Member member) { - this.member = member; - } + @Override + public String getUsername() { + return member.getAccount(); + } - @Override - public String getUsername() { - return member.getAccount(); - } + @Override + public String getPassword() { + return member.getPassword(); + } - @Override - public String getPassword() { - return member.getPassword(); - } + // 계정 만료 여부 확인 로직 + @Override + public boolean isAccountNonExpired() { + return true; + } - // 계정 만료 여부 확인 로직 - @Override - public boolean isAccountNonExpired() { - return true; - } + // 계정 잠김 여부 확인 로직 + @Override + public boolean isAccountNonLocked() { + return true; + } - // 계정 잠김 여부 확인 로직 - @Override - public boolean isAccountNonLocked() { - return true; - } + // 자격 증명(비밀번호) 만료 여부 확인 로직 + @Override + public boolean isCredentialsNonExpired() { + return true; + } - // 자격 증명(비밀번호) 만료 여부 확인 로직 - @Override - public boolean isCredentialsNonExpired() { - return true; - } + @Override + public boolean isEnabled() { + return "verified".equals(member.getStatus()); // 인증된 상태만 활성화 + } - @Override - public boolean isEnabled() { - return "verified".equals(member.getStatus()); // 인증된 상태만 활성화 - } + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - public Long getMemberId() { - return member.getMemberId(); - } + public Long getMemberId() { + return member.getMemberId(); + } - public String getEmail() { - return member.getEmail(); - } + public String getEmail() { + return member.getEmail(); + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java index 9d0a8cf..737b92f 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java @@ -4,23 +4,24 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; + import team05.integrated_feed_backend.exception.code.StatusCode; import team05.integrated_feed_backend.module.member.entity.Member; import team05.integrated_feed_backend.module.member.repository.MemberRepository; @Service public class CustomUserDetailsService implements UserDetailsService { - private final MemberRepository memberRepository; + private final MemberRepository memberRepository; - public CustomUserDetailsService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } + public CustomUserDetailsService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } - @Override - public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { - Member member = memberRepository.findByAccount(account) - .orElseThrow(() -> new UsernameNotFoundException(StatusCode.USER_NOT_FOUND.name())); + @Override + public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { + Member member = memberRepository.findByAccount(account) + .orElseThrow(() -> new UsernameNotFoundException(StatusCode.USER_NOT_FOUND.name())); - return new CustomUserDetails(member); - } + return new CustomUserDetails(member); + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java b/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java index 84b907e..736cd51 100644 --- a/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java +++ b/src/main/java/team05/integrated_feed_backend/module/member/entity/Member.java @@ -1,7 +1,15 @@ package team05.integrated_feed_backend.module.member.entity; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import team05.integrated_feed_backend.common.entity.BaseEntity; @Entity @@ -10,19 +18,19 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder public class Member extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long memberId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long memberId; - @Column(nullable = false, unique = true) - private String account; + @Column(nullable = false, unique = true) + private String account; - @Column(nullable = false) - private String password; + @Column(nullable = false) + private String password; - @Column(nullable = false, unique = true) - private String email; + @Column(nullable = false, unique = true) + private String email; - @Column(nullable = false) - private String status; + @Column(nullable = false) + private String status; } diff --git a/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java b/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java index c8211e5..4a271a7 100644 --- a/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java +++ b/src/main/java/team05/integrated_feed_backend/module/member/repository/MemberRepository.java @@ -1,11 +1,12 @@ package team05.integrated_feed_backend.module.member.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; -import team05.integrated_feed_backend.module.member.entity.Member; -import java.util.Optional; +import team05.integrated_feed_backend.module.member.entity.Member; public interface MemberRepository extends JpaRepository { - Optional findByAccount(String account); + Optional findByAccount(String account); } From b69b6676f2104a328fdcab74e724365e689fc60b Mon Sep 17 00:00:00 2001 From: Doeun Date: Sun, 25 Aug 2024 10:54:08 +0900 Subject: [PATCH 13/19] =?UTF-8?q?refactor:=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=81=EC=9A=A9=20=ED=9B=84=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @Transactional(readOnly = true), @RequiredArgsConstructor, @Slf4j 적용 --- .../core/config/SecurityConfig.java | 7 ++-- .../auth/jwt/JwtAuthenticationFilter.java | 9 +++-- .../module/auth/jwt/JwtUtil.java | 10 ++++-- .../security/CustomUserDetailsService.java | 11 ++++--- .../module/post/entity/Hashtag.java | 33 ++++++++++++------- src/main/resources/application-template.yml | 6 ++-- 6 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index e4748e4..206ed77 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -13,21 +13,18 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import lombok.RequiredArgsConstructor; import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; - public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) { - this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; - } - @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(jwtUtil, userDetailsService); diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index a6711d6..a367600 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -12,17 +12,16 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j +@RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; - public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { - this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; - } - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index 66e397a..03d20ce 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -16,8 +16,10 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; import team05.integrated_feed_backend.module.auth.security.CustomUserDetails; +@Slf4j @Component public class JwtUtil { @@ -46,12 +48,15 @@ public String generateToken(Authentication authentication) { Date now = new Date(); Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); - return Jwts.builder() + String token = Jwts.builder() .setClaims(claims) .setIssuedAt(now) // 발급 시간 .setExpiration(validity) // 만료 시간 .signWith(secretKey, SignatureAlgorithm.HS256) // 서명 .compact(); + + log.info("JWT 토큰 생성: 사용자 계정 = {}, 만료시간 = {}", account, validity); + return token; } // HTTP 요청 헤더에서 JWT 토큰을 추출 @@ -89,7 +94,6 @@ public String getAccount(String token) { .get("account", String.class); } - // JWT 토큰 유효성 검증 // JWT 토큰 유효성 검증 public boolean validateToken(String token) { try { @@ -97,8 +101,10 @@ public boolean validateToken(String token) { .setSigningKey(secretKey) .build() .parseClaimsJws(token); + log.debug("JWT 토큰 유효성 검사 통과: {}", token); return true; } catch (Exception e) { + log.error("JWT 토큰 유효성 검사 실패: {}", token, e); return false; } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java index 737b92f..26aed6a 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java @@ -4,24 +4,27 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import team05.integrated_feed_backend.exception.code.StatusCode; import team05.integrated_feed_backend.module.member.entity.Member; import team05.integrated_feed_backend.module.member.repository.MemberRepository; +@Slf4j @Service +@Transactional(readOnly = true) +@RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private final MemberRepository memberRepository; - public CustomUserDetailsService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - @Override public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { Member member = memberRepository.findByAccount(account) .orElseThrow(() -> new UsernameNotFoundException(StatusCode.USER_NOT_FOUND.name())); + log.info("유저 조회 성공: {}", member.getAccount()); return new CustomUserDetails(member); } } diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java index 9778d68..3ef9278 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java @@ -1,26 +1,37 @@ package team05.integrated_feed_backend.module.post.entity; -import jakarta.persistence.*; -import lombok.*; -import team05.integrated_feed_backend.common.entity.BaseEntity; - import java.util.ArrayList; import java.util.List; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import team05.integrated_feed_backend.common.entity.BaseEntity; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder public class Hashtag extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long hashtagId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long hashtagId; - @Column(nullable = false, unique = true) - private String hashtag; + @Column(nullable = false, unique = true) + private String hashtag; - @OneToMany(mappedBy = "hashtag", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) - private List postHashtags = new ArrayList<>(); + @OneToMany(mappedBy = "hashtag", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + private List postHashtags = new ArrayList<>(); } diff --git a/src/main/resources/application-template.yml b/src/main/resources/application-template.yml index 86feb84..e33d1ce 100644 --- a/src/main/resources/application-template.yml +++ b/src/main/resources/application-template.yml @@ -3,9 +3,9 @@ spring: name: integrated-feed-backend datasource: - url: database-url - username: database-user - password: password + url: ${POSTGRES_DB_URL} + username: ${POSTGRES_DB_USERNAME} + password: ${POSTGRES_DB_PASSWORD} driver-class-name: org.postgresql.Driver jpa: From ee79c9c0f05c10240faac02b542d6c805a232e28 Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 03:49:18 +0900 Subject: [PATCH 14/19] =?UTF-8?q?fix:=20swagger=20ui=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=A0=9C=EC=99=B8=20=EB=B0=8F=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - swagger ui가 빈 페이지 반환하는 문제 확인, ui 관련 요청을 필터에서 제외 - `SecurityConfig`에서 Swagger ui관련 접근 허용 설정 - `JwtAuthenticationFilter`에서 `filterChain.doFilter` 조건문 안에 있어 필터로 전달 안되는 문제 확인 후 수정 --- .../core/config/SecurityConfig.java | 16 +++++++++++--- .../auth/jwt/JwtAuthenticationFilter.java | 12 ++++++++--- .../module/auth/jwt/JwtUtil.java | 6 +++--- .../module/post/entity/Hashtag.java | 21 +++++++++++++------ .../module/post/entity/Post.java | 2 +- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index 206ed77..6035f87 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -35,12 +36,21 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .csrf(csrf -> csrf.disable()) // CSRF 보호를 비활성화 (JWT를 사용하기 때문에) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용X .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정하기 (회원가입, 로그인 등) + .requestMatchers( + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-ui.html", + "/api-docs/**", + "/webjars/**" + ).permitAll() // Swagger UI 관련 경로에 인증 없이 접근 허용 + .requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정 (회원가입, 로그인 등) + .requestMatchers(HttpMethod.POST, "/api/members").permitAll() .anyRequest().authenticated() // 그 외의 모든 요청 인증 필요 ) - .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가 + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 X + .addFilterBefore(jwtAuthenticationFilter(), + UsernamePasswordAuthenticationFilter.class); // JWT 필터를 UsernamePasswordAuthenticationFilter 앞에 추가 return http.build(); } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index a367600..a138120 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -26,6 +26,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // Swagger ui 관련 요청은 필터링하지 않음 + String path = request.getRequestURI(); + if (path.startsWith("/swagger-ui") || path.startsWith("/webjars/") || path.startsWith("/v3/api-docs")) { + filterChain.doFilter(request, response); + return; + } + // request에서 토큰 파싱, 토큰 문자열 반환해 이후 인증 확인 String token = jwtUtil.resolveToken(request); if (token != null && jwtUtil.validateToken(token)) { @@ -41,9 +48,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // SecurityContext에 객체 설정 SecurityContextHolder.getContext().setAuthentication(authentication); - - // 다음 필터로 요청 전달 - filterChain.doFilter(request, response); } + // 다음 필터로 요청 전달 + filterChain.doFilter(request, response); } } diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index 03d20ce..4542e5b 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -25,15 +25,15 @@ public class JwtUtil { private final Key secretKey; + @Value("${jwt.token-validity-in-seconds}") + private long tokenValidityInseconds; + //jjwt: String secretKey -> Key 객체 방식으로 대체됨 public JwtUtil(@Value("${jwt.secret}") String secret) { byte[] keyBytes = Base64.getDecoder().decode(secret); this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); } - @Value("${jwt.token-validity-in-seconds}") - private long tokenValidityInseconds; - // JWT 토큰 생성 public String generateToken(Authentication authentication) { CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal(); diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java index 99eb994..60195d7 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Hashtag.java @@ -1,13 +1,22 @@ package team05.integrated_feed_backend.module.post.entity; -import jakarta.persistence.*; -import lombok.*; -import team05.integrated_feed_backend.common.BaseEntity; - import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import team05.integrated_feed_backend.common.entity.BaseEntity; @Entity @Getter diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java index 99e62e5..4f5e075 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java @@ -18,7 +18,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import team05.integrated_feed_backend.common.BaseEntity; +import team05.integrated_feed_backend.common.entity.BaseEntity; import team05.integrated_feed_backend.common.enums.SocialMediaType; @Entity From 8d5f4888241fe572223a9398c628a12183a4f3e7 Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 14:03:54 +0900 Subject: [PATCH 15/19] =?UTF-8?q?fix:=20StatusCode=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?import=20=EC=9D=BC=EA=B4=84=20=EC=88=98=EC=A0=95,=20`UsernameNo?= =?UTF-8?q?tFoundException`=20401=20UNAUTHORIZED=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - USER_NOT_FOUND에서 UNAUTHORIZED 에러로 수정, 인증 과정 예외임을 명확히함 --- .../common/BaseApiResponse.java | 4 +-- .../common/code/StatusCode.java | 3 +- .../exception/GlobalExceptionHandler.java | 10 +++--- .../exception/custom/BadRequestException.java | 2 +- .../exception/custom/BusinessException.java | 2 +- .../custom/DataNotFoundException.java | 2 +- .../custom/DuplicateResourceException.java | 2 +- .../exception/custom/ForbiddenException.java | 2 +- .../security/CustomUserDetailsService.java | 4 +-- .../controller/PostStatisticsController.java | 2 +- .../user/controller/MemberController.java | 2 +- src/test/resources/application-test.yml | 32 +++++++++++++++++++ src/test/resources/application.yml | 10 ------ 13 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 src/test/resources/application-test.yml delete mode 100644 src/test/resources/application.yml diff --git a/src/main/java/team05/integrated_feed_backend/common/BaseApiResponse.java b/src/main/java/team05/integrated_feed_backend/common/BaseApiResponse.java index b488233..c09be39 100644 --- a/src/main/java/team05/integrated_feed_backend/common/BaseApiResponse.java +++ b/src/main/java/team05/integrated_feed_backend/common/BaseApiResponse.java @@ -4,10 +4,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** - * code, status, message 기본 응답 형식 + * code, status, message 기본 응답 형식 **/ @Getter @Schema(description = "기본 응답 형식") diff --git a/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java b/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java index 46c9413..95af613 100644 --- a/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java +++ b/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java @@ -1,4 +1,4 @@ -package team05.integrated_feed_backend.exception.code; +package team05.integrated_feed_backend.common.code; import org.springframework.http.HttpStatus; @@ -19,6 +19,7 @@ public enum StatusCode { **/ METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "요청 경로가 지원되지 않습니다."), USER_NOT_FOUND(HttpStatus.NOT_FOUND, "요청된 사용자를 찾을 수 없습니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증 오류가 발생했습니다."), /** * 500 번대 CODE diff --git a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java index 1b1ba87..2d9afcc 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java @@ -19,7 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import team05.integrated_feed_backend.common.BaseApiResponse; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; import team05.integrated_feed_backend.exception.custom.BadRequestException; import team05.integrated_feed_backend.exception.custom.BusinessException; import team05.integrated_feed_backend.exception.custom.DataNotFoundException; @@ -149,15 +149,15 @@ public BaseApiResponse handleUnknownException(Exception e) { } /** - * 요청된 사용자를 찾을 수 없는 경우 - * ex) 로그인 시 존재하지 않는 사용자를 입력한 경우 + * 인증 과정에서 사용자를 찾을 수 없는 경우 발생 + * ex) JWT 토큰에 포함된 사용자 이름으로 사용자를 로드할 때 해당 사용자가 존재하지 않는 경우 **/ - @ResponseStatus(NOT_FOUND) + @ResponseStatus(UNAUTHORIZED) @ExceptionHandler({UsernameNotFoundException.class}) public BaseApiResponse handleUsernameNotFoundException(UsernameNotFoundException e) { log.warn(e.getMessage(), e); - return BaseApiResponse.of(StatusCode.USER_NOT_FOUND); + return BaseApiResponse.of(StatusCode.UNAUTHORIZED); } /** diff --git a/src/main/java/team05/integrated_feed_backend/exception/custom/BadRequestException.java b/src/main/java/team05/integrated_feed_backend/exception/custom/BadRequestException.java index b09c907..658c38c 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/custom/BadRequestException.java +++ b/src/main/java/team05/integrated_feed_backend/exception/custom/BadRequestException.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.exception.custom; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** * 요청이 잘못된 경우 diff --git a/src/main/java/team05/integrated_feed_backend/exception/custom/BusinessException.java b/src/main/java/team05/integrated_feed_backend/exception/custom/BusinessException.java index a63b446..84370bd 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/custom/BusinessException.java +++ b/src/main/java/team05/integrated_feed_backend/exception/custom/BusinessException.java @@ -2,7 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** * 비즈니스 로직 중에서 나는 에러 정의 diff --git a/src/main/java/team05/integrated_feed_backend/exception/custom/DataNotFoundException.java b/src/main/java/team05/integrated_feed_backend/exception/custom/DataNotFoundException.java index ca84024..dec1fde 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/custom/DataNotFoundException.java +++ b/src/main/java/team05/integrated_feed_backend/exception/custom/DataNotFoundException.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.exception.custom; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** * 요청 결과가 없는 경우 diff --git a/src/main/java/team05/integrated_feed_backend/exception/custom/DuplicateResourceException.java b/src/main/java/team05/integrated_feed_backend/exception/custom/DuplicateResourceException.java index ab36b1b..0c58b37 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/custom/DuplicateResourceException.java +++ b/src/main/java/team05/integrated_feed_backend/exception/custom/DuplicateResourceException.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.exception.custom; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** * 생성하고자 요청하는 데이터가 이미 있는 경우 diff --git a/src/main/java/team05/integrated_feed_backend/exception/custom/ForbiddenException.java b/src/main/java/team05/integrated_feed_backend/exception/custom/ForbiddenException.java index 78faf5e..09de3bb 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/custom/ForbiddenException.java +++ b/src/main/java/team05/integrated_feed_backend/exception/custom/ForbiddenException.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.exception.custom; import lombok.Getter; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; /** * 권한이 없는 곳에 접근하고자 하는 경우 diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java index 26aed6a..d22b81c 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/security/CustomUserDetailsService.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; import team05.integrated_feed_backend.module.member.entity.Member; import team05.integrated_feed_backend.module.member.repository.MemberRepository; @@ -22,7 +22,7 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { Member member = memberRepository.findByAccount(account) - .orElseThrow(() -> new UsernameNotFoundException(StatusCode.USER_NOT_FOUND.name())); + .orElseThrow(() -> new UsernameNotFoundException(StatusCode.UNAUTHORIZED.name())); log.info("유저 조회 성공: {}", member.getAccount()); return new CustomUserDetails(member); diff --git a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostStatisticsController.java b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostStatisticsController.java index ce2a076..386d07f 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostStatisticsController.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostStatisticsController.java @@ -13,7 +13,7 @@ import lombok.RequiredArgsConstructor; import team05.integrated_feed_backend.common.BaseApiResponse; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; import team05.integrated_feed_backend.module.post.dto.request.PostStatisticsListReq; import team05.integrated_feed_backend.module.post.dto.response.PostStatisticsListRes; import team05.integrated_feed_backend.module.post.service.PostStatisticsService; diff --git a/src/main/java/team05/integrated_feed_backend/module/user/controller/MemberController.java b/src/main/java/team05/integrated_feed_backend/module/user/controller/MemberController.java index 577062c..fe020aa 100644 --- a/src/main/java/team05/integrated_feed_backend/module/user/controller/MemberController.java +++ b/src/main/java/team05/integrated_feed_backend/module/user/controller/MemberController.java @@ -1,7 +1,7 @@ package team05.integrated_feed_backend.module.user.controller; import static org.springframework.http.HttpStatus.*; -import static team05.integrated_feed_backend.exception.code.StatusCode.*; +import static team05.integrated_feed_backend.common.code.StatusCode.*; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..afd6eb1 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,32 @@ +spring: + application: + name: integrated-feed-backend + sql: + init: + mode: always # data.sql + + datasource: + url: ${POSTGRES_DB_URL} + username: ${POSTGRES_DB_USERNAME} + password: ${POSTGRES_DB_PASSWORD} + driver-class-name: org.postgresql.Driver + + jpa: + defer-datasource-initialization: true # data.sql + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + show-sql: true + hibernate: + ddl-auto: validate + +springdoc: + default-consumes-media-type: application/json + default-produces-media-type: application/json + swagger-ui: + tags-sorter: alpha + operations-sorter: method + +jwt: + secret: ${JWT_SECRET} # secret key, 만료 시간 환경 변수로 관리 + token-validity-in-seconds: ${JWT_EXPIRATION} \ No newline at end of file diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index 555fce0..0000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb;MODE=PostgreSQL - username: sa - driver-class-name: org.h2.Driver - - jpa: - show-sql: true - hibernate: - ddl-auto: create-drop From b848cb5d0b311026056c70308a960a6f54faf9f0 Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 14:18:30 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor:=20boolean=ED=83=80=EC=9E=85=20v?= =?UTF-8?q?alidateToken()=20->=20isValidToken()=20=EB=B3=80=EA=B2=BD=20(#2?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/auth/jwt/JwtAuthenticationFilter.java | 2 +- .../team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index a138120..8d28d35 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -35,7 +35,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // request에서 토큰 파싱, 토큰 문자열 반환해 이후 인증 확인 String token = jwtUtil.resolveToken(request); - if (token != null && jwtUtil.validateToken(token)) { + if (token != null && jwtUtil.isValidToken(token)) { String account = jwtUtil.getAccount(token); // UserDetailsService를 통해 UserDetails를 로드 diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index 4542e5b..0f2129f 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -95,7 +95,7 @@ public String getAccount(String token) { } // JWT 토큰 유효성 검증 - public boolean validateToken(String token) { + public boolean isValidToken(String token) { try { Jwts.parserBuilder() .setSigningKey(secretKey) From 85b7ff62de35243ca6d69714741f209fbf226fc3 Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 14:58:41 +0900 Subject: [PATCH 17/19] =?UTF-8?q?refactor:=20claim=20key=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94,=20test=20yml=EC=97=90=20jwt=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/auth/jwt/JwtUtil.java | 8 +++-- src/test/resources/application-test.yml | 32 ------------------- src/test/resources/application.yml | 14 ++++++++ 3 files changed, 19 insertions(+), 35 deletions(-) delete mode 100644 src/test/resources/application-test.yml create mode 100644 src/test/resources/application.yml diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java index 0f2129f..a2364ed 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java @@ -22,6 +22,8 @@ @Slf4j @Component public class JwtUtil { + private static final String CLAIM_MEMBER_ID = "memberId"; + private static final String CLAIM_ACCOUNT = "account"; private final Key secretKey; @@ -42,8 +44,8 @@ public String generateToken(Authentication authentication) { // JWT 토큰에 포함되는 정보(페이로드): jwt의 주체 memberId로 Claims claims = Jwts.claims().setSubject(String.valueOf(memberId)); - claims.put("memberId", memberId); - claims.put("account", account); + claims.put(CLAIM_MEMBER_ID, memberId); + claims.put(CLAIM_ACCOUNT, account); Date now = new Date(); Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); @@ -91,7 +93,7 @@ public String getAccount(String token) { .build() .parseClaimsJws(token) .getBody() - .get("account", String.class); + .get(CLAIM_ACCOUNT, String.class); } // JWT 토큰 유효성 검증 diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml deleted file mode 100644 index afd6eb1..0000000 --- a/src/test/resources/application-test.yml +++ /dev/null @@ -1,32 +0,0 @@ -spring: - application: - name: integrated-feed-backend - sql: - init: - mode: always # data.sql - - datasource: - url: ${POSTGRES_DB_URL} - username: ${POSTGRES_DB_USERNAME} - password: ${POSTGRES_DB_PASSWORD} - driver-class-name: org.postgresql.Driver - - jpa: - defer-datasource-initialization: true # data.sql - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - show-sql: true - hibernate: - ddl-auto: validate - -springdoc: - default-consumes-media-type: application/json - default-produces-media-type: application/json - swagger-ui: - tags-sorter: alpha - operations-sorter: method - -jwt: - secret: ${JWT_SECRET} # secret key, 만료 시간 환경 변수로 관리 - token-validity-in-seconds: ${JWT_EXPIRATION} \ No newline at end of file diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..6f15269 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,14 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;MODE=PostgreSQL + username: sa + driver-class-name: org.h2.Driver + + jpa: + show-sql: true + hibernate: + ddl-auto: create-drop + +jwt: + secret: dGVzdC1zZWNyZXQta2V5 + token-validity-in-seconds: 3600 \ No newline at end of file From c91c9ab1f747918e9269ccd7580eda9486b8775d Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 15:04:52 +0900 Subject: [PATCH 18/19] =?UTF-8?q?rename:=20JwtUtil=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20->=20JwtManager=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#2?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/config/SecurityConfig.java | 6 +++--- .../module/auth/jwt/JwtAuthenticationFilter.java | 10 +++++----- .../module/auth/jwt/{JwtUtil.java => JwtManager.java} | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/team05/integrated_feed_backend/module/auth/jwt/{JwtUtil.java => JwtManager.java} (97%) diff --git a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java index 6035f87..b99a475 100644 --- a/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java +++ b/src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java @@ -16,19 +16,19 @@ import lombok.RequiredArgsConstructor; import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; -import team05.integrated_feed_backend.module.auth.jwt.JwtUtil; +import team05.integrated_feed_backend.module.auth.jwt.JwtManager; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { - private final JwtUtil jwtUtil; + private final JwtManager jwtManager; private final UserDetailsService userDetailsService; @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { - return new JwtAuthenticationFilter(jwtUtil, userDetailsService); + return new JwtAuthenticationFilter(jwtManager, userDetailsService); } @Bean diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java index 8d28d35..7f867c3 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java @@ -19,7 +19,7 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtUtil jwtUtil; + private final JwtManager jwtManager; private final UserDetailsService userDetailsService; @Override @@ -33,16 +33,16 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } // request에서 토큰 파싱, 토큰 문자열 반환해 이후 인증 확인 - String token = jwtUtil.resolveToken(request); + String token = jwtManager.resolveToken(request); - if (token != null && jwtUtil.isValidToken(token)) { - String account = jwtUtil.getAccount(token); + if (token != null && jwtManager.isValidToken(token)) { + String account = jwtManager.getAccount(token); // UserDetailsService를 통해 UserDetails를 로드 var userDetails = userDetailsService.loadUserByUsername(account); // 토큰이 유효한 경우 Authentication 생성 - UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)jwtUtil.getAuthentication( + UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)jwtManager.getAuthentication( token, userDetails); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); diff --git a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtManager.java similarity index 97% rename from src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java rename to src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtManager.java index a2364ed..cc2470a 100644 --- a/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtUtil.java +++ b/src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtManager.java @@ -21,7 +21,7 @@ @Slf4j @Component -public class JwtUtil { +public class JwtManager { private static final String CLAIM_MEMBER_ID = "memberId"; private static final String CLAIM_ACCOUNT = "account"; @@ -31,7 +31,7 @@ public class JwtUtil { private long tokenValidityInseconds; //jjwt: String secretKey -> Key 객체 방식으로 대체됨 - public JwtUtil(@Value("${jwt.secret}") String secret) { + public JwtManager(@Value("${jwt.secret}") String secret) { byte[] keyBytes = Base64.getDecoder().decode(secret); this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); } From a0de7805e5fa3c1df23f53f875eab1e5389f589b Mon Sep 17 00:00:00 2001 From: Doeun Date: Mon, 26 Aug 2024 15:07:26 +0900 Subject: [PATCH 19/19] =?UTF-8?q?chore:=20PostController=20import=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/post/controller/PostController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java index 58e2ef4..c90aa4c 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import team05.integrated_feed_backend.common.BaseApiResponse; -import team05.integrated_feed_backend.exception.code.StatusCode; +import team05.integrated_feed_backend.common.code.StatusCode; import team05.integrated_feed_backend.module.post.dto.request.PostSearchReq; import team05.integrated_feed_backend.module.post.dto.response.PostSearchRes;