-
Notifications
You must be signed in to change notification settings - Fork 5
Spring Security
-
참고링크 : 라흐&히히 코드의 최초 코드이자 라흐&히히가 공부한 사이트
스프링 시큐리티는 필터를 사용합니다.
- 필터는 인터셉터와 비슷하지만, 서블릿보다도 더 앞에 있습니다.
- 필터는 클라이언트 요청이 서블릿으로 가기 전에 먼저 처리할 수 있도록 톰캣(WAS)에서 지원해주는 기능입니다.
아래 그림에 보시면 SecurityFilterChain 부분이 있는데요.
HttpRequest 가 서블릿에 도달하기 전에, 먼저 필터들을 위에서부터 통과합니다.
HttpResponse 가 나가기 전에, 필터들을 아래에서부터 통과합니다.
- 위 두 줄은 제가 기억나는대로 쓴건데 확실하지 않아요 -
- SecurityContextPersistentFilter
- SecurityContextRepository에서 SecurityContext를 가져와서 SecurityContextHolder에 주입하거나 반대로 저장하는 역할을 합니다.
- LogoutFilter
- logout 요청을 감시하며, 요청시 인증 주체(Principal)를 로그아웃 시킵니다.
- UsernamePasswordAuthenticationFilter
- login 요청을 감시하며, 인증 과정을 진행합니다.
- DefaultLoginPageGenerationFilter
- 사용자가 별도의 로그인 페이지를 구현하지 않은 경우, 스프링에서 기본적으로 설정한 로그인 페이지로 넘어가게 합니다.
- BasicAuthenticationFilter
- HTTP 요청의 (BASIC)인증 헤더를 처리하여 결과를 SecurityContextHolder에 저장합니다.
- RememberMeAuthenticationFilter
- SecurityContext에 인증(Authentication) 객체가 있는지 확인하고 RememberMeServices를 구현한 객체 요청이 있을 경우, RememberMe를 인증 토큰으로 컨텍스트에 주입합니다.
- AnonymousAuthenticationFilter
- 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 익명 사용자로 취급합니다.
- SessionManagementFilter
- 요청이 시작된 이후 인증된 사용자인지 확인하고, 인증된 사용자일 경우 SessionAuthenticationStrategy를 호출하여 세션 고정 보호 매커니즘을 활성화 하거나 여러 동시 로그인을 확인하는 것과 같은 세션 관련 활동을 수행합니다.
- ExceptionTranslationFilter
- 필터체인 내에서 발생되는 모든 예외를 처리합니다.
- FilterSecurityInterceptor
- AccessDecisionManager로 권한부여처리를 위임하고 HTTP 리소스의 보안 처리를 수행합니다.
우리 프로젝트의 config 패키지에 JwtAuthenticationFilter 클래스가 있습니다.
이 클래스가 저희의 커스텀 필터입니다.
- 이 필터의 목적
-
사용자가 자원에 접근 할 때 (ex 내 정보 조회), 헤더의 토큰을 검사해서
유효한 토큰이 아니면 접근을 못하게 하는 목적입니다.
-
// config/JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenDecoder jwtTokenDecoder;
public JwtAuthenticationFilter(JwtTokenDecoder jwtTokenDecoder) {
this.jwtTokenDecoder = jwtTokenDecoder;
}
~~~~
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String token = ((HttpServletRequest) request).getHeader("X-AUTH-TOKEN");
if (isValidToken(token)) {
// 토큰으로부터 Authentication 객체를 얻습니다.
Authentication authentication = jwtTokenDecoder.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장합니다.
SecurityContextHolder.getContext()
.setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private boolean isValidToken(String token) {
return Objects.nonNull(token)
&& jwtTokenDecoder.isValidToken(token);
}
}
- 토큰이 유효한 경우, 토큰으로부터 Authentication 객체를 얻어서 SecurityContext 영역에 저장합니다.
-
어떤 사용자가 토큰을 담아서 요청을 보내면, 필터는 이 요청으로부터 Authentication 을 얻습니다.
-
Authentication 객체에는 해당 유저의 Role (ex :일반사용자인가 ? 관리자인가?) 등
유저정보가 들어있습니다.
Authentication 객체에 대한 설명은 3.2로 이어집니다~
-
어떤 자원에 대한 접근 요청이 들어오면, 이 요청은
일반사용자의 요청인지, 관리자의 요청인지, 익명 사용자의 요청인지 등
요청을 보낸 주체(principle)가 있습니다.
-
주체(principle)는 자신에 대한 Authentication 이 존재하고, 이 Authentication 객체는 Security Context 에 저장되어 관리됩니다.
- 필터 체인의 필터들은 순서대로 실행되는데, 모든 필터들은 Security Context 라는 영역에 접근할 수 있습니다.
- Security Context 라는 공간은 한 번의 요청에서 사용되고 난 뒤 없어집니다.
-
우리 프로젝트의 config 패키지에 WebSecurityConfig 클래스가 있는데요.
configure 메서드를 보시면 addFilterBefore(..) 이 부분이 있습니다.
이 부분이 커스텀 필터를 시큐리티 필터 체인에 연결하는 부분입니다.
-
우리 코드에서는 JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 넣고 있습니다.
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenDecoder jwtTokenDecoder;
public WebSecurityConfig(JwtTokenDecoder jwtTokenDecoder) {
this.jwtTokenDecoder = jwtTokenDecoder;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 만을 고려하여 기본 설정은 해제하겠습니다.
.csrf().disable() // csrf 보안 토큰 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 토큰 기반 인증이므로 세션 역시 사용하지 않습니다.
.and()
.authorizeRequests() // 요청에 대한 사용권한 체크
.antMatchers("/users/**").hasRole("USER")
.anyRequest().permitAll() // 그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenDecoder),
UsernamePasswordAuthenticationFilter.class);
/* JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣는다 */
}
}