본문 바로가기

비트교육센터[전문가반]

Spring MVC [비트교육센터] _ 복습04 _ Security (Interceptor + Annotation)

프로세스 그림으로 설명

1. Inteceptor

 

1-1) 인터페이스로 Interceptor 만들기 (interface HandlerInterceptor) 

//인터페이스로 Interceptor 만든경우

public class MyInterceptor01 implements HandlerInterceptor {

 

           // Handler 처리하기 전에 (시스템 외부)

           // preHandle만 반환 값이 존재한다.

           // return false; 로 하면 preHandle까지만 들어고 막는다.  || return true; 인 경우 통과시킨다.

           @Override

           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

                                throws Exception {

                    

                     System.out.println("MyInterceptor01.preHandle");

                     return false;

           }

 

           // Handler 끝난 후에

           @Override

           public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

                                ModelAndView modelAndView) throws Exception {

 

                     System.out.println("MyInterceptor01.postHandle");

           }

 

           // ViewResolver fowarding이 끝난 경우

           @Override

           public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

 

                     System.out.println("MyInterceptor01.afterCompletion");

                    

           }

 

}

 

1-2) 상속으로 Interceptor 만들기 (HandlerInterceptorAdapter 을 상속받는다.)

 

//상속으로 Interceptor 만든경우

//preHandle(), postHandle(), afterCompletion()를 재정의해서 사용한다.

public class MyInterceptor02 extends HandlerInterceptorAdapter {

 

           @Override

           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

                                throws Exception {

 

                     System.out.println("MyInterceptor02.preHandle");

                     return true;

           }

 

}

 

1-3) 만든 Interceptor 클래스를 spring-servlet.xml 에 등록해주어야한다.

 

<!-- Interceptors 설정 -->

<mvc:interceptors>

           /user/auth로 요청이 들어왔을때 Interceptor 설정

           <mvc:interceptor>

                     <mvc:mapping path="/user/auth" />

                                <bean class="com.bitacademy.mysite.interceptor.MyInterceptor01" />

           </mvc:interceptor>

</mvc:interceptors>      

 

# Interceptor을 활용한 Login, Logout Security 만들 수 있다. (활용예제)

 

- [ LoginInterceptor.java ]

: UserController @RequestMapping(value="/join", method=RequestMethod.POST) 역할을 한다.

   => 로그인 실패시 redirect 작업 / 로그인 성공시 session에 등록해주는 작업

 

public class LoginInterceptor extends HandlerInterceptorAdapter {

 

           @Autowired

           private UserService userService;

          

           @Override

           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

                 throws Exception {

                     //login.jsp 페이지에서 입력한 이메일과 패스워드를 전달한 것을 request로 받는다.

                     String email = request.getParameter("email");

                     String password = request.getParameter("password");

                     UserVo userVo = new UserVo();

                     userVo.setEmail(email);

                     userVo.setPassword(password);

                    

 

                     UserVo authUser = userService.getUser(userVo);

                     //로그인 인증 실패한경우

                     if(authUser == null) {

                                response.sendRedirect(request.getContextPath() + "/user/login?result=fail");

                                return false;

                     }

                    

                     //로그인 인증 성공한 경우

                     //session 처리

                     HttpSession session = request.getSession(true);

                     session.setAttribute("authUser", authUser);

                     response.sendRedirect(request.getContextPath());

                    

                     return false;

           }

 

- [LogoutInterceptor.java]

: @RequestMapping("/logout") 역할을 한다.

   => 로그아웃시 세션 지우고 세션 비활성화 작업

 

public class LogoutInterceptor extends HandlerInterceptorAdapter {

           @Override

           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

                throws Exception {

 

                     HttpSession session = request.getSession();

                    

                     // 로그아웃 처리

                     if(session != null) {

                                session.removeAttribute("authUser"); //세션 지우고

                                session.invalidate(); //세션 비활성화(세션 삭제)

                     }

                    

                     response.sendRedirect(request.getContextPath());

                     return false;

           }

}

 

- [spring-servlet.xml]

 

</mvc:interceptors>

           <!-- login interceptor 등록 -->

           <mvc:interceptor>

                     <mvc:mapping path="/user/auth" />

                     <bean class="com.bitacademy.mysite.security.LoginInterceptor" />

           </mvc:interceptor>

 

           <!-- logout interceptor 등록 -->

           <mvc:interceptor>

                     <mvc:mapping path="/user/logout" />

                     <bean class="com.bitacademy.mysite.security.LogoutInterceptor" />

           </mvc:interceptor>

          

</mvc:interceptors>

 

 

2. Annotation 을 이용한 Security (@Auto)

 

예제상황: 회원정보를 수정하는 /update 요청이 들어왔을때 Security

 

2-1) Auth 어노테이션 파일 생성 

[Auth.java]

 

@Retention(RUNTIME)

@Target({METHOD, TYPE})

public @interface Auth {

          

           public Role role() default Role.USER;

          

}

 

2-2) [UserController.java]

: @Auth를 메서드(컨트롤러)에 선언 해줌으로써 /update 요청시 AuthInterceptor 를 수행할 수 있게한다.

 

@Auth

@RequestMapping(value="/update", method=RequestMethod.GET)

public String update(HttpSession session, Model model) {

                    

           // 접근제어

           UserVo authUser = (UserVo)session.getAttribute("authUser");

           Long no = authUser.getNo();

           UserVo userVo = userService.getUser(no);

           model.addAttribute("userVo", userVo);

                    

           return "user/update";

}

 

2-3) [AuthInterceptor.java]

: /update 를 요청한 Client UserVo 정보를 확인하여 Session authUser가 있다면 return true;

  없으면 return false; 하는 Interceptor 이다.

 

