Dev.GA

[spring/게시판] #5 Interceptor를 이용한 로그인 처리 본문

Dev.Project/project1-spring게시판

[spring/게시판] #5 Interceptor를 이용한 로그인 처리

Dev.GA 2018. 2. 2. 02:03



개발환경

Server OS : Windows10

Language : JAVA 1.6

Framework : Spring 3.1.1

WEB Server : Apache 

WAS Server : Tomcat 7

build tool : maven 2.5.1

DB : MySQL 5.7.16

ORM : mybatis 3.2.7


5. Interceptor를 이용한 로그인 처리



너무나 오랜만에 포스팅을 하게된다. 두달만..


이전까지 게시판의 기본적인 CRUD와 와꾸(?)를 잡아 주었기 때문에 하나씩 기능을 추가해보겠다.


이번 포스팅은 Interceptor를 이용한 로그인 세션 처리를 해보겠다.


먼저 Interceptor란 ? 


"가로챈다" 라는 의미를 가지고 있는 뜻으로, Spring에서는 Controller로 가는 요청을 가로채어 Controller를 제어하는 역할을 한다.


spring에서는 HandlerInterceptorAdapter라는 추상 클래스를 지원하는데 HandlerInterceptorAdapter는 3가지 메소드를 제공한다.


  • preHandle : Controller가 호출되기 전 수행
  • postHandle : Controller가 완료된 이후에 수행
  • afterCompletion : Controller 수행 후 view단 작업까지 완료 된 후 호출 

3가지 메소드를 활용하여 Interceptor는 다양한 방법으로 사용될 수 있는데, 그 중 대표적인 방법이 로그인 세션을 처리하는 것이다.


만약, Interceptor로 로그인을 처리하지 않는다면 사용자가 접속하여 여러가지 기능(읽고, 쓰고 등등)과 화면전환 시 마다 세션정보를 확인하여야 하는 문제가 발생한다. Spring에서는 이러한 기능을 Interceptor가 대신 할 수 있는 것이다.



우선 로그인 처리를 하기 위해서는 member테이블과 회원가입 절차가 필요하기에 간단하게 만들어본다.


