[Security] ์์ธ์ฒ๋ฆฌ
Categories: Spring
๐ ๊ฐ์ธ์ ์ธ ๊ณต๊ฐ์ผ๋ก ๊ณต๋ถ๋ฅผ ๊ธฐ๋กํ๊ณ ๋ณต์ตํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๋ธ๋ก๊ทธ์
๋๋ค.
์ ํํ์ง ์์ ์ ๋ณด๊ฐ ์์ ์ ์์ผ๋ ์ฐธ๊ณ ๋ฐ๋๋๋ค :๐ธ
[ํ๋ฆฐ ๋ด์ฉ์ ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์๋ฉด ๋ณต๋ฐ์ผ์ค๊ฑฐ์์]
Spring Security์์ ๋ฐ์ํ ์์ธ ์ฒ๋ฆฌ ํ์์ฑ
๋ก๊ทธ์ธ์ ์๋ชป ์ ๋ณด๋ฅผ ๊ธฐ์ ํ์ ๋
๋ง๋ฃ๋ ํ ํฐ์ ๋ฃ์์๋
๊ถํ์ด ๋ถ์ฌ๋์ง ์์ ๋ฆฌ์์ค์ request๋ฅผ ์ ์ก
(Admin์ด ์๋ User๊ฐ ๋ต๋ณ์ ์๋)
Spring Security์์ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ ๊ทธ ์ธ์ ์์ธ๊ฐ ๋ฐ์ํ์ ๋,
์๋ธ๋ฆฟ ์ปจํ
์ด๋์ธ Tomcat์ด HTTP ์ํ ์ฝ๋์ ๋ฐ๋ผ ๊ธฐ๋ณธ ์ค๋ฅ ํ์ด์ง๋ฅผ ๋ฐํํ๊ฒ ๋๋ค.
MVC์์ ๋ฐ์ํ๋ ์์ธ์ security์ ์์ธ๋ ๋ค๋ฅธ ๊ณณ์์ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์
๋ณ๋๋ก ๊ด๋ฆฌํด์ฃผ์ด์ผ ํ๋ค!
Spring Security์ ์์ธ๋ ํํฐ ์ฒด์ธ ๋ด์์ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ์์ ์ก์์ฃผ์ด์ผ ํ๋ค.
์์ธ์ฒ๋ฆฌ
1๏ธโฃ JwtVerificationFilter์ ์์ธ ์ฒ๋ฆฌ ๋ก์ง ์ถ๊ฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JwtVerificationFilter extends OncePerRequestFilter {
...
...
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// (1)
try {
Map<String, Object> claims = verifyJws(request);
setAuthenticationToContext(claims);
} catch (SignatureException se) {
request.setAttribute("exception", se);
} catch (ExpiredJwtException ee) {
request.setAttribute("exception", ee);
} catch (Exception e) {
request.setAttribute("exception", e);
}
filterChain.doFilter(request, response);
}
-
try -catch๋ฌธ์ด ์ถ๊ฐ
โ ํน์ ์์ธ ํ์ ์ Exception์ด catch ๋๋ฉด ํด๋น Exception์
request.setAttribute("exception", Exception ๊ฐ์ฒด)
์ ๊ฐ์ด HttpServletRequest์ ์ ํธ๋ฆฌ๋ทฐํธ(Attribute)๋ก ์ถ๊ฐ
- ๊ธฐ์กด ์์ธ ์ฒ๋ฆฌ์๋ ๋ค๋ฅด๊ฒ Exception์ catchํ ํ์ Exception์ ๋ค์ throw ํ๋ค๋ ์ง ํ๋ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๊ณ , ๋จ์ํ request.setAttribute()๋ฅผ ์ค์
- SecurityContext์ ํด๋ผ์ด์ธํธ์ ์ธ์ฆ ์ ๋ณด(Authentication ๊ฐ์ฒด)๊ฐ ์ ์ฅ๋์ง ์๊ฒ ํ๊ธฐ ์ํจ
- SecurityContext์ ํด๋ผ์ด์ธํธ์ ์ธ์ฆ ์ ๋ณด(Authentication ๊ฐ์ฒด)๊ฐ ์ ์ฅ๋์ง ์์ ์ํ๋ก ๋ค์(next) Security Filter ๋ก์ง์ ์ํํ๋ค ๋ณด๋ฉด ๊ฒฐ๊ตญ์๋ Filter ๋ด๋ถ์์
AuthenticationException
์ด ๋ฐ์ํ๊ฒ ๋๊ณ , ์ดAuthenticationException
์ ๋ฐ๋ก ์๋์์ ์ค๋ช ํ๋ AuthenticationEntryPoint๊ฐ ์ฒ๋ฆฌํ๊ฒ ๋๋ค.
2๏ธโฃ AuthenticationEntryPoint ๊ตฌํ
AuthenticationEntryPoint๋ SignatureException
, ExpiredJwtException
๋ฑ Exception ๋ฐ์์ผ๋ก ์ธํด SecurityContext์ Authentication์ด ์ ์ฅ๋์ง ์์ ๊ฒฝ์ฐ ๋ฑ AuthenticationException
์ด ๋ฐ์ํ ๋ ํธ์ถ๋๋ ํธ๋ค๋ฌ ๊ฐ์ ์ญํ ์ ํ๋ค.
-
MemberAuthenticationEntryPoint : AuthenticationEntryPoint ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ํด๋์ค
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
package com.springboot.auth.handler; ... ... @Slf4j @Component public class MemberAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { Exception exception = (Exception) request.getAttribute("exception"); ErrorResponder.sendErrorResponse(response, HttpStatus.UNAUTHORIZED); logExceptionMessage(authException, exception); } private void logExceptionMessage(AuthenticationException authException, Exception exception) { String message = exception != null ? exception.getMessage() : authException.getMessage(); log.warn("Unauthorized error happened: {}", message); } }
- commence ๋ฉ์๋ : ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๊ฐ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ค ํ ๋ ํธ์ถ
- request์ ๋ด๊ฒจ์ ธ ์๋ ์์ธ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ ์จ๋ค. (์์ JwtVerificationFilter์ doFilterInternal ๋ฉ์๋์์ ์์ธ ๋ฐ์์ request.setAttribute(โexceptionโ, se); ๋ก ์์ธ๋ฅผ ๋ด์๊ธฐ ๋๋ฌธ์ ์ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด ๋ด๊ฒจ์ ธ ์๋ค.
- ErrorResponder.sendErrorResponse(response, HttpStatus.UNAUTHORIZED) โ ์๋ฌ ์๋ต์ ์ ์ก
- logExceptionMessage ๋ฉ์๋
- authException์ ์ธ์ฆ ์์ธ, ๋๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ exception์ ์ถ๊ฐ ์์ธ
- String message = exception != null ? exception.getMessage() โ ์ถ๊ฐ ์์ธ๊ฐ ์์ผ๋ฉด ๊ทธ ๋ฉ์ธ์ง๋ฅผ ์ฌ์ฉํ๊ณ , ์ธ์ฆ ์์ธ ๋ฉ์ธ์ง๊ฐ ์๋ค๋ฉด ์ธ์ฆ ์์ธ๋ฅผ ์ฌ์ฉํ๋ค
- commence ๋ฉ์๋ : ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๊ฐ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ค ํ ๋ ํธ์ถ
-
ErrorResponder
AuthenticationException
๋ฐ์ํ๋ฉด ErrorResponse๋ฅผ ์์ฑํด์ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก1 2 3 4 5 6 7 8 9 10 11 12 13 14
package com.springboot.auth.utils; ... ... public class ErrorResponder { public static void sendErrorResponse(HttpServletResponse response, HttpStatus httpStatus) throws IOException { Gson gson = new Gson(); ErrorResponse errorResponse = ErrorResponse.of(httpStatus); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(httpStatus.value()); response.getWriter().write(gson.toJson(errorResponse,ErrorResponse.class)); }
- HttpStatus๋ฅผ ๋ฐ์ ErrorREpnose ๊ฐ์ฒด๋ฅผ ์์ฑ
- response.setContentType(MediaType.APPLICATION_JSON_VALUE); โ ์๋ต์ json type์ผ๋ก ๋ณด๋ด์ค๋ค.
- HTTP ์๋ต์ JSON ํ์์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์์ฑํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํ๋ ์ญํ ์ ํ๋ค.
3๏ธโฃ AccessDeniedHandler ๊ตฌํ
์ธ์ฆ์๋ ์ฑ๊ณตํ์ง๋ง ํด๋น ๋ฆฌ์์ค์ ๋ํ ๊ถํ์ด ์์ผ๋ฉด ํธ์ถ๋๋ ํธ๋ค๋ฌ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.springboot.auth.handler;
...
@Slf4j
@Component
public class MemberAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
ErrorResponder.sendErrorResponse(response, HttpStatus.FORBIDDEN);
log.warn("Forbidden error happend: {}", accessDeniedException.getMessage());
}
}
sendErrorResponse
๋ฉ์๋๋ HTTP ์๋ต์ ์ฝํ ์ธ ํ์ ์application/json
์ผ๋ก ์ค์ ํ๊ณ , ์ํ ์ฝ๋๋ฅผ 403์ผ๋ก ์ค์ ํ๋ฉฐ,ErrorResponse
๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์์ด๋ก ๋ณํํ์ฌ ์๋ต ๋ณธ๋ฌธ์ ์์ฑaccessDeniedException.getMessage()
๋ ์ ๊ทผ ๊ฑฐ๋ถ ์์ธ์ ์์ธ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ค.
4๏ธโฃ SecurityConfiguration์ AuthenticationEntryPoint ๋ฐ AccessDeniedHandler ์ถ๊ฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SecurityConfiguration {
...
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable()
.cors(withDefaults())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable()
.exceptionHandling()
.authenticationEntryPoint(new MemberAuthenticationEntryPoint()) // (1) ์ถ๊ฐ
.accessDeniedHandler(new MemberAccessDeniedHandler()) // (2) ์ถ๊ฐ
.and()
.apply(new CustomFilterConfigurer())
.and()
.authorizeHttpRequests(authorize -> authorize
.antMatchers(HttpMethod.POST, "/*/members").permitAll()
.antMatchers(HttpMethod.PATCH, "/*/members/**").hasRole("USER")
.antMatchers(HttpMethod.GET, "/*/members").hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/*/members/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.DELETE, "/*/members/**").hasRole("USER")
.anyRequest().permitAll()
);
return http.build();
}
...
...
...
...
}
- exceptionHandling(): ์์ธ ์ฒ๋ฆฌ ์ค์ ์ ๊ตฌ์ฑ
authenticationEntryPoint(new MemberAuthenticationEntryPoint())
: ์ธ์ฆ ์คํจ ์ ์ฒ๋ฆฌํ ํธ๋ค๋ฌ ์ค์ .accessDeniedHandler(new MemberAccessDeniedHandler())
: ๊ถํ ๋ถ์กฑ ์ ์ฒ๋ฆฌํ ํธ๋ค๋ฌ ์ค์ .
Leave a comment