public class AuthInterceptor extends HandlerInterceptorAdapter {

 

           @Override

           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

                                throws Exception {

                     //1. Handler 종류 확인해 본다.

                     // DeafultServletHandler가 처리하는 경우(보통, assets의 정정 자원 접근) 통과시켜준다.

                     if(handler instanceof HandlerMethod == false) {

                                return true;

                     }

                    

                     //2. Handler Method(컨트롤러) 인 경우 casting 해준다.

                     HandlerMethod handlerMethod = (HandlerMethod)handler;

                    

                     // 3. 클라이언트가 요청한 url 에 해당하는 Method(컨트롤러) @Auth 달려 있는지 확인하기

                     Auth auth = handlerMethod.getMethodAnnotation(Auth.class);

                    

                     // 4. 클라이언트가 요청한 url 에 해당하는 Method(컨트롤러) @Auth가 안달려 있으면      

                    // return true;   (패스시켜준다.)

                     if(auth == null) {

                                return true;

                     }

                    

                     // 5. @Auth가 달려 있는 Method(컨트롤러) 경우에는 인증(Authetication) 여부 확인

                     HttpSession session = request.getSession();

                     if(session == null) { // 세션이 없는 경우 돌려보낸다.(로그인 하는 페이지로)

                                response.sendRedirect(request.getContextPath() + "/user/login");

                                return false;

                     }

                    

                     // 6. 세션이 있는 경우에는

                     UserVo authUser = (UserVo)session.getAttribute("authUser");

                     if(authUser == null) { // 인증이 없는 경우 돌려보낸다.(로그인 하는 페이지로)

                                response.sendRedirect(request.getContextPath() + "/user/login");

                                return false;                           

                     }

                    

                     // 여기까지 통과한 경우는 세션에 authUser가 있는 경우임으로 통과시켜준다.

                     return true; 

           }

 

}

 

2-4) [spring-servlet.xml] AuthInterceptor 등록 

: 모든 경로에 AuthInterceptor을 하도록 하는데, 3개의 url만 제외 (/assets/** || /user/auth || /user/logout)

  

</mvc:interceptors>      

           <mvc:interceptor>

                     <mvc:mapping path="/**" />

                     <mvc:exclude-mapping path="/assets/**" />

                     <mvc:exclude-mapping path="/user/auth" />

                     <mvc:exclude-mapping path="/user/logout" />

                                <bean class="com.bitacademy.mysite.security.AuthInterceptor" />

           </mvc:interceptor>

</mvc:interceptors>      

 

3. Annotation 을 이용한 Security (@AuthUser 파리미터)

 

UserController update() 컨트롤러에서 authUser session을 통해서 불러오는 방법으로 접근제어 했었다.

=> Argument Resolver 라는 방법을 사용해서 update() 컨트롤러에 session에 있는 authUser를 매개변수로

   전달하는 것을 만들 수 있다.

 

* Argument Resolver ,

사용자가 컨트롤러의 메서드 인자값으로 임의의 값을 전달하려할때 사용된다.

(, 세션에 저장되어 있는 값 중, 특정 이름의 값을 메서드 인자로 전달한다.)

 

3-1) AuthUser 어노테이션 파일 생성

[AuthUser.java]

 

@Retention(RUNTIME)

@Target(PARAMETER)

public @interface AuthUser {

          

}

 

3-2) AuthUserHandlerMethodArgumentResolver 클래스 생성 (HandlerMethodArgumentResolver 를 상속)

: AuthUser에 대한 Argument Resolver를 만든다.

 

 [AuthUserHandlerMethodArgumentResolver.java]

 

//@AuthUser 파라미터 어노테이션을 검사해서 처리하는 Argument Resolver 작성

public class AuthUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

 

           // @AuthUser 검사하는 메서드

           @Override

           public boolean supportsParameter(MethodParameter parameter) {

                    

                     // 1. 요청한 파라미터에  @AuthUser 달려 있는지 확인하기

                     AuthUser authUser = parameter.getParameterAnnotation(AuthUser.class);

                    

                     // 2. 요청한 파라미터에  @AuthUser 가 안붙어 있는 경우 패스시켜준다.

                     if(authUser == null) {

                                return false;

                     }

                               

                     // 파라미터 타입이 UserVo가 아니면 ... 패스시켜준다.

                     if(!parameter.getParameterType().equals(UserVo.class)) {

                                return false;

                     }

                               

                     return true;

           }

                    

           @Override

           public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,

                                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

                    

                     //UserVo 타입이 아니거나, @AuthUser 파라미터가 아닌 경우 UNRESOLVED 시켜준다.

                     if(!supportsParameter(parameter)) {

                                return WebArgumentResolver.UNRESOLVED;

                     }

                    

                     //여기서부터는 @AuthUser 파라미터이면서 UserVo 타입이라는 이라는 것

                    

                     //webRequest 로 파라미터를 받는 이유는 Spring이 톰켓 외 was에도 사용될 수 있기때문에 범위를

                     //넓혀서 매개변수로 보낸것

                     //우리는 톰캣임으로 HttpServletRequest casting 해서 사용하면 된다.

                     HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();

                     HttpSession session = request.getSession();

                    

                     if(session == null) {

                                return null;

                     }

                    

                     return session.getAttribute("authUser");

                    

           }

          

          

 

}

 

3-3 [spring-servlet.xml] 에서 argument resolver 등록해주어야한다.

 

<!-- argument resolver -->

<mvc:annotation-driven>

           <mvc:argument-resolvers>

                     bean class="com.bitacademy.mysite.security.AuthUserHandlerMethodArgumentResolver"/>          

           </mvc:argument-resolvers>                   

</mvc:annotation-driven>