Spring Security๋ฅผ ์ด์ฉํด์ ๊ถํ๋ณ URL ์ ๊ทผ์ ์ค์ ํ๋ ์ค, 403 Forbidden ์๋ฌ๋ฅผ ๋ง์ฃผํ๋ค.
๋ฌธ์ ์ํฉ
๋ค์๊ณผ ๊ฐ์ด /admin/** ์ /users/** ์ ๋ํ ๊ถํ์ ์ค์ ํ ์ํ์์ ADMIN ๊ณ์ ์ผ๋ก ์ ๊ทผํ ๋ 403 Forbidden ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/users/**").hasRole("USER")
๋ฌธ์ ์์ธ ๋ถ์
JwtFilter์์ ์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ(Authorization)๋ฅผ ์ฒ๋ฆฌํ๋ฉด์ ๋ฐ์ํ๋ค.
Spring Security๊ฐ ๋ด๋ถ์ ์ผ๋ก SecurityContextHolder๋ฅผ ์ด์ฉํด ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋๋ฐ, JwtFilter์์ SecurityContextHolder์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ์ง ์์์ ๊ถํ ์ฒ๋ฆฌ๊ฐ ์ ๋๋ก ์ด๋ฃจ์ด์ง์ง ์์๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก JwtFilter์์ ์ธ์ฆ์ ์งํํ์์๋ Spring Security์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฐพ์ง ๋ชปํด 403 Forbidden ์๋ฌ๊ฐ ๋ฐํ๋ ๊ฒ์ด๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
SpringContextHolder์ ์ธ์ฆ ์ ๋ณด ์ ์ฅํ๊ธฐ
JwtFilter์์ JWT ํ ํฐ์ ํ์ฑํ์ฌ ์ฌ์ฉ์ ์ด๋ฉ์ผ๊ณผ ๊ถํ ์ ๋ณด๋ฅผ ์ถ์ถํ ๋ค, ์ด๋ฅผ SecurityContextHolder์ ์ ์ฅํ๋๋ก ์์ ํ๋ค.
String email = jwtUtil.extractEmail(jwt); // JWT์์ ์ด๋ฉ์ผ ์ถ์ถ
String userRole = jwtUtil.extractRoles(jwt); // JWT์์ ์ฌ์ฉ์ ๊ถํ ์ถ์ถ
UserRole role = UserRole.of(userRole); // ๊ถํ ๋ณํ
// ๊ถํ ๋ฆฌ์คํธ ์์ฑ
List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(role.name()));
// SecurityContextHolder์ ์ธ์ฆ ์ ๋ณด ์ ์ฅ
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(email, null, authorities)
);
JwtFilter์์ ์ธ๊ฐ ๋ก์ง ์ ๊ฑฐ
Spring Security์์ ๊ถํ(Authorization)์ ๊ด๋ฆฌํ๋๋ก ์ค๊ณํ๊ธฐ ๋๋ฌธ์ JwtFilter์์ ์ธ๊ฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ก์ง์ ์ ๊ฑฐํ๋ค.
์ด๋ฅผ ํตํด ์ธ์ฆ๊ณผ ์ธ๊ฐ ๋ก์ง์ด ์๋ก ์ถฉ๋ํ์ง ์๋๋ก ๊ตฌ์กฐ๋ฅผ ๋จ์ํํ๋ค.
hasRole ๋์ hasAuthority ์ฌ์ฉ
Spring Security์์ ๊ถํ์ ํ์ธํ ๋๋ ROLE_ ์ ๋์ฌ๊ฐ ํฌํจ๋ ๊ฐ์ ์ด์ฉํ๋ค. ์๋ฅผ ๋ค์ด, "ADMIN" ์ญํ ์ ํ์ธํ๋ ค๋ฉด "ROLE_ADMIN" ๊ถํ์ด ์กด์ฌํด์ผ ํ๋ค.
๋ฐ๋ผ์ hasRole("ADMIN") ๋์ hasAuthority("ADMIN")์ ์ฌ์ฉํด์ผ ์ ์์ ์ผ๋ก ์๋ํ๋ค.
์ต์ข ์ฝ๋
JwtFilter
@Slf4j
@RequiredArgsConstructor
@Component
public class JwtFilter implements Filter {
private final JwtUtil jwtUtil;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String url = httpRequest.getRequestURI();
if (url.startsWith("/auth")) {
chain.doFilter(request, response);
return;
}
String bearerJwt = httpRequest.getHeader("Authorization");
if (bearerJwt == null) {
// ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ 400์ ๋ฐํํฉ๋๋ค.
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT ํ ํฐ์ด ํ์ํฉ๋๋ค.");
return;
}
String jwt = jwtUtil.substringToken(bearerJwt);
try {
// JWT ์ ํจ์ฑ ๊ฒ์ฌ์ claims ์ถ์ถ
Claims claims = jwtUtil.extractClaims(jwt);
if (claims == null) {
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "์๋ชป๋ JWT ํ ํฐ์
๋๋ค.");
return;
}
// ์ธ๊ฐ -> ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ..? ๊ณ ๋ฏผ์ ํด๋ณด๊ณ
String email = jwtUtil.extractEmail(jwt);
String userRole = jwtUtil.extractRoles(jwt);
UserRole role = UserRole.of(userRole);
List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(role.name()));
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(email, null, authorities));
chain.doFilter(request, response);
} catch (SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature, ์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, ๋ง๋ฃ๋ JWT token ์
๋๋ค.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "๋ง๋ฃ๋ JWT ํ ํฐ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, ์ง์๋์ง ์๋ JWT ํ ํฐ ์
๋๋ค.", e);
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "์ง์๋์ง ์๋ JWT ํ ํฐ์
๋๋ค.");
} catch (Exception e) {
log.error("Internal server error", e);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
Spring Security
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.addFilterBefore(jwtFilter, SecurityContextHolderAwareRequestFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.requestMatchers("/todos/**").permitAll()
.requestMatchers("/admin/**").hasAuthority("ADMIN")
.requestMatchers("/users/**").hasAuthority("USER")
.anyRequest().authenticated()
)
.build();
}
}
๊ฒฐ๋ก
SecurityContextHolder๋ฅผ ํ์ฉํ์ฌ Spring Security์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ช
์์ ์ผ๋ก ์ ๋ฌํด์ผ ํ๋ค.
์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ(Authorization) ์ญํ ์ ๋ช
ํํ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ๋ค.
- JwtFilter: ์ธ์ฆ ์ ๋ณด ์ถ์ถ ๋ฐ ์ ์ฅ
- Spring Security: ๊ถํ ๊ด๋ฆฌ ๋ฐ ์ ๊ทผ ์ ์ด
๊ถํ ๊ฒ์ฌ ์ hasRole ๋์ hasAuthority๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ๋ค.