1
2
3
4
5
6
7
8
CREATE TABLE `member` (
  `code` INT(10NOT NULL AUTO_INCREMENT,
  `mbrId` VARCHAR(20DEFAULT NULL,
  `mbrPw` VARCHAR(20DEFAULT NULL,
  `mbrPw_check` VARCHAR(20DEFAULT NULL,
  `mbr_name` VARCHAR(20DEFAULT NULL,
  PRIMARY KEY (`code`)
ENGINE=INNODB AUTO_INCREMENT=DEFAULT CHARSET=utf8mb4
cs

member테이블을 만들고 member 테이블 VO객체를 만들어준다.

MemberVO.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.ga.member.service;
 
public class MemberVO {
 
    private String mbr_code = "";
    private String mbrId = "";
    private String mbrPw = "";
    private String mbrPw_check = "";
    private String mbr_name = "";
    
    public String getMbr_code() {
        return mbr_code;
    }
    public void setMbr_code(String mbr_code) {
        this.mbr_code = mbr_code;
    }
    public String getMbrId() {
        return mbrId;
    }
    public void setMbrId(String mbrId) {
        this.mbrId = mbrId;
    }
    public String getMbrPw() {
        return mbrPw;
    }
    public void setMbrPw(String mbrPw) {
        this.mbrPw = mbrPw;
    }
    public String getMbrPw_check() {
        return mbrPw_check;
    }
    public void setMbrPw_check(String mbrPw_check) {
        this.mbrPw_check = mbrPw_check;
    }
    public String getMbr_name() {
        return mbr_name;
    }
    public void setMbr_name(String mbr_name) {
        this.mbr_name = mbr_name;
    }
    
}
 
cs

MemberVO를 만들었으면 회원가입 절차가 들어간 MemberService, Impl, DAO, mapper를 만들어 준다.
간단한 insert 기능이기에 앞으로 이러한 간단한 코드는 생략하겠다.


나의 경우는 위와 같이 회원가입 창을 만들고 '회원가입' 버튼을 통해 Action을 MemberController로 전달하고

MemberServiceImpl에서 회원가입 절차를 만들었다.


MemberServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.ga.member.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.ga.common.LoginUtil;
import com.ga.member.service.MemberVO;
 
@Service("memberServiceImpl")
public class MemberServiceImpl implements MemberService {
 
    @Autowired
    private MemberDAO memberDAOService;
    
    @Override
    public void insertMembership(MemberVO memberVO) throws Exception {
                
        String encode_password = LoginUtil.encryptPassword(memberVO.getMbrId(), memberVO.getMbrPw());
        memberVO.setMbrPw(encode_password);
        memberVO.setMbrPw_check(encode_password);
        memberDAOService.insertMembership(memberVO);
        
    }
 
}
cs



18번째 라인) 사용자가 입력한 id, password를 이용하여 패스워드를 암호화하는 과정이다.

LoginUtil의 encryptPassword method를 통해 암호화 과정을 거친 후 DB에 저장한다.

암호화에는 'SHA-512'를 사용하였다.

LoginUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.ga.common;
 
import java.security.MessageDigest;
 
import org.apache.commons.codec.binary.Base64;
 
public class LoginUtil {
    
    public static String encryptPassword(String id, String pw) throws Exception{
        
        if(pw == null){
            return "";
        }
        
        byte[] hashValue = null;
        
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        
        md.reset();
        md.update(id.getBytes());
        
        hashValue = md.digest(pw.getBytes());
        
        return new String(Base64.encodeBase64(hashValue));
    }
 
}
 
cs


위의 encryptPassword method는 사용자의 id와 password를 가지고 암호화를 하는 것이기 때문에
뒤이어 진행하게될 로그인 처리시, 패스워드 check에서 사용하게 된다.

그럼 회원가입을 하는 간단한 절차는 마무리 되었고, 위의 과정을 엮으로 진행하여
사용자의 id, password를 입력해 member테이블에서 조회하여 일치하면 간단하겠지만, 이는 단순한 테이블을 select하는 행위에 불과하기 때문에 처리해야할 일이 남았다.

사용자의 올바른 정보를 입력하여 로그인에 성공했다면, 해당 사용자의 세션처리를 통해 지속적으로 사용자의 정보를 가지고 있도록 세션 처리를 해줘야 한다.


이 로그인 세션처리 과정을 Interceptor라는 것을 사용할 것이다.


먼저, 사용자의 정보를 가지고 DB에서 로그인 할 수 있도록 처리하겠다.


main_SQL.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="com.ga.main.service.mapper.MainMapper">
        
    <select id="loginAction" parameterType="com.ga.common.LoginVO" resultType="com.ga.common.LoginVO">
        select
            mbrId as user_id
            , mbrPw as user_pw
            , mbr_name as user_name
        from member
        where 1=1
        and mbrId = #{user_id}
        and mbrPw = #{user_pw}
    </select>
    
</mapper>
cs

간단하게 사용자 id와 패스워드를 조회하여 일치하는 사용자 정보를 가져오는데 로그인에 사용할 VO객체를 LoginVO라 하여 따로 만들어 주었다.

다음에 진행 할 Controller에서 확인 할 수 있다.

MainController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.ga.main.web;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.ga.common.LoginVO;
import com.ga.common.annotation.NoLoginCheck;
import com.ga.main.service.impl.MainService;
 
@Controller
public class MainController {
    
    @Autowired
    private MainService mainServiceImpl;
    
    @RequestMapping(value="/main.do")
    public String mainPage() throws Exception{
        
        return "main/loginForm";
    }
    
    @RequestMapping(value="/main/login.do")
    public String login(@ModelAttribute("loginVO") LoginVO loginVO,
            HttpServletRequest request,
            Model model) throws Exception{
        
        LoginVO resultVO = new LoginVO();
        
        resultVO = mainServiceImpl.loginAction(loginVO);
                
        if(resultVO != null && !resultVO.getUser_id().equals(""&& !resultVO.getUser_pw().equals("")){
            
            request.getSession().setAttribute("loginVO", resultVO);
            
            return "forward:/board/boardList.do";
            
        } else {
            
            model.addAttribute("msg""사용자의 ID 혹은 패스워드가 일치하지 않습니다.");
            
            return "redirect:/main.do";
            
        }
    }
}
 
cs

사용자가 정보를 입력하고 로그인 버튼을 실행하게 되면,
34번째 라인) 로그인을 진행할 Controller에서 정보와 위에서 작성한 SQL로 질의하여 사용자 정보를 가져온다.

LoginVO에 담긴 사용자 정보는
36번째 라인) 올바른 정보인지 확인하여 40번째 라인) 접속 화면으로 이동하거나,
46번째 라인) 사용자 정보가 없을 경우 다시 로그인 화면으로 보내준다.

이때 올바른 접속정보일 경우 38번째 라인) request.getSession().setAttribute() 를 통해 세션에 사용자 정보를 담아준다.

사실 이렇게만 하면 로그인 처리는 확인이 되었으나, 위에서 말한 Interceptor설정을 해주어야 한다.

servlet-context.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />
 
    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />
    <resources mapping="/css/**" location="/css/"/>
    <resources mapping="/js/**" location="/js/"/>
    
    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    
    <context:component-scan base-package="com.ga" />
    
    <beans:bean id="LoginInterceptor" class="com.ga.common.interceptor.LoginInterceptor"></beans:bean>
    
    <interceptors>
        <interceptor>
            <mapping path="/board/*.do"/>
            <beans:ref bean="LoginInterceptor"/>
        </interceptor>
    </interceptors>
    
</beans:beans>
 
cs


servlet-context.xml에서 

30번째 라인) <interceptors> </interceptors> 태그 안에 <mapping path="" /> 로 설정을 해주면 interceptor가 컨트롤러를 제어 할 수 있게 된다. 컨트롤러 단위별로 하나씩 해도 되며, bean으로 선언된 interceptor에 해당하는 기능을 통해 제어 하고 싶은 컨트롤러를 지정해 주면된다.


