-
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.
* feat: JWT Filter와 Error Handler를 추가한다. * feat: TokenService에 AccessToken을 validate하는 메소드를 추가한다. * feat: 인증 객체를 담는 dto(JwtAuthentication)를 추가한다. * feat: Jwt Filter를 Security FilterChain에 등록한다.
- Loading branch information
Showing
7 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
src/main/java/com/dnd/accompany/domain/auth/dto/jwt/JwtAuthentication.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.accompany.domain.auth.dto.jwt; | ||
|
||
import java.util.Objects; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import com.dnd.accompany.domain.auth.exception.InvalidTokenException; | ||
import com.dnd.accompany.global.common.response.ErrorCode; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class JwtAuthentication { | ||
|
||
private Long id; | ||
private String accessToken; | ||
|
||
public JwtAuthentication(Long id, String accessToken) { | ||
this.id = validateId(id); | ||
this.accessToken = validateAccessToken(accessToken); | ||
} | ||
|
||
private Long validateId(Long id) { | ||
if (Objects.isNull(id) || id <= 0L) { | ||
throw new InvalidTokenException(ErrorCode.INVALID_TOKEN); | ||
} | ||
|
||
return id; | ||
} | ||
|
||
private String validateAccessToken(String accessToken) { | ||
if (StringUtils.isEmpty(accessToken)) { | ||
throw new InvalidTokenException(ErrorCode.INVALID_TOKEN); | ||
} | ||
|
||
return accessToken; | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/main/java/com/dnd/accompany/domain/auth/dto/jwt/JwtAuthenticationToken.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,56 @@ | ||
package com.dnd.accompany.domain.auth.dto.jwt; | ||
|
||
import java.util.Collection; | ||
|
||
import org.springframework.security.authentication.AbstractAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
|
||
public class JwtAuthenticationToken extends AbstractAuthenticationToken { | ||
|
||
private final Object principal; | ||
private String credentials; | ||
|
||
public JwtAuthenticationToken(String principal, String credentials) { | ||
super(null); | ||
super.setAuthenticated(false); | ||
|
||
this.principal = principal; | ||
this.credentials = credentials; | ||
} | ||
|
||
/** | ||
* 인증이 완료될 경우 사용하는 생성자입니다. | ||
*/ | ||
public JwtAuthenticationToken(Object principal, String credentials, | ||
Collection<? extends GrantedAuthority> authorities) { | ||
super(authorities); | ||
super.setAuthenticated(true); | ||
|
||
this.principal = principal; | ||
this.credentials = credentials; | ||
} | ||
|
||
@Override | ||
public Object getPrincipal() { | ||
return principal; | ||
} | ||
|
||
@Override | ||
public String getCredentials() { | ||
return credentials; | ||
} | ||
|
||
@Override | ||
public void setAuthenticated(boolean isAuthenticated) { | ||
if (isAuthenticated) { | ||
throw new IllegalArgumentException("유효하지 않은 접근입니다."); | ||
} | ||
super.setAuthenticated(false); | ||
} | ||
|
||
@Override | ||
public void eraseCredentials() { | ||
super.eraseCredentials(); | ||
credentials = null; | ||
} | ||
} |
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
52 changes: 52 additions & 0 deletions
52
src/main/java/com/dnd/accompany/global/filter/ExceptionHandlerFilter.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,52 @@ | ||
package com.dnd.accompany.global.filter; | ||
|
||
import java.io.IOException; | ||
|
||
import org.apache.commons.lang3.CharEncoding; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import com.dnd.accompany.domain.auth.exception.TokenException; | ||
import com.dnd.accompany.global.common.response.ErrorResponse; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
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; | ||
|
||
/** | ||
* TokenException이 발생할 경우 Handling합니다. | ||
*/ | ||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class ExceptionHandlerFilter extends OncePerRequestFilter { | ||
|
||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
FilterChain filterChain) throws ServletException, IOException { | ||
try { | ||
filterChain.doFilter(request, response); | ||
} catch (TokenException e) { | ||
log.debug("[ExceptionHandler] token error message = {}", e.getMessage()); | ||
generateErrorResponse(response, e); | ||
} | ||
} | ||
|
||
private void generateErrorResponse(HttpServletResponse response, TokenException e) throws IOException { | ||
response.setStatus(e.getErrorCode().getStatus().intValue()); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding(CharEncoding.UTF_8); | ||
response.getWriter() | ||
.write(objectMapper.writeValueAsString( | ||
ErrorResponse.from(e.getErrorCode()) | ||
)); | ||
} | ||
|
||
} |
46 changes: 46 additions & 0 deletions
46
src/main/java/com/dnd/accompany/global/filter/JwtAuthenticationEntryPoint.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,46 @@ | ||
package com.dnd.accompany.global.filter; | ||
|
||
import static org.springframework.http.HttpStatus.UNAUTHORIZED; | ||
|
||
import java.io.IOException; | ||
|
||
import org.apache.commons.lang3.CharEncoding; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.stereotype.Component; | ||
|
||
import com.dnd.accompany.global.common.response.ErrorCode; | ||
import com.dnd.accompany.global.common.response.ErrorResponse; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
/** | ||
* JwtAuthenticationFilter에서 사용자 인증에 실패한 경우 발생하는 401 예외를 핸들링합니다. | ||
*/ | ||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
|
||
private static final String ERROR_LOG_MESSAGE = "[ERROR] {} : {}"; | ||
|
||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
public void commence(HttpServletRequest request, HttpServletResponse response, | ||
AuthenticationException authException) throws IOException { | ||
log.info(ERROR_LOG_MESSAGE, authException.getClass().getSimpleName(), authException.getMessage()); | ||
response.setStatus(UNAUTHORIZED.value()); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding(CharEncoding.UTF_8); | ||
response.getWriter() | ||
.write(objectMapper.writeValueAsString( | ||
ErrorResponse.from(ErrorCode.ACCESS_DENIED) | ||
)); | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
src/main/java/com/dnd/accompany/global/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,67 @@ | ||
package com.dnd.accompany.global.filter; | ||
|
||
import static org.springframework.http.HttpHeaders.AUTHORIZATION; | ||
|
||
import java.io.IOException; | ||
|
||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import com.dnd.accompany.domain.auth.dto.jwt.JwtAuthenticationToken; | ||
import com.dnd.accompany.domain.auth.service.TokenService; | ||
|
||
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; | ||
|
||
/** | ||
* JWT 인증 필터로, accessToken을 검증합니다. | ||
* 토큰이 유효할 경우 유효한 인증 객체를 SecurityContextHolder에 넣어줍니다. | ||
*/ | ||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
private static final String BEARER_TYPE = "Bearer"; | ||
private final TokenService tokenService; | ||
|
||
@Override | ||
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
FilterChain filterChain) throws ServletException, IOException { | ||
|
||
String accessToken = getAccessToken(request); | ||
|
||
if (accessToken != null) { | ||
JwtAuthenticationToken authentication = tokenService.getAuthenticationByAccessToken(accessToken); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
|
||
@Override | ||
protected boolean shouldNotFilter(HttpServletRequest request) { | ||
return request.getRequestURI().endsWith("tokens"); | ||
} | ||
|
||
private String getAccessToken(HttpServletRequest request) { | ||
String authHeaderValue = request.getHeader(AUTHORIZATION); | ||
if (authHeaderValue != null) { | ||
return extractToken(authHeaderValue); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private String extractToken(String authHeaderValue) { | ||
if (authHeaderValue.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) { | ||
return authHeaderValue.substring(BEARER_TYPE.length()).trim(); | ||
} | ||
|
||
return null; | ||
} | ||
} |