[Security] JWT ๊ตฌํ (4) - JWT ํํฐ & JWT ํธ๋ค๋ฌ ๊ตฌํ
by rlaehddnd0422์ด๋ฒ์๋ ์ธ์ฆ ์์ ์ ์๋์ผ๋ก ํธ์ถ๋ ์ ์๋ JWT ํํฐ๋ฅผ ๊ตฌํํด๋ณด๊ณ , JWT ํํฐ๋ฅผ ํต๊ณผํ์ง ๋ชปํ ๊ฒฝ์ฐ (๊ถํ ๋ฏธ๋ฌ, ํ ํฐ Validate ์คํจ) ์ฒ๋ฆฌํ๋ JWT ํธ๋ค๋ฌ๋ฅผ ๊ตฌํํด๋ด ์๋ค.
๐ฉJwtFilter ํํฐ ๊ตฌํ extends GenericFilterBean
@Slf4j
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private TokenProvider tokenProvider;
public JwtFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
// ํ ํฐ์ ์ธ์ฆ์ ๋ณด SecurityContext์ ์ ์ฅ
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Security Context์ '{}' ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ์ต๋๋ค, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("์ ํจํ JWT ํ ํฐ์ด ์์ต๋๋ค, uri: {}", requestURI);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
- GenericFilterBean์ Spring Security์ ํจ๊ป ๋ง์ด ์ฌ์ฉ๋๋ฉฐ, ์ฌ์ฉ์ ์ธ์ฆ, ๊ถํ ๋ถ์ฌ, ๋ก๊น ๋ฑ๊ณผ ๊ฐ์ ๊ณตํต๋ ์์ ์ ์ฒ๋ฆฌํ๋ ๋ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.
- ๋ฐ๋ผ์ UsernamePasswordAuthenticationFilter๋ ์ฃผ๋ก Spring Security์์ ์ฌ์ฉ์ ์ธ์ฆ์ ํนํ๋ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ฐ๋ฉด, GenericFilterBean์ Spring Framework์์ ์ผ๋ฐ์ ์ธ ํํฐ๋ง ์์ ์ ์ํํ๊ณ ์ ํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. GenericFilterBean์ ๋ค์ํ ํํฐ๋ง ์์ ์ ๊ตฌํํ ์ ์๋ ์ ์ฐ์ฑ์ ์ ๊ณตํ๋ฉฐ, Spring์ ๋ค๋ฅธ ๊ธฐ๋ฅ๊ณผ ํตํฉํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
JwtFilter - doFilter()
// ํ ํฐ์ ์ธ์ฆ์ ๋ณด SecurityContext์ ์ ์ฅ
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest); // 0
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt))
Authentication authentication = tokenProvider.getAuthentication(jwt); // 1
SecurityContextHolder.getContext().setAuthentication(authentication); // 2
log.debug("Security Context์ '{}' ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ์ต๋๋ค, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("์ ํจํ JWT ํ ํฐ์ด ์์ต๋๋ค, uri: {}", requestURI);
}
filterChain.doFilter(servletRequest, servletResponse); // 3
}
resolveToken method
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
0) resolveToken๋ฉ์๋๋ฅผ ํตํด request๋ก๋ถํฐ Authorizaiton ํค๋ ์ ๋ณด๋ฅผ ๊บผ๋ด์ ๋ฒ ๋ฆฌ์ด๋ฅผ ํด์ ํ๊ณ ๋ฆฌํดํฉ๋๋ค.
1) ์์ฒญ ์ ๋ณด์ ํ ํฐ์ด ์๊ณ ์ด ์ ํฌ์คํ ์์ ์์ฑํ TokenProvider๋ก ํ ํฐ ๊ฒ์ฆ์ ํต๊ณผํ๋ฉด ํ ํฐ์ผ๋ก๋ถํฐ Authentication ๊ฐ์ฒด๋ฅผ ๋ง๋ญ๋๋ค.
2) Authentication ๊ฐ์ฒด๋ฅผ Security Context์ ๋ด์ต๋๋ค. ( for Authentication & Authorization )
3) ๋ค์ ํํฐ๋ก ๋์ด๊ฐ๋๋ค.
๐ฉ์ด ํํฐ๋ ์ด๋ ๊ฒ ๋์ํฉ๋๋ค!
์ธ์ฆ or ์ธ๊ฐ๊ฐ ํ์ํ ์์ฒญ
โก๏ธ JwtFilter์์ ์์ฒญ ์ธํฐ์ ํธ
โก๏ธ ์์ฒญ ํค๋ ์ ๋ณด์์ ํ ํฐ์ ๊บผ๋ด์ TokenProvider๋ก ํ ํฐ ๊ฒ์ฆ
โก๏ธ ๊ฒ์ฆ ํต๊ณผ ์ Authentication ๊ฐ์ฒด ์์ฑ
โก๏ธ Session์์ญ์ SecurityContext์ Authentication ๊ฐ์ฒด๋ฅผ ๋ด๊ณ ํํฐ ํต๊ณผ
์ด์ , JWT ํํฐ ๊ตฌํ์ ์ด์ด ์ธ์ฆ, ์ธ๊ฐ์ ์คํจํ์ฌ ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์ด ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ํธ๋ค๋ฌ๋ฅผ ๋ง๋ค์ด ๋ด ์๋ค.
๐ฉ AuthenticationEntryPoint ๊ตฌํ : ์ธ์ฆ ์คํจ ์ฒ๋ฆฌ ํธ๋ค๋ฌ
AuthenticationEntryPoint๋ ์ธ์ฆ ์ฒ๋ฆฌ ๊ณผ์ ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ ์์ธ๋ฅผ ์ฒ๋ฆฌํด์ฃผ๋ ํธ๋ค๋ฌ์ ๋๋ค.
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// ์ ํจํ ์๊ฒฉ์ฆ๋ช
์ ์ ๊ณตํ์ง ์๊ณ ์ ๊ทผํ๋ ค ํ ๋ 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
- ์ธ์ฆ์ ์คํจํ์์ ๊ฒฝ์ฐ 401 ์๋ต์ ๋ฆฌํดํ ์ ์๋๋ก EntryPoint๋ฅผ ๊ตฌํํฉ๋๋ค.
๐ฉ AccessDeniedHandler ๊ตฌํ : ๊ถํ ๋ฏธ๋ฌ ์ฒ๋ฆฌ ํธ๋ค๋ฌ
AccessDeniedHandler๋ ์ธ๊ฐ ์ฒ๋ฆฌ ๊ณผ์ ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌํด์ฃผ๋ ํธ๋ค๋ฌ์ ๋๋ค.
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//ํ์ํ ๊ถํ์ด ์์ด ์ ๊ทผํ๋ ค ํ ๋ 403
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
- ํ์ํ ๊ถํ์ด ์๋ ๊ฒฝ์ฐ 403 ์๋ต์ ๋ฐํํ๋ ํธ๋ค๋ฌ๋ฅผ ๊ตฌํํฉ๋๋ค.
๐ฉJwtFilter & Handler ๋ฑ๋ก
JwtFilter โก๏ธ JwtSecurityConfig ๋ฑ๋ก
JwtSecurityConfig ์์ฑ ๋ฐ ๋ฑ๋ก
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private TokenProvider tokenProvider;
public JwtSecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity http) {
http.addFilterBefore(
new JwtFilter(tokenProvider),
UsernamePasswordAuthenticationFilter.class
);
}
}
- ๊ธฐ์กด์ ๋ก๊ทธ์ธ ์์ ์์ ํธ์ถ๋๋ UsernamePasswordAuthenticationFilter๋ฅผ JwtFilter๋ก Override ํด์ค๋๋ค.
- addFilterBefore() : ์คํ๋ง ์ํ๋ฆฌํฐ ํํฐ๋ง์ ๋ฑ๋กํด์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ addFilterBefore์ ๋ฑ๋กํด์ค๋๋ค.
JwtSecurityConfig & AuthenticationEndPoint & AccessDeniedHandler โก๏ธ SecurityConfig ๋ฑ๋ก
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http. // ํ ํฐ ์ฌ์ฉํ๋ ๋ฐฉ์์ด๋ฏ๋ก csrf disable
csrf().disable()
.exceptionHandling() // ์์ธ ์ฒ๋ฆฌ ํธ๋ค๋ฌ ๋ฑ๋ก
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.headers()
.frameOptions()
.sameOrigin()
// ์ธ์
์ฌ์ฉํ์ง ์๊ธฐ ์ํด stateless๋ก ์ค์
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/hello").permitAll()
.antMatchers("/api/authenticate").permitAll() // ๋ก๊ทธ์ธ
.antMatchers("/api/signup").permitAll() // ํ์๊ฐ์
.anyRequest().authenticated()
.and()
.apply(new JwtSecurityConfig(tokenProvider)); // JWT ํํฐ ๋ฑ๋ก
return http.build();
}
}
<์ฐธ๊ณ ์๋ฃ>
๋ณธ ํฌ์คํ
์ ์ ์๊ตฌ๋์ Spring Boot JWT Tutorial ๊ฐ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ฑํ์์ต๋๋ค.
'๐ Backend > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Security] JWT ๊ตฌํ (6) - ํ์ ๊ฐ์ & Authorization Validation (0) | 2023.06.07 |
---|---|
[Security] JWT ๊ตฌํ (5) - ๋ก๊ทธ์ธ์ ํตํด JWT ํํฐ ํ ์คํธ (0) | 2023.06.06 |
[Security] JWT ๊ตฌํ (3) - ํ ํฐ ์์ฑ๊ธฐ ๊ตฌํ (JWT TokenProvider) (0) | 2023.06.06 |
[Security] JWT ๊ตฌํ (2) - Security ์ค์ , ๋ฐ์ดํฐ ์ฝ์ (0) | 2023.06.06 |
[Security] JWT ๊ตฌํ (1) - ํ๋ก์ ํธ ์์ฑ (0) | 2023.06.06 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422