-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: JWT 기반 인증 및 Spring Security 설정 구현 #23 #26
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
9b65ad6
rename: BaseEntity entity 패키지로 이동 (#5)
medoeun eb5b981
feat: jwt 관련 의존성 설정 (#23)
medoeun eb23ac5
feat: `JwtUtil` 기본틀 작성 (#23)
medoeun be5ce80
Merge remote-tracking branch 'origin/main' into feature/jwt-auth
medoeun 938d8e8
feat: jwt 필터 구현 (#23)
medoeun d0d5795
feat: jwt 필터 구현 (#23)
medoeun bc5db59
rename: `jwtUtil` auth.jwt로 이동 (#23)
medoeun 20ca514
feat: application.yml에 jwt 설정 추가 (#23)
medoeun ea88983
feat: CustomUserDetails 추가 (#23)
medoeun 35d94a7
rename: UserDetails 파일 auth/security로 이동 (#23)
medoeun 7b19943
Merge remote-tracking branch 'origin/main' into feature/jwt-auth
medoeun 4a12c87
feat: 시큐리티 관련 에러 설정 추가 (#23)
medoeun 1d4990a
feat: CustomUserDetailsService 구현, SecurityConfig 수정 (#23)
medoeun 666e435
Merge remote-tracking branch 'origin/main' into feature/jwt-auth
medoeun 74fa689
style: 코딩 컨벤션 적용
medoeun b69b667
refactor: 어노테이션 적용 후 로그 추가 및 불필요한 코드 삭제 (#23)
medoeun df88534
Merge remote-tracking branch 'origin/main' into feature/jwt-auth
medoeun ee79c9c
fix: swagger ui 필터 제외 및 필터 메소드 수정 (#23)
medoeun 8d5f488
fix: StatusCode 관련 import 일괄 수정, `UsernameNotFoundException` 401 UNAU…
medoeun b848cb5
refactor: boolean타입 validateToken() -> isValidToken() 변경 (#23)
medoeun 85b7ff6
refactor: claim key 상수화, test yml에 jwt 설정 추가 (#23)
medoeun c91c9ab
rename: JwtUtil 클래스 -> JwtManager로 변경 (#23)
medoeun a0de780
chore: PostController import 수정 (#23)
medoeun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...rated_feed_backend/common/BaseEntity.java → ...eed_backend/common/entity/BaseEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
src/main/java/team05/integrated_feed_backend/core/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package team05.integrated_feed_backend.core.config; | ||
|
||
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; | ||
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 lombok.RequiredArgsConstructor; | ||
import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter; | ||
import team05.integrated_feed_backend.module.auth.jwt.JwtManager; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
@RequiredArgsConstructor | ||
public class SecurityConfig { | ||
|
||
private final JwtManager jwtManager; | ||
private final UserDetailsService userDetailsService; | ||
|
||
@Bean | ||
public JwtAuthenticationFilter jwtAuthenticationFilter() { | ||
return new JwtAuthenticationFilter(jwtManager, userDetailsService); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
|
||
http | ||
.csrf(csrf -> csrf.disable()) // CSRF 보호를 비활성화 (JWT를 사용하기 때문에) | ||
.authorizeHttpRequests(authorize -> authorize | ||
.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() // 그 외의 모든 요청 인증 필요 | ||
) | ||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 X | ||
.addFilterBefore(jwtAuthenticationFilter(), | ||
UsernamePasswordAuthenticationFilter.class); // JWT 필터를 UsernamePasswordAuthenticationFilter 앞에 추가 | ||
|
||
return http.build(); | ||
} | ||
|
||
@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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/main/java/team05/integrated_feed_backend/exception/custom/BadRequestException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/main/java/team05/integrated_feed_backend/exception/custom/DataNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...main/java/team05/integrated_feed_backend/exception/custom/DuplicateResourceException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/main/java/team05/integrated_feed_backend/exception/custom/ForbiddenException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package team05.integrated_feed_backend.module.auth.jwt; | ||
|
||
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 jakarta.servlet.FilterChain; | ||
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 JwtManager jwtManager; | ||
private final UserDetailsService userDetailsService; | ||
|
||
@Override | ||
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 = jwtManager.resolveToken(request); | ||
|
||
if (token != null && jwtManager.isValidToken(token)) { | ||
String account = jwtManager.getAccount(token); | ||
|
||
// UserDetailsService를 통해 UserDetails를 로드 | ||
var userDetails = userDetailsService.loadUserByUsername(account); | ||
medoeun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// 토큰이 유효한 경우 Authentication 생성 | ||
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)jwtManager.getAuthentication( | ||
token, userDetails); | ||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
|
||
// SecurityContext에 객체 설정 | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
// 다음 필터로 요청 전달 | ||
filterChain.doFilter(request, response); | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
src/main/java/team05/integrated_feed_backend/module/auth/jwt/JwtManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package team05.integrated_feed_backend.module.auth.jwt; | ||
|
||
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 io.jsonwebtoken.Claims; | ||
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 JwtManager { | ||
private static final String CLAIM_MEMBER_ID = "memberId"; | ||
private static final String CLAIM_ACCOUNT = "account"; | ||
|
||
private final Key secretKey; | ||
|
||
@Value("${jwt.token-validity-in-seconds}") | ||
private long tokenValidityInseconds; | ||
|
||
//jjwt: String secretKey -> Key 객체 방식으로 대체됨 | ||
public JwtManager(@Value("${jwt.secret}") String secret) { | ||
byte[] keyBytes = Base64.getDecoder().decode(secret); | ||
this.secretKey = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName()); | ||
} | ||
|
||
// 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(CLAIM_MEMBER_ID, memberId); | ||
claims.put(CLAIM_ACCOUNT, account); | ||
|
||
Date now = new Date(); | ||
Date validity = new Date(now.getTime() + tokenValidityInseconds * 1000); | ||
|
||
String token = Jwts.builder() | ||
.setClaims(claims) | ||
.setIssuedAt(now) // 발급 시간 | ||
.setExpiration(validity) // 만료 시간 | ||
.signWith(secretKey, SignatureAlgorithm.HS256) // 서명 | ||
.compact(); | ||
|
||
log.info("JWT 토큰 생성: 사용자 계정 = {}, 만료시간 = {}", account, validity); | ||
return token; | ||
} | ||
|
||
// 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(CLAIM_ACCOUNT, String.class); | ||
} | ||
|
||
// JWT 토큰 유효성 검증 | ||
public boolean isValidToken(String token) { | ||
try { | ||
Jwts.parserBuilder() | ||
.setSigningKey(secretKey) | ||
.build() | ||
.parseClaimsJws(token); | ||
log.debug("JWT 토큰 유효성 검사 통과: {}", token); | ||
return true; | ||
} catch (Exception e) { | ||
log.error("JWT 토큰 유효성 검사 실패: {}", token, e); | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헉 요부분은
BCryptPasswordEncoder
말고Argon2PasswordEncoder
를 쓸 것 같아요!얘가 조금 더 보안이 좋다고 하더라구요! 근데 이건 제가 작업하면서 바꿔놓겠습니당😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇군요!! 저도 더 알아보겠습니당