[spring] Intercepter
Spring Intercepter
-
로그인이 안되어 있을시 리다이렉트 구현시에, 많은 API 들에 로그인이 되어있는지 확인 하는 코드를 중복해서 똑같이 집어넣으면, 가독성이 떨어지고, 중복성이 심해진다.
-
이럴때 HandlerIntercepter 인터페이스를 사용한다.
Spring MVC Lifecycle
Intercepter 까지 가는 과정
Dispatch Servlet => Handler Adpater => Interceptor
Handler Intercepter
-
다수의 컨트롤러에 대해 동일한 기능을 똑같이 적용해야 할 때 사용한다.
-
org.springframework.web.HandlerInterceptor 인터페이스를 사용한다.
-
다음과 같은 시점에서 인터셉터를 발동할 수 있다.
- 컨트롤러(핸들러) 실행전
- 컨트롤러(핸들러) 실행 후, 아직 뷰를 실행하기 전
- 뷰를 실행한 이후
Handler Intercepter는 다음과 같은 메서드를 정의하고 있다.
1. preHandle()
- 컨트롤러(핸들러) 객체를 실행하기 전에 필요한 기능을 구현할 때 사용한다.
-
리턴 타입은 boolean 이다. preHandle() 이 false 를 리턴하면 컨트롤러(또는 다음 HandlerInterceptor)를 실행하지 않는다.
- Params ( Object handler )
- 웹 요청을 처리할 컨트롤러 객체
- 로그인하지 않은 경우 컨트롤러를 실행 안시키게 할수 있음
- 컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성
boolean preHandle(HttpServletRequest request, HttpservletResponse response, Object handler) throws Exception;
2. postHandle()
- 컨트롤러(핸들러)가 정상적으로 실행된 이후에 추가기능을 구현할 때 사용한다.
- 컨트롤러가 Exception 을 발생하면 PostHandle() 메서드는 실행하지 않는다.
void postHandle(HttpServletRequest request, HttpservletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
3. afterCompletion()
-
컨트롤러 실행 이후에 예기치 않게 발생한 익셉션을 로그로 남긴다거나, 실행 시간을 기록하는 등의 후처리를 하기에 적합한 메소드다.
-
뷰가 클라이언트에 응답을 전송한 뒤에 실행된다.
- 컨트롤러 실행 과정에서 익셉션이 발생하면 이 메서드의 네 번째 파라미터로 전달된다.
- Exception ex
- 익셉션이 발생하지 않으면 네 번째 파라미터는 null 이 된다.
void afterCompletion(HttpServletRequest request, HttpservletResponse response, Object handler, Exception ex) throws Exception;
HandlerInterceptor와 컨트롤러의 실행 흐름 (Process)
<그림>
로그인 여부에따라 페이지를 못들어오게 리다이렉트 하는 것은 HandlerInterCepter 인터페이스에서 preHandler() 메소드를 사용한다.
인터셉터의 preHandler 사용, 로그인 안되어있을시 리다이렉트 예제
public class StudyAuthCheckIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession = request.getSession(false);
if (httpSession != null){
// httpSession이 만들어져 있는지 확인
Object authInfo = httpSession.getAttribute("authInfo");
// httpSession 에서 authInfo가 들어가 있는지 확인
if (authInfo != null){
return true;
// true 리턴하면 컨트롤러의 API 실행
}
}
response.sendRedirect(request.getContextPath());
return false;
// false 리턴하면 컨트롤러의 API 실행 안함
}
}
인터셉터를 만들었으면, 그 인터셉터를 어디에 적용해야 할지 설정해야 한다. WebMvcConfigurer 를 implements 한 MvcConfig 클래스에서 인터셉터를 설정한다.
인터셉터 설정 예제)
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public StudyAuthCheckIntercepter studyAuthCheckIntercepter() {
return new StudyAuthCheckIntercepter();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(studyAuthCheckIntercepter()).addPathPatterns("/api/back/board/**");
// addPathPatterns : 인터셉터를 적용할 경로 패턴을 지정한다.
// Ant 경로 패턴을 사용한다.
}
}
Ant 경로 패턴
- *, **, ? 의 세가지 특수 문자를 이용해서 경로를 표현한다.
* : 0개 또는 그 이상의 글자
? : 1개 글자
** : 0개 또는 그 이상의 폴더 경로
Ant 경로 패턴 사용 예제
@RequestMapping("/member/?*.info")
// /member/로 시작하고 확장자가 .info로 끝나는 모든 경로
@RequestMapping("/fqq/f?OO.fq")
// /fqq/f 로 시작하고, 1글자가 사이에 위치하고 OO.fq 로 끝나는 모든 경로
@RequestMapping("/folders/**/files")
// /folders/ 로 시작하고, 중간에 0개 이상의 중간 경로가 존재해ㅏ고 /files로 끝나는 모든 경로. 예를 들어 /folder/files, /folders/1/2/3/files 등등..
exlucdePathPatterns()
- addPathPatterns() 메서드에 지정한 경로 패턴 중 일부를 제외하고 싶을 때 사용한다.
Spring Security 사용 Inteceptor 만들기
참고사이트 :
https://soon-devblog.tistory.com/5?category=1026232
- Spring-scurity를 사용하면 모든 요청은 Session을 발급받는다.
-
Session을 발급받으면 클라이언트의 쿠키에 JESSIONID라는 키로 SessionID가 저장된다.
-
AuthenticationFilter는 해당 요청의 JESSIONID(SessionID)를 확인하여 JESSIONID와 매핑되는 인증정보가 Security Context에 있는지 판단한다.
-
JESSIONID(SessionID)에 매핑되는 인증정보가 Security Context 내에 없으면 HTTP Error Code 를 발생시키거나, 리다이렉트 처리를 할 수 있다.
-
- AuthenticationFilter 는 기본적으로 로그인폼으로 오는 데이터를 username과 password로 인식하고 있다.
- Spring Security는 자동으로 HTML Login Form을 생성한다.
- 따라서 처리하려는 로그인 데이터 양식도 생성된 Login Form 에 맞춰서 username / password 로 변수명을 통일시켜줘야 한다.
- Spring-security는 dafault 설정으로 Mapping URL을 /login 으로 설정했는데, 프론트에서 오는 로그인 요청도 /login 으로 통일시켜 줘야한다.
- AuthenticationFilter는 입력받은 username / password를 이용해 UsernamePasswordAuthenticationToken 을 만든다.
- 통칭 AuthenticationToken 은 Authentication 인터페이스의 구현체다.
- AuthenticationToken에 있는 username / password가 유효한 계정인지 판단하기 위해 AuthenticationMangaer에게 위임한다.
- AuthenticationManager는 등록한 AuthenticationProvider들을 연쇄적으로 실행시킨다.
- AuthenticationProvider의 구현체에서는 다음과 같은 작업이 필요하다.
- (1). AuthenticationToken에 있는 계정 정보가 유효한지 판단하는 로직 (DB로부터 조회)
- (2). 계정 정보가 유효하다면 유저의 상세정보(이름, 나이 등 필요한 정보)를 이용해 새로운 UserPasswordAuthenticationToekn 을 발급
- 새롭게 발급받은 AuthenticationToken은 Security Context에 저장된다.
- 계정 정보가 유효하다면 AuthenticationFilter는 AuthencationSuccessHandler에 따라 요청을 redirect 시킨다.
요약
Session을 발급받는다 ->
클라이언트 쿠키에 JSSESSIONID 가 생성된다. ->
filter는 JSSESSIONID 가 Security context에 있는지 확인한다 ->
있으면 Token을 발급시킨다 ->
Manager는 등록한 Provider들을 연쇄 실행시킨다 ->
Provider는 Token에 있는 계정정보가 유효한지 DB로부터 조회한다 ->
정보가 유효하면 Provider는 Token 을 발급시킨다. ->
발급된 Token은 Security Context에 저장된다. ->
계정 정보가 유효하면 Filter는 SusccesHandler에 따라 요청을 redirect 시킨다.
Spring Security 구현하기
- Maven dependency에 스프링 시큐리티를 추가한다.
- WeboSecurityConfig 클래스를 생성한다.
- WebSecurityConfigurerAdapter를 상속받는다.
- 다음과 같은 설정들을 추가한다.
- 어떤 요청에 대해서 인증을 요구할 것인지
- 특정 요청에 대해서 어떤 권한을 요구할 것인지
- 인증되지 않은 요청을 어떤 url로 redirect시킬지
- 로그인 성공 후 어느 화면으로 이동시킬지
- 로그아웃시 어떤 작업을 수행시킬지
- 403에러에 대해 어떻게 처리할지
- Authentication 유효성을 어떻게 판단할지
- configure 를 Override 해서 구현한다.
- AuthenticationSuccessHandler class 를 생성한다.
- SavedRequestAwareAuthenticationSuccessHandler 를 상속받는다.
- 인증 성공 후에 Redirect시킬 URL을 설정한다.
- AuthenticationProvider를 구현한다.
- Authentication 을 Override 한다.
- 인증을 처리한다.
- AccessDeniedHandler 를 구현한다.
- 인증에 성공했으나, 권한이 적합하지 않을경우 403 에러를 발생시킨다.
SecurityConfiguraiton 예제)
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private AuthProvider authProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
// authorizeRequest() 요청에 대한 인증/권한 설정을 담당한다.
.antMatchers("/","main").permitAll()
// ("/", "main") 요청은 인증 체크를 하지 않는다.
.antMatchers("/admin/*").hasAnyAuthority("ADMIN")
// ("admin/*")에 해당하는 요청은 "ADMIN" 권한이 있어야 접근 가능하다.
.anyRequest().authenticated()
// 위에서 정의한 요청을 제외한 모든 요청에 대해서는 인증을 요구한다.
.and()
.formLogin()
// 로그인 관련 설정을 담당한다.
.loginPage("/login")
// 아직 인증 전 상태이며 인증을 요구하는 요청이라면 "/login" 으로 redirect 시킨다.
.usernameParameter("userId")
// Spring Security에서 설정한 dafault 변수명인 username 을 userID 이름으로 변경시킨다.
.successHandler(new LoginSuccesHandler("/home"))
// 인증을 성공했을때 어떤 URL로 Redirect 시킬지 정의한다. 이를 위해 AuthenticationSuccessHandler 클래스를 생성해야 된다.
.permitAll()
.and()
.logout()
// 로그아웃 관련 설정을 담당한다.
.invalidateHttpSession(true)
// 로그아웃 요청이 오면 Session을 무효화 한다.
.deleteCookies("JESSIONID")
// 로그아웃 요청이오면 키-JESSIONID로 저장된 쿠키를 제거한다.
.permitAll()
.and()
.authenticationProvider(authProvider)
// 로그인 폼으로부터 오는 데이터가 유효한 계정 정보인지를 판단하기 위해 AuthenticationProvider를 구현했다.
.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
// 인증에는 성공했으나 권한이 맞지 않을 경우 실행될 Handler를 등록한다.
}