추가로 spring 3.2버전 부터는 exclude-mapping이라 해서 해당 interceptor로부터 제외시키는 기능을 제공해준다.


LoginInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.ga.common.interceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
import com.ga.common.LoginVO;
import com.ga.common.annotation.NoLoginCheck;
 
public class LoginInterceptor extends HandlerInterceptorAdapter{
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        
        HttpSession session = request.getSession();
        LoginVO loginVO = (LoginVO) session.getAttribute("loginVO");
 

        if(loginVO == null){
            response.sendRedirect("/main.do");
            return false;
        }

        
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub
        super.postHandle(request, response, handler, modelAndView);
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // TODO Auto-generated method stub
        super.afterCompletion(request, response, handler, ex);
    }
 
    
    
}
 
cs


25번째 라인) 과 같이 세션정보가 없을 경우 로그인 페이지로 이동시킨다.(여기서는 /main.do가 로그인 페이지가 된다)

26번째 라인) return 값을 false로 해주어, 다음 요청으로 넘어가지 않게 해준다.


여기서 봐야할 Interceptor의 특징은,

1. prehandle에서 return값이 false일 경우 다음으로 넘어가지 않고 끝나게 된다.

2. posthandle의 경우 Controller에서 Exception이 발생 할 경우 posthandle로 요청이 넘어오지 않는다.

3. afterCompletion의 경우 Exception이 발생하여도 뷰단은 실행된다.


따라서, 로그인 세션정보가 없을 경우 모두 로그인 페이지로 넘어가도록 redirect시켜주었다.


메인페이지 화면이다.


상단에 header를 두어 세션정보를 모든 화면에서 볼 수 있도록 하였다.


현재 세션 정보가 없기에 오른쪽 상단에 로그인 버튼이 출력되며, 글쓰기 혹은 게시글을 클릭할 경우 preHandle에 따라 로그인 페이지로 redirect된다.


로그인에 성공하여 세션정보를 가지고 있으면, 상단에 세션정보가 나타나게 되고 글쓰기 혹은 게시글을 클릭하여도 정상적인 요청으로 실행이 된다.


MainController.java
1
2
3
4
5
6
7
8
9
@RequestMapping(value="/main/logout.do")
    public String logout(@ModelAttribute("loginVO") LoginVO loginVO,
            HttpServletRequest request,
            Model model) throws Exception{
        
            request.getSession().removeAttribute("loginVO");
            
            return "redirect:/board/boardList.do";
    }
cs

다음으로 로그아웃 기능을 만들어 MainController에 추가한다.

6번째 라인) 세션정보를 제거하여 로그아웃에 성공하도록 해주며, 메인페이지(게시판)로 redirect시켜준다.



이로써, Interceptor를 통한 로그인 처리를 완료하였다. Interceptor로는 로그인 처리 이외에 감사관리, 권한관리 등을 처리할 수 있으며

예외처리에도 유용하게 사용할 수 있다.




Comments