Skip to content

Commit

Permalink
fead: Custom Exception Handler 설정
Browse files Browse the repository at this point in the history
  • Loading branch information
LJW22222 committed Oct 1, 2024
1 parent 2faf2e9 commit 26e3323
Show file tree
Hide file tree
Showing 18 changed files with 183 additions and 142 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.nbe2.api.global.config;

import java.io.IOException;

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.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import com.nbe2.api.global.exception.NoPermissionException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

private final HandlerExceptionResolver resolver;

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

@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
resolver.resolveException(request, response, null, NoPermissionException.EXCEPTION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,29 @@

import com.nbe2.api.global.exception.JwtNotFountException;
import com.nbe2.api.global.jwt.JwtProvider;
import com.nbe2.api.global.jwt.JwtValidator;
import com.nbe2.domain.auth.*;

@RequiredArgsConstructor
@Component
public class CustomSecurityFilter extends OncePerRequestFilter {

private final JwtProvider jwtProvider;
private final JwtValidator jwtValidator;

@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwtToken = reversToken(request);

// AccessToken(JWT) 유효한지 검사
// 유효하지 않으면 Refresh Token을 이용해 새 AccessToken 발급
if (jwtToken != null && jwtValidator.checkJwt(jwtToken)) {
try {
String jwtToken = reversToken(request);
// AccessToken(JWT) 유효한지 검사
// 유효하지 않으면 Refresh Token을 이용해 새 AccessToken 발급
System.out.println("전부다 유효합니다.");
UserPrincipal userPrincipal = jwtProvider.getUserPrincipal(jwtToken);
UserPrincipal tokenUserPrincipal = jwtProvider.getTokenUserPrincipal(jwtToken);
List<GrantedAuthority> grantedAuthorities =
convertorGrantedAuthority(String.valueOf(userPrincipal.role()));
setSecurityContextHolder(userPrincipal.userId(), grantedAuthorities);
convertorGrantedAuthority(String.valueOf(tokenUserPrincipal.role()));
setSecurityContextHolder(tokenUserPrincipal.userId(), grantedAuthorities);
} catch (Exception e) {
request.setAttribute("exception", e);
}

filterChain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.nbe2.api.global.config;

import java.io.IOException;

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 com.nbe2.api.global.exception.JwtUnkownException;

@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 {
if (isExceptionInSecurityFilter(request)) {
resolver.resolveException(
request, response, null, (Exception) request.getAttribute("exception"));
return;
}
resolver.resolveException(request, response, null, JwtUnkownException.EXCEPTION);
}

private boolean isExceptionInSecurityFilter(HttpServletRequest request) {
return request.getAttribute("exception") != null;
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
package com.nbe2.api.global.config;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import lombok.RequiredArgsConstructor;

import com.nbe2.api.global.jwt.JwtProvider;

@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

private final CustomSecurityFilter customSecurityFilter;
private final JwtProvider jwtProvider;

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;

@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
Expand All @@ -44,7 +39,9 @@ SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
sessionManagement.sessionCreationPolicy(
SessionCreationPolicy.STATELESS))
// 필터 추가
.addFilterBefore(customSecurityFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(
new CustomSecurityFilter(jwtProvider),
UsernamePasswordAuthenticationFilter.class)
// 접근 제어 설정
.authorizeHttpRequests(
authorizationManagerRequestMatcherRegistry ->
Expand Down Expand Up @@ -81,35 +78,13 @@ SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
// 그 외 회원 인증 필요
.anyRequest()
.authenticated());

httpSecurity.exceptionHandling(
httpSecurityExceptionHandlingConfigurer ->
httpSecurityExceptionHandlingConfigurer
// 토큰
.authenticationEntryPoint(
new AuthenticationEntryPoint() {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
"UnAuthorized");
}
})
.accessDeniedHandler(
new AccessDeniedHandler() {
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
response.sendError(
HttpServletResponse.SC_FORBIDDEN);
}
}));
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler));

