-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from dnd-side-project/develop
Deploy/Security & 일정
- Loading branch information
Showing
45 changed files
with
1,388 additions
and
1,847 deletions.
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
16 changes: 16 additions & 0 deletions
16
src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.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,16 @@ | ||
package com.dnd.jjakkak.domain.jwt.exception; | ||
|
||
import com.dnd.jjakkak.global.exception.GeneralException; | ||
|
||
public class AccessTokenExpiredException extends GeneralException { | ||
private static final String MESSAGE = "엑세스 토큰이 만료됨."; | ||
|
||
public AccessTokenExpiredException() { | ||
super(MESSAGE); | ||
} | ||
|
||
@Override | ||
public int getStatusCode() { | ||
return 401; | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.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,16 @@ | ||
package com.dnd.jjakkak.domain.jwt.exception; | ||
|
||
import com.dnd.jjakkak.global.exception.GeneralException; | ||
|
||
public class MalformedTokenException extends GeneralException { | ||
private static final String MESSAGE = "손상된 토큰."; | ||
|
||
public MalformedTokenException() { | ||
super(MESSAGE); | ||
} | ||
|
||
@Override | ||
public int getStatusCode() { | ||
return 401; | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
src/main/java/com/dnd/jjakkak/domain/jwt/filter/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,103 @@ | ||
package com.dnd.jjakkak.domain.jwt.filter; | ||
|
||
import com.dnd.jjakkak.domain.jwt.exception.AccessTokenExpiredException; | ||
import com.dnd.jjakkak.domain.jwt.exception.MalformedTokenException; | ||
import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; | ||
import com.dnd.jjakkak.domain.member.entity.Member; | ||
import com.dnd.jjakkak.domain.member.entity.Role; | ||
import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; | ||
import com.dnd.jjakkak.domain.member.repository.MemberRepository; | ||
import com.dnd.jjakkak.global.config.security.SecurityConfig; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
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; | ||
import org.springframework.security.authentication.AbstractAuthenticationToken; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContext; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.PatternMatchUtils; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* 검증을 실행하는 필터입니다. | ||
* | ||
* @author 류태웅 | ||
* @version 2024. 08. 03. | ||
*/ | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
private final JwtProvider jwtProvider; | ||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
String path = request.getRequestURI(); | ||
if(PatternMatchUtils.simpleMatch(SecurityConfig.WHITE_LIST, path)){ | ||
log.debug("path: {} -> passed token filter", path); | ||
filterChain.doFilter(request, response); | ||
} | ||
String token = parseBearerToken(request); | ||
log.debug("도착한 토큰: {}", token); | ||
if (token == null) { // Bearer 인증 방식이 아니거나 빈 값일 경우 진행하지 말고 다음 필터로 바로 넘김 | ||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
String kakaoId; | ||
try { | ||
kakaoId = jwtProvider.validate(token); | ||
log.debug("검증된 카카오 ID: {}", kakaoId); | ||
} catch (ExpiredJwtException e) { | ||
log.error("엑세스 토큰이 만료됨", e); | ||
throw new AccessTokenExpiredException(); | ||
} catch (MalformedJwtException e) { | ||
log.error("손상된 토큰", e); | ||
throw new MalformedTokenException(); | ||
} | ||
|
||
if (kakaoId == null) { | ||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
Member member = memberRepository.findByKakaoId(Long.parseLong(kakaoId)) | ||
.orElseThrow(MemberNotFoundException::new); | ||
Role role = member.getRole(); | ||
|
||
List<GrantedAuthority> authorities = new ArrayList<>(); | ||
authorities.add(new SimpleGrantedAuthority(role.toString())); | ||
|
||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); | ||
AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member, null, authorities); | ||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
|
||
securityContext.setAuthentication(authenticationToken); | ||
SecurityContextHolder.setContext(securityContext); | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
|
||
private String parseBearerToken(HttpServletRequest request) { | ||
String authorization = request.getHeader("Authorization"); | ||
if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) { | ||
return authorization.substring(7); | ||
} | ||
return null; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.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,38 @@ | ||
package com.dnd.jjakkak.domain.jwt.handler; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
/** | ||
* 검증 실패 시 예외 처리 링크로 이동하는 핸들러입니다. | ||
* | ||
* @author 류태웅 | ||
* @version 2024. 08. 02. | ||
*/ | ||
|
||
@Component | ||
public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { | ||
|
||
@Override | ||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { | ||
String errorMessage; | ||
if(e instanceof UsernameNotFoundException){ | ||
errorMessage="존재하지 않는 아이디 입니다."; | ||
} | ||
else{ | ||
errorMessage="알 수 없는 이유로 로그인이 안되고 있습니다."; | ||
} | ||
|
||
errorMessage= URLEncoder.encode(errorMessage, StandardCharsets.UTF_8);//한글 인코딩 깨지는 문제 방지 | ||
response.sendRedirect("http://localhost:8080/auth/oauth-response/error="+errorMessage); | ||
} | ||
} |
Oops, something went wrong.