-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 인증.인가 필터 및 소셜 로그인 연동 #36
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
Changes from all commits
766511b
6e6f6ee
775093a
c1adbeb
681c5d8
e59c1a8
e78d5b5
f768774
291f7c9
42d3d95
2b676af
8f9f88c
2c49059
b23a6fa
c776f43
3a7a505
4a16efb
f3e16cf
b8f1e46
caa7452
2da2229
54ab951
800bdb0
f0003a9
8510267
ecdc972
7b1ae2a
31b3634
ddf9647
943e45a
40c1e2a
9187296
781cc4d
ad4eab1
12c17d0
adcd74a
ca20c73
59b7bde
0402c64
dba86f4
22f3edf
5b9ea9c
d01a344
df2b46c
1af9a7b
11ef7ac
b8c58f5
c2b00ea
9271fd4
50a5f32
873144a
1c63005
95eb446
533775d
458a06d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package konkuk.thip.common.exception; | ||
|
|
||
| import konkuk.thip.common.exception.code.ErrorCode; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class AuthException extends RuntimeException { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM |
||
| private final ErrorCode errorCode; | ||
|
|
||
| public AuthException(ErrorCode errorCode) { | ||
| this.errorCode = errorCode; | ||
| } | ||
|
|
||
| public AuthException(ErrorCode errorCode, Exception e) { | ||
| super(e); | ||
| this.errorCode = errorCode; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package konkuk.thip.common.security.annotation; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target(ElementType.PARAMETER) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface Oauth2Id { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package konkuk.thip.common.security.annotation; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target(ElementType.PARAMETER) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface UserId { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package konkuk.thip.common.security.argument_resolver; | ||
|
|
||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import konkuk.thip.common.exception.AuthException; | ||
| import konkuk.thip.common.security.annotation.Oauth2Id; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.core.MethodParameter; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.bind.support.WebDataBinderFactory; | ||
| import org.springframework.web.context.request.NativeWebRequest; | ||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
| import org.springframework.web.method.support.ModelAndViewContainer; | ||
|
|
||
| import static konkuk.thip.common.exception.code.ErrorCode.AUTH_TOKEN_NOT_FOUND; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class Oauth2IdArgumentResolver implements HandlerMethodArgumentResolver { | ||
|
|
||
| @Override | ||
| public boolean supportsParameter(MethodParameter parameter) { | ||
| return parameter.hasParameterAnnotation(Oauth2Id.class) | ||
| && parameter.getParameterType().equals(String.class); | ||
| } | ||
|
|
||
| @Override | ||
| public String resolveArgument(MethodParameter parameter, | ||
| ModelAndViewContainer mavContainer, | ||
| NativeWebRequest webRequest, | ||
| WebDataBinderFactory binderFactory) { | ||
|
|
||
| Object oauth2Id = ((HttpServletRequest) webRequest.getNativeRequest()).getAttribute("oauth2Id"); | ||
| if (oauth2Id == null) { | ||
| throw new AuthException(AUTH_TOKEN_NOT_FOUND); | ||
| } | ||
| return (String) oauth2Id; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package konkuk.thip.common.security.argument_resolver; | ||
|
|
||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import konkuk.thip.common.exception.AuthException; | ||
| import konkuk.thip.common.security.annotation.UserId; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.core.MethodParameter; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.bind.support.WebDataBinderFactory; | ||
| import org.springframework.web.context.request.NativeWebRequest; | ||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
| import org.springframework.web.method.support.ModelAndViewContainer; | ||
|
|
||
| import static konkuk.thip.common.exception.code.ErrorCode.AUTH_TOKEN_NOT_FOUND; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { | ||
|
|
||
| @Override | ||
| public boolean supportsParameter(MethodParameter parameter) { | ||
| return parameter.hasParameterAnnotation(UserId.class) | ||
| && parameter.getParameterType().equals(Long.class); | ||
| } | ||
|
|
||
| @Override | ||
| public Long resolveArgument(MethodParameter parameter, | ||
| ModelAndViewContainer mavContainer, | ||
| NativeWebRequest webRequest, | ||
| WebDataBinderFactory binderFactory) { | ||
|
|
||
| Object userId = ((HttpServletRequest) webRequest.getNativeRequest()).getAttribute("userId"); | ||
| if (userId == null) { | ||
| throw new AuthException(AUTH_TOKEN_NOT_FOUND); | ||
| } | ||
| return (Long) userId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package konkuk.thip.common.security.constant; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public enum AuthParameters { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와우 이거 상수로싹다 정리하신거 너뮤좋네요 💯 |
||
| JWT_HEADER_KEY("Authorization"), | ||
| JWT_PREFIX("Bearer "), | ||
| KAKAO("kakao"), | ||
| GOOGLE("google"), | ||
| KAKAO_PROVIDER_ID_KEY("id"), | ||
| GOOGLE_PROVIDER_ID_KEY("sub"), | ||
| JWT_ACCESS_TOKEN_KEY("userId"), | ||
| JWT_SIGNUP_TOKEN_KEY("oauth2Id"), | ||
| ; | ||
|
Comment on lines
+6
to
+15
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM! authorization 관련 string value 들을 enum으로 모아두니 관리하기 편할 것 같습니다! |
||
|
|
||
| private final String value; | ||
|
|
||
| AuthParameters(String value) { | ||
| this.value = value; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package konkuk.thip.common.security.filter; | ||
|
|
||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.beans.factory.annotation.Qualifier; | ||
| import org.springframework.security.core.AuthenticationException; | ||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.servlet.HandlerExceptionResolver; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| @Component | ||
| public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
| private final HandlerExceptionResolver resolver; | ||
|
|
||
| public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver){ | ||
| this.resolver = resolver; | ||
| } | ||
|
|
||
| @Override | ||
| public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { | ||
| Exception e = (Exception) request.getAttribute("exception"); | ||
| if(e == null){ | ||
| e = authException; | ||
| } | ||
| resolver.resolveException(request, response, null, e); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,79 @@ | ||||||||||||||||||||||||||||||||||||||||
| package konkuk.thip.common.security.filter; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.FilterChain; | ||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.ServletException; | ||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.AuthException; | ||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.security.oauth2.CustomOAuth2User; | ||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.security.oauth2.LoginUser; | ||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.security.util.JwtUtil; | ||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.core.Authentication; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import static konkuk.thip.common.exception.code.ErrorCode.*; | ||||||||||||||||||||||||||||||||||||||||
| import static konkuk.thip.common.security.constant.AuthParameters.*; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||
| public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private final JwtUtil jwtUtil; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||
| protected void doFilterInternal(HttpServletRequest request, | ||||||||||||||||||||||||||||||||||||||||
| HttpServletResponse response, | ||||||||||||||||||||||||||||||||||||||||
| FilterChain filterChain) throws ServletException, IOException { | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| String token = extractToken(request); | ||||||||||||||||||||||||||||||||||||||||
| if (token == null) { | ||||||||||||||||||||||||||||||||||||||||
| throw new AuthException(AUTH_TOKEN_NOT_FOUND); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (!jwtUtil.validateToken(token)) { | ||||||||||||||||||||||||||||||||||||||||
| throw new AuthException(AUTH_INVALID_TOKEN); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (jwtUtil.isExpired(token)) { | ||||||||||||||||||||||||||||||||||||||||
| throw new AuthException(AUTH_EXPIRED_TOKEN); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| LoginUser loginUser = jwtUtil.getLoginUser(token); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (loginUser.userId() != null) { | ||||||||||||||||||||||||||||||||||||||||
| request.setAttribute(JWT_ACCESS_TOKEN_KEY.getValue(), loginUser.userId()); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||
| request.setAttribute(JWT_SIGNUP_TOKEN_KEY.getValue(), loginUser.oauth2Id()); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| CustomOAuth2User customOAuth2User = new CustomOAuth2User(loginUser); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities()); | ||||||||||||||||||||||||||||||||||||||||
| SecurityContextHolder.getContext().setAuthentication(authToken); | ||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||
| log.error("JWT 필터에서 오류 발생: {}", e.getMessage()); | ||||||||||||||||||||||||||||||||||||||||
| request.setAttribute("exception", e); | ||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||
| filterChain.doFilter(request, response); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외 처리 시 보안 정보 노출을 주의해야 합니다. 로그에 전체 예외 메시지를 출력하면 민감한 정보가 노출될 수 있습니다. } catch (Exception e) {
- log.error("JWT 필터에서 오류 발생: {}", e.getMessage());
+ log.error("JWT 필터에서 오류 발생", e);
request.setAttribute("exception", e);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private String extractToken(HttpServletRequest request) { | ||||||||||||||||||||||||||||||||||||||||
| String authorization = request.getHeader(JWT_HEADER_KEY.getValue()); | ||||||||||||||||||||||||||||||||||||||||
| if (authorization != null && authorization.startsWith(JWT_PREFIX.getValue())) { | ||||||||||||||||||||||||||||||||||||||||
| return authorization.split(" ")[1]; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| log.info("토큰이 없습니다."); | ||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 토큰 추출 로직을 개선할 수 있습니다. 현재 구현에서 잠재적인 private String extractToken(HttpServletRequest request) {
String authorization = request.getHeader(JWT_HEADER_KEY.getValue());
- if (authorization != null && authorization.startsWith(JWT_PREFIX.getValue())) {
- return authorization.split(" ")[1];
+ if (authorization != null && authorization.startsWith(JWT_PREFIX.getValue())) {
+ String[] parts = authorization.split(" ");
+ if (parts.length == 2) {
+ return parts[1];
+ }
}
log.info("토큰이 없습니다.");
return null;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.