return httpSecurity.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
@RequiredArgsConstructor
public enum JwtErrorCode implements BaseErrorCode {
UNSUPPORTED_TOKEN(BAD_REQUEST, "TOKEN_400", "지원 하지 않은 토큰"),
EXPIRED_TOKEN(UNAUTHORIZED, "TOKEN_401", "만료된 토큰"),
TOKEN_EXPIRED(UNAUTHORIZED, "TOKEN_401", "만료된 토큰"),
TOKEN_NOT_VALIDATE(UNAUTHORIZED, "TOKEN_401", "유효하지 않은 토큰"),
TOKEN_BAD_SIGNATURE(UNAUTHORIZED, "TOKEN_401", "서명 불일치"),
REFRESH_TOKEN_EXPIRED(UNAUTHORIZED, "TOKEN_401", "만료된 리프레쉬 토큰"),
ACCESS_TOKEN_NOT_FOUNT(NOT_FOUND, "TOKEN_404", "ACCESS TOKEN이 없음");
ACCESS_TOKEN_NOT_FOUNT(NOT_FOUND, "TOKEN_404", "ACCESS TOKEN이 없음"),
UNKNOWN_EXCEPTION(INTERNAL_SERVER, "TOKEN_900", "알 수 없는 오류 발생");

private final Integer status;
private final String errorCode;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nbe2.api.global.exception;

import com.nbe2.common.exception.WebException;

public class JwtExpriedException extends WebException {

public static final WebException EXCEPTION = new JwtExpriedException();

private JwtExpriedException() {
super(JwtErrorCode.TOKEN_EXPIRED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nbe2.api.global.exception;

import com.nbe2.common.exception.WebException;

public class JwtUnkownException extends WebException {

public static final WebException EXCEPTION = new JwtUnkownException();

private JwtUnkownException() {
super(JwtErrorCode.UNKNOWN_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

@Component
public class JwtGenerator implements TokenGenerator {
private static final long ACCESS_EXPIRATION_TIME = 80; // -> 약 10분의 토큰 유효 기간
private static final long ACCESS_EXPIRATION_TIME = 860000000; // -> 약 10분의 토큰 유효 기간
private static final long REFRESH_EXPIRATION_TIME = 860000000; // -> 약 하루의 토큰 유효 기간

private static String SECRET_KEY;
Expand All @@ -33,9 +33,8 @@ private static Key getKey() {
return new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}

@Override
// Jwt 생성
public Tokens generateToken(UserPrincipal principal) {
public Tokens generate(UserPrincipal principal) {
return Tokens.builder()
.accessToken(generatorAccessToken(principal))
.refreshToken(generatorRefreshToken(principal))
Expand All @@ -45,7 +44,7 @@ public Tokens generateToken(UserPrincipal principal) {
private static String generatorAccessToken(UserPrincipal principal) {
return Jwts.builder()
.setHeader(setHeader("ACCESS"))
.setClaims(setAccessClaims(principal))
.setClaims(setClaims(principal))
.setSubject(String.valueOf(principal.userId()))
.setIssuedAt(getNowDate())
.setExpiration(new Date(getNowDate().getTime() + ACCESS_EXPIRATION_TIME))
Expand All @@ -56,7 +55,7 @@ private static String generatorAccessToken(UserPrincipal principal) {
private static String generatorRefreshToken(UserPrincipal principal) {
return Jwts.builder()
.setHeader(setHeader("REFRESH"))
.setClaims(setRefreshClaims(principal))
.setClaims(setClaims(principal))
.setSubject(String.valueOf(principal.userId()))
.setIssuedAt(getNowDate())
.setExpiration(new Date(getNowDate().getTime() + REFRESH_EXPIRATION_TIME))
Expand All @@ -72,13 +71,13 @@ private static Map<String, Object> setHeader(String type) {
return header;
}

private static Map<String, Object> setAccessClaims(UserPrincipal principal) {
Map<String, Object> claims = new HashMap<>();
claims.put("UserPrincpal", principal);
return claims;
}
// private static Map<String, Object> setAccessClaims(UserPrincipal principal) {
// Map<String, Object> claims = new HashMap<>();
// claims.put("UserPrincpal", principal);
// return claims;
// }

private static Map<String, Object> setRefreshClaims(UserPrincipal principal) {
private static Map<String, Object> setClaims(UserPrincipal principal) {
Map<String, Object> claims = new HashMap<>();
claims.put("ROLE", principal.role().name());
return claims;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.nbe2.api.global.exception.JwtExpriedException;
import com.nbe2.api.global.exception.JwtUnsupportedException;
import com.nbe2.domain.auth.TokenProvider;
import com.nbe2.domain.auth.UserPrincipal;
import com.nbe2.domain.user.UserRole;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.*;

@Component
public class JwtProvider implements TokenProvider {
Expand All @@ -28,20 +29,35 @@ private static Key getKey() {
return new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}

public UserPrincipal getUserPrincipal(String token) {
return (UserPrincipal) getTokenClaims(getKey(), token).get("UserPrincpal");
// public UserPrincipal getAccessTokenUserPrincipal(String token) {
// long userId = Long.parseLong(getUserId(token));
// String roleName = getUserRole(token);
// return (UserPrincipal) getTokenClaims(getKey(), token).get("UserPrincpal");
// }

public UserPrincipal getTokenUserPrincipal(String token) {
String roleName = getUserRole(token);
long userId = Long.parseLong(getUserId(token));
return UserPrincipal.of(userId, UserRole.valueOf(roleName));
}

public String getUserId(String token) {
private String getUserId(String token) {
return getTokenSubject(getKey(), token);
}

public String getUserRole(String token) {
private String getUserRole(String token) {
return (String) getTokenClaims(getKey(), token).get("ROLE");
}

private static Claims getTokenClaims(Key key, String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
try {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e) {
throw JwtExpriedException.EXCEPTION;
} catch (UnsupportedJwtException e) {
// logger.info("지원되지 않는 JWT 토큰입니다. ");
throw JwtUnsupportedException.EXCEPTION;
}
}

private static String getTokenSubject(Key key, String token) {
Expand Down
Loading

0 comments on commit 26e3323

Please sign in to comment.