Skip to content

Commit

Permalink
Merge pull request #150 from eod940/053-REFACTOR-TOKEN-SERVICE
Browse files Browse the repository at this point in the history
refactor: [053-REFACTOR-TOKEN-SERVICE] 토큰서비스 보안관련 버그 수정 및 리팩토링
  • Loading branch information
eod940 committed Mar 28, 2024
2 parents d182df0 + 1fc4ea7 commit f8d2e7d
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
throw new ClassCastException(
principal + " is not assignable to " + parameter.getParameterType());
}
throw new CustomAuthException(ErrorCode.NO_PRINCIPAL); // [100] 401 unauthorized
throw new CustomAuthException(ErrorCode.INVALID_USER_DETAILS); // [200] 401 unauthorized
}
return principal;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.valuewith.tweaver.commons.security;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
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;

@Component
@Slf4j
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

private final HandlerExceptionResolver resolver;

public JwtAuthEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
log.error("resolver - 인증 에러 발생");
try {
resolver.resolveException(request, response, null,
(Exception) request.getAttribute("exception"));
} catch (Exception e) {
response.setStatus(888);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.valuewith.tweaver.commons.PrincipalDetails;
import com.valuewith.tweaver.commons.security.service.TokenService;
import com.valuewith.tweaver.constants.ErrorCode;
import com.valuewith.tweaver.exception.CustomAuthException;
import com.valuewith.tweaver.member.entity.Member;
import com.valuewith.tweaver.member.repository.MemberRepository;
import java.io.IOException;
Expand All @@ -10,17 +12,16 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

Expand All @@ -35,51 +36,37 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = resolveTokenFromRequest(request);

// 토큰이 올바르다면 인증 정보를 Context에 저장하는 과정이 추가
if (request.getRequestURI().equals("/auth/signin")
&& StringUtils.hasText(token)
&& tokenService.isValidToken(token)) {
filterChain.doFilter(request, response);
return;
}

// 리프레시 토큰이 유효한지 검증합니다.
String refreshToken = tokenService
.parseRefreshToken(request)
.filter(tokenService::isValidToken)
.orElse(null);
try {
// 리프레시 토큰이 유효한지 검증합니다.
String refreshToken = tokenService
.parseRefreshToken(request)
.filter(tokenService::isValidToken)
.orElse(null);

/*
헤더에 리프레시 토큰이 존재 -> 사용자 AccessToken 만료
받은 리프레시 토큰을 DB와 비교 후 재발급 진행
*/
if (refreshToken != null) {
reissueAccessTokenAfterRefreshToken(response, refreshToken);
return;
}
if (refreshToken != null) {
reissueAccessTokenAfterRefreshToken(response, refreshToken);
return;
}

/*
리프레시 토큰이 없거나 유효하지 않다면 사용자 AccessToken 검사가 필요하다.
AccessToken이 없거나 유효하지 않다면 -> 403
AccessToken이 있고 유효하다면 -> 200
*/
authenticateAccessToken(request, response, filterChain);
}

/**
* 클라이언트에서는 로그인 이후 request headers에 Authorization: Bearer {token} 값이 추가되어야 합니다.
*/
private String resolveTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(TOKEN_HEADER);

// 토큰에서 "Bearer "를 없엔 뒤 토큰 값만 추출
if (!ObjectUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)) {
return token.substring(TOKEN_PREFIX.length());
authenticateAccessToken(request);
} catch (Exception e) {
request.setAttribute("exception", e);
}
filterChain.doFilter(request, response);
}

return null;
private void checkTokenValidity(String token) {
tokenService.checkTokenValidity(token);
}

public void reissueAccessTokenAfterRefreshToken(HttpServletResponse response,
Expand All @@ -95,14 +82,21 @@ public void reissueAccessTokenAfterRefreshToken(HttpServletResponse response,
);
}

public void authenticateAccessToken(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = tokenService.parseAccessToken(request);
if (token != null && tokenService.isValidToken(token)) {
String email = tokenService.getMemberEmail(token);
memberRepository.findByEmail(email).ifPresent(this::saveAuthentication);
public void authenticateAccessToken(HttpServletRequest request)
throws ServletException, IOException {
String trimmedAccessToken = getAccessTokenFromRequest(request);
checkTokenValidity(trimmedAccessToken);
String email = tokenService.getMemberEmailForFilter(trimmedAccessToken);
memberRepository.findByEmail(email).ifPresent(this::saveAuthentication);
}

public String getAccessTokenFromRequest(HttpServletRequest request) {
String trimmedToken = tokenService.parseAccessToken(request);
if (trimmedToken == null) {
log.error("authenticateAccessToken: [Nothing] 토큰 없음");
throw new CustomAuthException(ErrorCode.NO_PRINCIPAL); // [100]
}
filterChain.doFilter(request, response);
return trimmedToken;
}

private String reissueRefreshToken(Member member) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.valuewith.tweaver.commons.security;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

@Component
public class JwtDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {

// TODO: (24년 03월 25일) 현시점 인가 관련 세팅이 없으므로 999만 발생
response.sendError(999, "접근 불가능 에러");
}
}
Loading

0 comments on commit f8d2e7d

Please sign in to comment.