Skip to content

Commit f127658

Browse files
authored
Merge pull request #81 from init-cloud/feature/#80-security-exception
feat: handle Jwt Exception on Security filter
2 parents 0c40fab + 1362444 commit f127658

File tree

10 files changed

+252
-67
lines changed

10 files changed

+252
-67
lines changed

src/main/java/scanner/common/dto/CommonResponse.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scanner.common.dto;
22

3+
import javax.naming.AuthenticationException;
4+
35
import org.springframework.http.HttpStatus;
46
import org.springframework.http.ResponseEntity;
57
import org.springframework.lang.Nullable;
@@ -8,6 +10,7 @@
810
import lombok.Builder;
911
import lombok.Getter;
1012
import scanner.common.enums.ResponseCode;
13+
import scanner.common.exception.ApiAuthException;
1114
import scanner.common.exception.ApiException;
1215

1316
@AllArgsConstructor
@@ -33,6 +36,25 @@ public static ResponseEntity<Object> toException(ApiException e) {
3336
.build());
3437
}
3538

39+
// JWT, 인가 관련 ExceptionDto
40+
public static ResponseEntity<Object> toException(ApiAuthException e) {
41+
return ResponseEntity.status(e.getResponseCode().getHttpStatus())
42+
.body(CommonResponse.builder()
43+
.success(false)
44+
.data(null)
45+
.error(new ExceptionDto(e.getResponseCode()))
46+
.build());
47+
}
48+
49+
// JWT, 인가 관련 ExceptionDto
50+
public static ResponseEntity<Object> toException(AuthenticationException e) {
51+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
52+
.body(CommonResponse.builder()
53+
.success(false)
54+
.data(null)
55+
.error(new ExceptionDto(ResponseCode.INVALID_TOKEN)).build());
56+
}
57+
3658
public static ResponseEntity<Object> toException(Exception e) {
3759
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
3860
.body(CommonResponse.builder()

src/main/java/scanner/common/enums/ResponseCode.java

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,38 @@
1010
public enum ResponseCode {
1111

1212
/* Invalid Request */
13-
TOKEN_EXPIRED(4001, HttpStatus.UNAUTHORIZED, "Expired Token."), INVALID_TOKEN(4002, HttpStatus.UNAUTHORIZED,
14-
"Invalid Token."), INVALID_PROVIDER(4003, HttpStatus.BAD_REQUEST, "Invalid Scan Provider."), UNSUPPORTED_FORMAT(
15-
4004, HttpStatus.BAD_REQUEST, "Invalid File Format."), DATA_MISSING(4005, HttpStatus.BAD_REQUEST,
16-
"Data missing. or File missing."), INVALID_REQUEST(4007, HttpStatus.BAD_REQUEST,
17-
"Invalid Request."), INVALID_USER(4008, HttpStatus.BAD_REQUEST, "Invalid User."), INVALID_PASSWORD_LENGTH(4009,
18-
HttpStatus.BAD_REQUEST, "Password too short."), INVALID_PASSWORD_FORMAT(4010, HttpStatus.BAD_REQUEST,
19-
"Invalid Password Create rule."), EXISTED_USER(4011, HttpStatus.BAD_REQUEST,
20-
"User exist."), INVALID_AUTHORIIY_TYPE(4012, HttpStatus.BAD_REQUEST, "Invalid Authority."), INVALID_AUTHORIIY(
21-
4013, HttpStatus.UNAUTHORIZED, "Have no Authority."), INVALID_JSON_FORMAT(4014, HttpStatus.UNAUTHORIZED,
22-
"Error During Parse JSON."), NO_SCAN_RESULT(4015, HttpStatus.BAD_REQUEST, "There are no Scan results."),
13+
TOKEN_EXPIRED(4001, HttpStatus.UNAUTHORIZED, "Expired Token."),
14+
INVALID_TOKEN(4002, HttpStatus.UNAUTHORIZED, "Invalid Token."),
15+
INVALID_PROVIDER(4003, HttpStatus.BAD_REQUEST, "Invalid Scan Provider."),
16+
UNSUPPORTED_FORMAT(4004, HttpStatus.BAD_REQUEST, "Invalid File Format."),
17+
DATA_MISSING(4005, HttpStatus.BAD_REQUEST, "Data missing. or File missing."),
18+
INVALID_REQUEST(4007, HttpStatus.BAD_REQUEST, "Invalid Request."),
19+
INVALID_USER(4008, HttpStatus.BAD_REQUEST, "Invalid User."),
20+
INVALID_PASSWORD_LENGTH(4009, HttpStatus.BAD_REQUEST, "Password too short."),
21+
INVALID_PASSWORD_FORMAT(4010, HttpStatus.BAD_REQUEST, "Invalid Password Create rule."),
22+
EXISTED_USER(4011, HttpStatus.BAD_REQUEST, "User exist."),
23+
INVALID_AUTHORIIY_TYPE(4012, HttpStatus.BAD_REQUEST, "Invalid Authority."),
24+
INVALID_AUTHORIIY(4013, HttpStatus.UNAUTHORIZED, "Have no Authority."),
25+
INVALID_JSON_FORMAT(4014, HttpStatus.UNAUTHORIZED, "Error During Parse JSON."),
26+
NO_SCAN_RESULT(4015, HttpStatus.BAD_REQUEST, "There are no Scan results."),
27+
INVALID_TOKEN_FORMAT(4016, HttpStatus.UNAUTHORIZED, "Invalid Token format."),
28+
UNSUPPORTED_TOKEN(4017, HttpStatus.UNAUTHORIZED, "Unsupported Token."),
29+
INVALID_TOKEN_SIGNATURE(4018, HttpStatus.UNAUTHORIZED, "Invalid Token signature."),
30+
EMPTY_TOKEN_CLAIMS(4019, HttpStatus.UNAUTHORIZED, "Empty Token Claims."),
31+
NULL_TOKEN(4020, HttpStatus.UNAUTHORIZED, "Token is null"),
2332

2433
/* Server Error. */
25-
SERVER_BUSY(5001, HttpStatus.INTERNAL_SERVER_ERROR, "Server busy."), SCAN_ERROR(5002,
26-
HttpStatus.INTERNAL_SERVER_ERROR, "Scan Error."), SERVER_STORE_ERROR(5003, HttpStatus.INTERNAL_SERVER_ERROR,
27-
"Could not store the file."), SERVER_DECOMPRESS_ERROR(5004, HttpStatus.INTERNAL_SERVER_ERROR,
28-
"Could not decompress the file."), SERVER_CREATE_DIR_ERROR(5005, HttpStatus.INTERNAL_SERVER_ERROR,
29-
"Could not create upload directory."),
34+
SERVER_BUSY(5001, HttpStatus.INTERNAL_SERVER_ERROR, "Server busy."),
35+
SCAN_ERROR(5002, HttpStatus.INTERNAL_SERVER_ERROR, "Scan Error."),
36+
SERVER_STORE_ERROR(5003, HttpStatus.INTERNAL_SERVER_ERROR, "Could not store the file."),
37+
SERVER_DECOMPRESS_ERROR(5004, HttpStatus.INTERNAL_SERVER_ERROR, "Could not decompress the file."),
38+
SERVER_CREATE_DIR_ERROR(5005, HttpStatus.INTERNAL_SERVER_ERROR, "Could not create upload directory."),
3039

31-
SERVER_LOAD_FILE_ERROR(5006, HttpStatus.INTERNAL_SERVER_ERROR, "Could not load file."), SERVER_LOAD_VISUALIZE_ERROR(
32-
5007, HttpStatus.INTERNAL_SERVER_ERROR, "Could not load Visualize data."), SERVER_UPDATE_USER_ERROR(5008,
33-
HttpStatus.INTERNAL_SERVER_ERROR, "Could not update User."), SERVER_PASSWORD_ERROR(5009,
34-
HttpStatus.INTERNAL_SERVER_ERROR, "Could not change password."), SERVER_ERROR(5100,
35-
HttpStatus.INTERNAL_SERVER_ERROR, "Unknown error.");
40+
SERVER_LOAD_FILE_ERROR(5006, HttpStatus.INTERNAL_SERVER_ERROR, "Could not load file."),
41+
SERVER_LOAD_VISUALIZE_ERROR(5007, HttpStatus.INTERNAL_SERVER_ERROR, "Could not load Visualize data."),
42+
SERVER_UPDATE_USER_ERROR(5008, HttpStatus.INTERNAL_SERVER_ERROR, "Could not update User."),
43+
SERVER_PASSWORD_ERROR(5009, HttpStatus.INTERNAL_SERVER_ERROR, "Could not change password."),
44+
SERVER_ERROR(5100, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown error.");
3645

3746
private final int code;
3847
private final HttpStatus httpStatus;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package scanner.common.exception;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
import scanner.common.enums.ResponseCode;
6+
7+
@Getter
8+
@RequiredArgsConstructor
9+
public class ApiAuthException extends RuntimeException {
10+
private final ResponseCode responseCode;
11+
}
12+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package scanner.common.exception.handler;
2+
3+
import javax.naming.AuthenticationException;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.ExceptionHandler;
7+
import org.springframework.web.bind.annotation.ResponseBody;
8+
import org.springframework.web.bind.annotation.RestControllerAdvice;
9+
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
10+
11+
import lombok.extern.slf4j.Slf4j;
12+
import scanner.common.dto.CommonResponse;
13+
import scanner.common.exception.ApiAuthException;
14+
15+
@RestControllerAdvice(value = "authenticationExceptionAdvice")
16+
@Slf4j
17+
public class AuthenticationExceptionHandler extends ResponseEntityExceptionHandler {
18+
19+
@ExceptionHandler(value = ApiAuthException.class)
20+
@ResponseBody
21+
public ResponseEntity<Object> handleJwtException(ApiAuthException e) {
22+
log.error("handleJwtException throw JwtException : {}", e.getResponseCode().getMessage());
23+
return CommonResponse.toException(e);
24+
}
25+
26+
@ExceptionHandler(value = AuthenticationException.class)
27+
@ResponseBody
28+
public ResponseEntity<Object> handleAuthenticateException(AuthenticationException e) {
29+
log.error("handleAuthenticateException throw AuthenticateException : {}", e.getMessage());
30+
return CommonResponse.toException(e);
31+
}
32+
}

src/main/java/scanner/security/config/SecurityConfig.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1515

1616
import scanner.security.filter.JwtAuthenticationFilter;
17+
import scanner.security.filter.JwtExceptionFilter;
18+
import scanner.security.filter.JwtGlobalEntryPoint;
1719
import scanner.security.provider.JwtTokenProvider;
1820

1921
@Configuration
2022
@EnableWebSecurity
2123
@RequiredArgsConstructor
2224
public class SecurityConfig extends WebSecurityConfigurerAdapter {
2325

26+
private final JwtGlobalEntryPoint jwtGlobalEntryPoint;
27+
private final JwtExceptionFilter jwtExceptionFilter;
2428
private final JwtTokenProvider jwtTokenProvider;
2529
private final Properties properties;
2630

@@ -32,10 +36,8 @@ public AuthenticationManager authenticationManagerBean()
3236
}
3337

3438
@Override
35-
public void configure(HttpSecurity http)
36-
throws Exception {
37-
http.csrf()
38-
.disable();
39+
public void configure(HttpSecurity http) throws Exception {
40+
http.csrf().disable();
3941

4042
http.httpBasic()
4143
.disable()
@@ -48,25 +50,20 @@ public void configure(HttpSecurity http)
4850
.antMatchers("/swagger-ui/**").permitAll()
4951
.and()
5052
.authorizeRequests()
51-
.antMatchers("/api/v1/**").permitAll()
52-
.and()
53-
.authorizeRequests()
5453
.antMatchers("/api/v1").permitAll()
5554
.antMatchers("/api/v1/user/signin").permitAll()
5655
.antMatchers("/api/v1/user/signup").permitAll()
5756
.and()
58-
.authorizeRequests()
59-
.antMatchers("/api/v1/app/**").permitAll()
60-
.and()
61-
.authorizeRequests()
62-
.antMatchers("/api/v1/admin/**").hasRole("ADMIN")
57+
.authorizeRequests().antMatchers("/api/v1/admin/**").hasRole("ADMIN")
6358
.and()
6459
.authorizeRequests()
6560
.anyRequest().authenticated()
6661
.and()
67-
.addFilterBefore(
68-
new JwtAuthenticationFilter(jwtTokenProvider, properties),
69-
UsernamePasswordAuthenticationFilter.class);
62+
.exceptionHandling().authenticationEntryPoint(jwtGlobalEntryPoint)
63+
.and()
64+
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, properties),
65+
UsernamePasswordAuthenticationFilter.class)
66+
.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class);
7067
}
7168

7269
@Bean

src/main/java/scanner/security/filter/JwtAuthenticationFilter.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,41 @@
44

55
import javax.servlet.FilterChain;
66
import javax.servlet.ServletException;
7-
import javax.servlet.ServletRequest;
8-
import javax.servlet.ServletResponse;
97
import javax.servlet.http.HttpServletRequest;
8+
import javax.servlet.http.HttpServletResponse;
109

1110
import org.springframework.security.core.Authentication;
1211
import org.springframework.security.core.context.SecurityContextHolder;
13-
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
12+
import org.springframework.web.filter.OncePerRequestFilter;
1413

1514
import lombok.RequiredArgsConstructor;
15+
16+
import scanner.common.enums.ResponseCode;
17+
import scanner.common.exception.ApiAuthException;
1618
import scanner.security.config.Properties;
1719
import scanner.security.provider.JwtTokenProvider;
1820

1921
@RequiredArgsConstructor
20-
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
22+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2123

2224
private final JwtTokenProvider jwtTokenProvider;
2325
private final Properties properties;
2426

2527
@Override
26-
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws
28+
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
2729
IOException,
2830
ServletException {
2931

30-
String token = jwtTokenProvider.resolve((HttpServletRequest)request);
32+
String token = jwtTokenProvider.resolve(request);
3133

3234
if (token == null)
33-
SecurityContextHolder.getContext().setAuthentication(null);
35+
throw new ApiAuthException(ResponseCode.NULL_TOKEN);
36+
37+
if (!jwtTokenProvider.validate(token, properties.getSecret()))
38+
throw new ApiAuthException(ResponseCode.INVALID_TOKEN);
3439

35-
else if (jwtTokenProvider.validate(token, properties.getSecret())) {
36-
Authentication authentication = jwtTokenProvider.getAuthentication(token, properties.getSecret());
37-
SecurityContextHolder.getContext().setAuthentication(authentication);
38-
}
40+
Authentication authentication = jwtTokenProvider.getAuthentication(token, properties.getSecret());
41+
SecurityContextHolder.getContext().setAuthentication(authentication);
3942

4043
chain.doFilter(request, response);
4144
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package scanner.security.filter;
2+
3+
import java.io.IOException;
4+
5+
import javax.servlet.FilterChain;
6+
import javax.servlet.ServletException;
7+
import javax.servlet.http.HttpServletRequest;
8+
import javax.servlet.http.HttpServletResponse;
9+
10+
import org.springframework.stereotype.Component;
11+
import org.springframework.web.filter.OncePerRequestFilter;
12+
13+
import io.jsonwebtoken.ExpiredJwtException;
14+
import io.jsonwebtoken.MalformedJwtException;
15+
import io.jsonwebtoken.UnsupportedJwtException;
16+
import lombok.extern.slf4j.Slf4j;
17+
import scanner.common.enums.ResponseCode;
18+
19+
@Slf4j
20+
@Component
21+
public class JwtExceptionFilter extends OncePerRequestFilter {
22+
@Override
23+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
24+
FilterChain filterChain) throws ServletException, IOException {
25+
try {
26+
filterChain.doFilter(request, response);
27+
} catch (SecurityException e) {
28+
request.setAttribute("exception", ResponseCode.INVALID_TOKEN_SIGNATURE.getCode());
29+
} catch (MalformedJwtException e) {
30+
request.setAttribute("exception", ResponseCode.INVALID_TOKEN_FORMAT.getCode());
31+
} catch (ExpiredJwtException e) {
32+
request.setAttribute("exception", ResponseCode.TOKEN_EXPIRED.getCode());
33+
} catch (UnsupportedJwtException e) {
34+
request.setAttribute("exception", ResponseCode.UNSUPPORTED_TOKEN.getCode());
35+
} catch (IllegalArgumentException e) {
36+
request.setAttribute("exception", ResponseCode.EMPTY_TOKEN_CLAIMS.getCode());
37+
} catch (Exception e) {
38+
request.setAttribute("exception", ResponseCode.INVALID_TOKEN.getCode());
39+
}
40+
41+
filterChain.doFilter(request, response);
42+
}
43+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package scanner.security.filter;
2+
3+
import java.io.IOException;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
import javax.servlet.http.HttpServletRequest;
8+
import javax.servlet.http.HttpServletResponse;
9+
10+
import org.json.simple.JSONObject;
11+
import org.springframework.http.HttpStatus;
12+
import org.springframework.security.web.AuthenticationEntryPoint;
13+
import org.springframework.stereotype.Component;
14+
15+
import lombok.extern.slf4j.Slf4j;
16+
import scanner.common.enums.ResponseCode;
17+
18+
@Slf4j
19+
@Component("jwtGlobalEntryPoint")
20+
public class JwtGlobalEntryPoint implements AuthenticationEntryPoint {
21+
22+
@Override
23+
public void commence(HttpServletRequest request, HttpServletResponse response,
24+
org.springframework.security.core.AuthenticationException authException) throws IOException {
25+
Integer exception = (Integer)request.getAttribute("exception");
26+
27+
if (exception == null) {
28+
setResponse(response, ResponseCode.INVALID_REQUEST);
29+
} else if (exception.equals(ResponseCode.EMPTY_TOKEN_CLAIMS.getCode())) {
30+
setResponse(response, ResponseCode.EMPTY_TOKEN_CLAIMS);
31+
} else if (exception.equals(ResponseCode.INVALID_TOKEN_SIGNATURE.getCode())) {
32+
setResponse(response, ResponseCode.INVALID_TOKEN_SIGNATURE);
33+
} else if (exception.equals(ResponseCode.INVALID_TOKEN.getCode())) {
34+
setResponse(response, ResponseCode.INVALID_TOKEN);
35+
} else if (exception.equals(ResponseCode.TOKEN_EXPIRED.getCode())) {
36+
setResponse(response, ResponseCode.TOKEN_EXPIRED);
37+
} else if (exception.equals(ResponseCode.UNSUPPORTED_TOKEN.getCode())) {
38+
setResponse(response, ResponseCode.UNSUPPORTED_TOKEN);
39+
} else if (exception.equals(ResponseCode.INVALID_TOKEN_FORMAT.getCode())) {
40+
setResponse(response, ResponseCode.INVALID_TOKEN_FORMAT);
41+
}
42+
}
43+
44+
private void setResponse(HttpServletResponse response, ResponseCode code) throws IOException {
45+
response.setStatus(HttpStatus.UNAUTHORIZED.value());
46+
response.setContentType("application/json");
47+
48+
Map<String, Object> errorMap = new HashMap<>();
49+
errorMap.put("code", code.getCode());
50+
errorMap.put("message", code.getMessage());
51+
52+
Map<String, Object> responseMap = new HashMap<>();
53+
responseMap.put("success", false);
54+
responseMap.put("data", null);
55+
responseMap.put("error", errorMap);
56+
57+
JSONObject responseJson = new JSONObject(responseMap);
58+
59+
response.getWriter().print(responseJson);
60+
}
61+
}

0 commit comments

Comments
 (0)