Spring 18 회원관리(꼼꼼하게 정리)

Kang.__.Mingu·2024년 9월 22일

Spring

목록 보기
17/21

Userinfo.java(DTO)

기존에 있던 테이블로 진행(userinfo 테이블)

/*
CREATE TABLE userinfo (
    userid   VARCHAR2(100) PRIMARY KEY,
password VARCHAR2(100),
name     VARCHAR2(200),
email    VARCHAR2(300),
auth     NUMBER(1)
);
*/

/*
이름       널?       유형
-------- -------- -------------
USERID   NOT NULL VARCHAR2(100)
PASSWORD          VARCHAR2(100)
NAME              VARCHAR2(200)
EMAIL             VARCHAR2(300)
AUTH              NUMBER(1)
*/

@Data
public class Userinfo {
    private String userid;
    private String password;
    private String name;
    private String email;
    private int auth;
}

UserinfoMapper(xml, java)

UserinfoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill09.mapper.UserinfoMapper">
    <!-- UserinfoMapper 인터페이스와 매핑될 SQL 쿼리들 정의 -->

    <!-- 회원정보 삽입 쿼리 -->
    <insert id="insertUserinfo">
        insert into userinfo values(#{userid}, #{password}, #{name}, #{email}, #{auth})
        <!-- MyBatis가 파라미터로 전달받은 Userinfo 객체의 필드 값들을 테이블 컬럼에 삽입 -->
    </insert>

    <!-- 개인 회원정보를 조건에 맞게 업데이트하는 쿼리 -->
    <update id="updateUserinfo">
        update userinfo
        <set>
            <!-- 특정 필드의 값이 null이 아니고, 빈 문자열이 아닐 때만 해당 컬럼을 업데이트 -->
            <if test="password != null and password != ''">
                password=#{password},
                <!-- 패스워드가 null이 아니고 빈 문자열이 아닐 경우 패스워드 업데이트 -->
            </if>
            <if test="name != null and name != ''">
                name=#{name},
                <!-- 이름이 null이 아니고 빈 문자열이 아닐 경우 이름 업데이트 -->
            </if>
            <if test="email != null and email != ''">
                email=#{email},
                <!-- 이메일이 null이 아니고 빈 문자열이 아닐 경우 이메일 업데이트 -->
            </if>
            <if test="auth == 1 or auth == 9">
                auth=#{auth}
                <!-- 권한(auth)이 1이거나 9일 경우에만 권한 값을 업데이트 -->
            </if>
        </set>
        where userid=#{userid}
        <!-- 특정 userid에 해당하는 행만 업데이트 -->
    </update>

    <!-- 회원정보 삭제 쿼리 -->
    <delete id="deleteUserinfo">
        delete from userinfo where userid=#{userid}
        <!-- 주어진 userid 값을 가진 회원정보 삭제 -->
    </delete>

    <!-- 특정 회원정보를 검색하는 쿼리 (결과는 Userinfo DTO 객체로 반환) -->
    <select id="selectUserinfo" resultType="Userinfo">
        select userid, password, name, email, auth from userinfo where userid=#{userid}
        <!-- 주어진 userid에 해당하는 회원정보를 검색 -->
    </select>

    <!-- 모든 회원정보를 검색하는 쿼리 (결과는 Userinfo DTO 객체 리스트로 반환) -->
    <select id="selectUserinfoList" resultType="Userinfo">
        select userid, password, name, email, auth from userinfo order by userid
        <!-- 모든 회원정보를 검색하고 userid 기준으로 정렬 -->
    </select>

</mapper>

UserinfoMapper.java

import java.util.List;
import xyz.itwill09.dto.Userinfo;

public interface UserinfoMapper {

    // 새로운 회원 정보를 userinfo 테이블에 삽입하는 메서드
    // => 파라미터로 전달된 Userinfo 객체의 필드를 SQL 쿼리에 사용하여 삽입
    // 반환값은 삽입된 행(row)의 개수
    int insertUserinfo(Userinfo userinfo);

    // 회원 정보를 수정하는 메서드
    // => 전달된 Userinfo 객체의 userid를 기준으로 나머지 필드값을 업데이트
    // 반환값은 수정된 행(row)의 개수
    int updateUserinfo(Userinfo userinfo);

    // 특정 userid에 해당하는 회원 정보를 삭제하는 메서드
    // => 전달된 userid에 해당하는 회원 정보를 userinfo 테이블에서 삭제
    // 반환값은 삭제된 행(row)의 개수
    int deleteUserinfo(String userid);

    // 특정 userid에 해당하는 회원 정보를 검색하는 메서드
    // => 해당 userid에 맞는 Userinfo 객체를 반환
    // 존재하지 않으면 null을 반환
    Userinfo selectUserinfo(String userid);

    // userinfo 테이블에 저장된 모든 회원 정보를 검색하는 메서드
    // => 여러 회원 정보를 List<Userinfo> 형식으로 반환
    List<Userinfo> selectUserinfoList();
}

UserinfoDAO, UserinfoDAOImpl

UserinfoDAO

import java.util.List;

import xyz.itwill09.dto.Userinfo;

public interface UserinfoDAO {
    int insertUserinfo(Userinfo userinfo);
    int updateUserinfo(Userinfo userinfo);
    int deleteUserinfo(String userid);
    Userinfo selectUserinfo(String userid);
    List<Userinfo> selectUserinfoList();
}

UserinfoDAOImpl

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import xyz.itwill09.dto.Userinfo;
import xyz.itwill09.mapper.UserinfoMapper;

// @Repository: Spring에서 해당 클래스가 데이터 접근 객체(DAO)임을 명시하는 어노테이션
// => 해당 클래스를 Spring Bean으로 등록하고, 스프링 컨테이너에서 관리
@Repository

// @RequiredArgsConstructor: Lombok 어노테이션으로, final로 선언된 필드에 대해 생성자를 자동으로 생성
// => 여기서는 final 필드인 SqlSession 객체를 생성자 주입 방식으로 자동 주입받기 위함
@RequiredArgsConstructor
public class UserinfoDAOImpl implements UserinfoDAO {

    // SqlSession: MyBatis에서 제공하는 객체로, 데이터베이스와의 상호작용(SQL 실행 등)을 수행
    // => SqlSession 객체는 MyBatis 매퍼와 상호작용하며 SQL 쿼리를 실행하고, 결과를 반환
    private final SqlSession sqlSession;

    // 회원정보를 데이터베이스에 삽입하는 메서드
    @Override
    public int insertUserinfo(Userinfo userinfo) {
        // getMapper: MyBatis가 제공하는 메서드로, UserinfoMapper 인터페이스의 구현체를 반환
        // => 반환된 Mapper를 통해 insertUserinfo 메서드를 호출하여 SQL 쿼리 실행
        return sqlSession.getMapper(UserinfoMapper.class).insertUserinfo(userinfo);
    }

    // 회원정보를 업데이트하는 메서드
    @Override
    public int updateUserinfo(Userinfo userinfo) {
        // UserinfoMapper를 통해 updateUserinfo 메서드를 호출하여 SQL 쿼리 실행
        return sqlSession.getMapper(UserinfoMapper.class).updateUserinfo(userinfo);
    }

    // 회원정보를 삭제하는 메서드
    @Override
    public int deleteUserinfo(String userid) {
        // UserinfoMapper를 통해 deleteUserinfo 메서드를 호출하여 SQL 쿼리 실행
        return sqlSession.getMapper(UserinfoMapper.class).deleteUserinfo(userid);
    }

    // 특정 회원정보를 조회하는 메서드
    @Override
    public Userinfo selectUserinfo(String userid) {
        // UserinfoMapper를 통해 selectUserinfo 메서드를 호출하여 SQL 쿼리 실행 및 회원정보 반환
        return sqlSession.getMapper(UserinfoMapper.class).selectUserinfo(userid);
    }

    // 모든 회원정보를 조회하는 메서드
    @Override
    public List<Userinfo> selectUserinfoList() {
        // UserinfoMapper를 통해 selectUserinfoList 메서드를 호출하여 SQL 쿼리 실행 및 회원정보 리스트 반환
        return sqlSession.getMapper(UserinfoMapper.class).selectUserinfoList();
    }
}

UserinfoService,UserinfoServiceImpl

UserinfoService

public interface UserinfoService {
    void addUserinfo(Userinfo userinfo);
    void modifyUserinfo(Userinfo userinfo);
    void removeUserinfo(String userid);
    Userinfo getUserinfo(String userid);
    List<Userinfo> getUserinfoList();
    Userinfo loginAuth(Userinfo userinfo);
}

UserinfoServiceImpl

  • 사용자로부터 입력받아 전달된 문자열(비밀번호)을 암호화 처리하는 방법
  1. jbcrypt 라이브러리를 프로젝트에 빌드 처리 - 메이븐 pom.xml
<!-- https://mvnrepository.com/artifact/org.mindrot/jbcrypt -->
<!-- => 암호화 처리 기능을 제공하기 위한 라이브러리 -->
<dependency>
	<groupId>org.mindrot</groupId>
	<artifactId>jbcrypt</artifactId>
	<version>0.4</version>
</dependency>
  1. BCrypt.hashpw(String password, String salt) 정적메소드를 호출하여 문자열(비밀번호)를 암호화 처리
    • 메소드의 매개변수에는 암호화 처리할 문자열과 첨가물의 문자열을 전달받아 암호화 처리
    • 첨가물에 의해 비밀번호가 다르게 암호화 처리
    • BCrypt.gensalt(int log_bounds): 매개변수에 첨가물의 길이를 전달받아 첨가물을 생성하여 반환하는 정적메소드 - 매개변수에 값을 전달하지 않으면 기본값으로 [0]으로 설정
  2. BCrypt.checkpw(String plainText, String hashed) 정적메소드를 호출하여 일반 문자열과 암호화 처리된 문자열을 비교하여 다른 경우 [false]를 반환하고 같은 경우 [true]를 반환받아 처리
@Service
@RequiredArgsConstructor
public class UserinfoServiceImpl implements UserinfoService {
    private final UserinfoDAO userinfoDAO;

    @Transactional
    @Override
    public void addUserinfo(Userinfo userinfo) {
        if(userinfoDAO.selectUserinfo(userinfo.getUserid()) != null) {
            //예외를 명확히 구분하여 예외 처리시 사용하기 위해 예외클래스를 작성해 예외 발생
            throw new ExistsUserinfoException("이미 사용중인 아이디를 입력 하였습니다.", userinfo);
        }

        //매개변수로 전달받은 회원정보의 비밀번호를 암호화 처리하여 필드값 변경
        String hashedPassword=BCrypt.hashpw(userinfo.getPassword(), BCrypt.gensalt());
        userinfo.setPassword(hashedPassword);

        userinfoDAO.insertUserinfo(userinfo);
    }

    @Transactional
    @Override
    public void modifyUserinfo(Userinfo userinfo) {
        if(userinfoDAO.selectUserinfo(userinfo.getUserid()) == null) {
            throw new UserinfoNotFoundException();
        }

        if(userinfo.getPassword() != null && !userinfo.getPassword().equals("")) {
            String hashedPassword=BCrypt.hashpw(userinfo.getPassword(), BCrypt.gensalt());
            userinfo.setPassword(hashedPassword);
        }

        userinfoDAO.updateUserinfo(userinfo);
    }

    @Transactional
    @Override
    public void removeUserinfo(String userid) {
        if(userinfoDAO.selectUserinfo(userid) == null) {
            throw new UserinfoNotFoundException();
        }

        userinfoDAO.deleteUserinfo(userid);
    }

    @Override
    public Userinfo getUserinfo(String userid) {
        Userinfo userinfo=userinfoDAO.selectUserinfo(userid);

        if(userinfo == null) {
            throw new UserinfoNotFoundException();
        }

        return userinfo;
    }

    @Override
    public List<Userinfo> getUserinfoList() {
        return userinfoDAO.selectUserinfoList();
    }

    @Override
    public Userinfo loginAuth(Userinfo userinfo) {
        Userinfo authUserinfo=userinfoDAO.selectUserinfo(userinfo.getUserid());

        if(authUserinfo == null) {//아이디 인증 실패
            throw new LoginAuthFailException("아이디의 회원정보가 존재하지 않습니다.", userinfo.getUserid());
        }

        if(!BCrypt.checkpw(userinfo.getPassword(), authUserinfo.getPassword())) {//비밀번호 인증 실패
            throw new LoginAuthFailException("아이디가 없거나 비밀번호가 맞지 않습니다.", userinfo.getUserid());
        }

        return authUserinfo;
    }
}

Exception(예외 처리, 직접)

  • Exception 클래스(RuntimeException 클래스) 상속받아 작성
  • 예외 처리는 RuntimeException을 상속받아 처리하는 예외 처리 방법, 상속 받지 않고 처리하는 예외 처리 방법 두 가지로 나뉜다.
  • RuntimeException 클래스를 상속받으면 try-catch나 extends 예외 처리를 하지 않아도 된다.

ExistsUserinfoException

  • 회원정보를 등록할 때 사용자로부터 입력받은 회원정보의 아이디가 기존 회원정보의 아이디와 중복될 경우 발생되어 예외를 생성하기 위한 클래스

  • 예외 처리에 필요한 값을 필드에 저장하여 사용

public class ExistsUserinfoException extends RuntimeException{
    private static final long serialVersionUID = 1L;

    // 예외 처리에 필요한 값을 저장하기 위한 필드 작성
    // => 사용자로부터 입력받은 회원정보를 필드에
    @Getter
    private Userinfo userinfo;

    public ExistsUserinfoException() {
    }
    public ExistsUserinfoException(String message, Userinfo userinfo) {
        super(message);
        this.userinfo = userinfo;
    }
}

UserinfoNotFoundException

  • 회원정보에 대한 변경, 삭제, 검색할 때 전달받은 아이디의 회원정보를 찾을 수 없는 경우 발생될 예외를 생성하기 위한 예외 클래스
public class UserinfoNotFoundException extends RuntimeException{
    public UserinfoNotFoundException() {
    }
    public UserinfoNotFoundException(String message) {
        super(message);
    }
}

LoginAuthFailException

  • 로그인 처리할 때 전달받은 아이디와 비밀번호에 대한 인증이 실패한 경우 예외를 생성하기 위한 예외 클래스
public class LoginAuthFailException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    @Getter
    private String userid;

    public LoginAuthFailException() {
        // TODO Auto-generated constructor stub
    }

    public LoginAuthFailException(String message, String userid) {
        super(message);
        this.userid=userid;
    }
}

BadRequestException

  • 비정상적인 요청인 경우 발생될 예외를 생성하기 위한 예외클래스
public class BadRequestException extends RuntimeException{
    private static final long serialVersionUID = 1L;

    public BadRequestException() {}
    public BadRequestException(String message) {
        super(message);
    }
}

UserinfoController.java

@Controller
@RequestMapping("/userinfo")
@RequiredArgsConstructor
public class UserinfoController {
    private final UserinfoService userinfoService;
    
    //회원정보를 입력받기 위한 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 관리자만 요청 가능한 페이지로 설정
    @RequestMapping(value = "/write", method = RequestMethod.GET)
    public String write(HttpSession session) {
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");

        //페이지를 요청한 사용자가 비로그인 사용자이거나 관리자가 아닌 경우 인위적 예외 발생
        if(loginUserinfo == null || loginUserinfo.getAuth() != 9) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        return "userinfo/user_write";
    }
    
    @RequestMapping(value = "/write", method = RequestMethod.POST)
    public String write(@ModelAttribute Userinfo userinfo, Model model) {
        //매개변수로 전달받은 회원정보의 아이디가 중복될 경우 ExistsUserinfoException 발생
        userinfoService.addUserinfo(userinfo);
        return "redirect:/userinfo/login";
    }
    
    //인증정보를 입력받기 위한 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "userinfo/user_login";
    }
    
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@ModelAttribute Userinfo userinfo, Model model, HttpSession session) {
        //매개변수로 전달받은 회원정보로 인증이 실패한 경우 LoginAuthFailException 발생
        Userinfo authUserinfo=userinfoService.loginAuth(userinfo);
        session.setAttribute("loginUserinfo", authUserinfo);
        return "userinfo/user_login";
    }
    
    //로그아웃 처리하고 로그인 페이지를 요청할 수 있는 URL 주소를 반환하는 요청 처리 메소드
    @RequestMapping("/logout")
    public String logout(HttpSession session) {
        //session.removeAttribute("loginUserinfo");
        session.invalidate();
        return "redirect:/userinfo/login";
    }
    
    @RequestMapping("/list")
    public String list(Model model, HttpSession session) {
        if(session.getAttribute("loginUserinfo") == null) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        model.addAttribute("userinfoList", userinfoService.getUserinfoList());
        return "userinfo/user_list";
    }
    
   	//아이디를 전달받아 USERINFO 테이블에 저장된 행을 검색하여 Request Scope 속성값으로 저장하고
    //회원정보를 출력하는 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 로그인 사용자만 요청 가능한 페이지로 설정
    @RequestMapping("/view")
    public String view(@RequestParam String userid, Model model, HttpSession session) {
        if(session.getAttribute("loginUserinfo") == null) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
        return "userinfo/user_view";
    }
    
    //아이디를 전달받아 USERINFO 테이블에 저장된 행을 검색하여 Request Scope 속성값으로 저장하고
    //회원정보를 변경하는 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 관리자만 요청 가능한 페이지로 설정
    @RequestMapping(value = "/modify", method = RequestMethod.GET)
    public String modify(@RequestParam String userid, Model model, HttpSession session) {
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
        //페이지를 요청한 사용자가 비로그인 사용자이거나 관리자가 아닌 경우 인위적 예외 발생
        if(loginUserinfo == null || loginUserinfo.getAuth() != 9) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
        return "userinfo/user_modify";
    }
    
    //변경할 회원정보를 전달받아 USERINFO 테이블에 저장된 행을 변경하고 회원정보 출력 페이지를
    //요청할 수 있는 URL 주소를 반환하는 요청 처리 메소드
    @RequestMapping(value = "/modify", method = RequestMethod.POST)
    public String modify(@ModelAttribute Userinfo userinfo, HttpSession session) {
        userinfoService.modifyUserinfo(userinfo);

        //로그인 사용자와 변경 처리된 사용자가 동일한 경우 세션에 저장된 권한 관련 속성값 변경
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
        if(loginUserinfo.getUserid().equals(userinfo.getUserid())) {
            session.setAttribute("loginUserinfo", userinfoService.getUserinfo(userinfo.getUserid()));
        }

        return "redirect:/userinfo/view?userid="+userinfo.getUserid();
    }
    
    //아이디를 전달받아 USERINFO 테이블에 저장된 행을 삭제하고 회원목록 출력 페이지를 요청할
    //수 있는 URL 주소를 반환하는 요청 처리 메소드
    // => 관리자만 요청 가능한 페이지로 설정
    @RequestMapping("/remove")
    public String remove(@RequestParam String userid, HttpSession session) {
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
        //페이지를 요청한 사용자가 비로그인 사용자이거나 관리자가 아닌 경우 인위적 예외 발생
        if(loginUserinfo == null || loginUserinfo.getAuth() != 9) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }

        userinfoService.removeUserinfo(userid);

        //로그인 사용자와 삭제 처리된 회원정보의 아이디가 동일한 경우 로그아웃 페이지로
        //리다이렉트 이동 처리
        if(loginUserinfo.getUserid().equals(userid)) {
            return "redirect:/userinfo/logout";
        }

        return "redirect:/userinfo/list";
    }

Views

WEB-INF/views/userinfo 폴더
→ user_write.jsp
→ user_login.jsp
→ user_error.jsp
→ user_list.jsp
→ user_view.jsp
→ user_modify.jsp

Exception(예외 처리, 따로 관리)

  • 위에는 예외가 발생하면 요청 처리 메소드에서 예외처리를 했다면 이번에는 예외 처리 메소드를 사용해 예외 처리 기능을 구현하고 인터셉터를 사용해 권한 처리 기능 구현을 할 것이다.
    => 요청 처리 메소드에서는 예외 처리 권한 관련 명령을 작성하지 않는다.

인터셉터(Interceptor)

  • 요청 처리 메소드의 명령 실행 전 또는 실행 후에 실행될 명령을 제공하는 기능

  • HandlerInterceptor 인터페이스를 상속받은 interceptor 클래스를 작성하여 Spring Bean Configuration File(servlet-context.xml)에 Spring Bean으로 등록하고 인터셉트로 사용되도록 환경설정

  • HandlerInterceptor 인터페이스의 기본 메소드(Default Mehod) 중 필요한 메소드만 오버라이드 선언하여 사용

  • 필터는 Front Controller의 이전 위치에 존재하여 실행되고 인터셉터는 Front Controller의 다음 위치에 존재하여 실행되어야한다.

  • 필터는 WAS 프로그램에 의해 실행되고 인터셉터는 Front Controller에 의해 관리되어야한다.

AdminAuthInterceptor

  • 관리자 관련 권한 처리를 위해 작성된 Interceptor 클래스

  • 요청 처리 메소드 호출 전에 비로그인 사용자 또는 관리자가 아닌 사용자가 페이지를 요청할 경우 인위적 예외 발생

public class AdminAuthInterceptor implements HandlerInterceptor {
    //요청 처리 메소드가 호출되기 전에 실행될 명령을 작성하기 위한 메소드
    // => false 반환 : 요청 처리 메소드 미호출, true : 요청 처리 메소드 호출
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        HttpSession session=request.getSession();

        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");

        if(loginUserinfo == null || loginUserinfo.getAuth() != 9) {
            //포워드 이동 및 리다이렉트 이동 가능
            //response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            //return false;

            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }

        return true;
    }

    //요청 처리 메소드가 호출된 후에 실행될 명령을 작성하기 위한 메소드
    // => 요청 처리 메소드의 반환값(ViewName)으로 뷰를 생성하기 전에 실행될 명령을 작성
    // => ModelAndView 객체를 제공받아 ModelAndView 객체에 저장된 정보를 변경할 때 사용
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }


    //요청 처리 메소드가 호출된 후에 실행될 명령을 작성하기 위한 메소드
    // => 요청 처리 메소드의 반환값(ViewName)으로 뷰를 생성한 후에 실행될 명령을 작성
    // => 뷰 관련 정보를 변경할 때 사용
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {
        // TODO Auto-generated method stub
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

LoginAuthInterceptor.java

  • 로그인 사용자 관련 권한 처리를 위해 작성된 Interceptor 클래스
    => 요청 처리 메소드 호출 전에 비로그인 사용자가 페이지를 요청할 경우 인위적 예외 발생
public class LoginAuthInterceptor implements HandlerInterceptor {
    //요청 처리 메소드가 호출되기 전에 실행될 명령을 작성하기 위한 메소드
    // => false 반환 : 요청 처리 메소드 미호출, true : 요청 처리 메소드 호출
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        HttpSession session=request.getSession();

        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");

        if(loginUserinfo == null) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        return true;
    }
}

servlet-context.xml

  • HandlerInterceptor 인터페이스를 상속받은 interceptor 클래스를 작성하여 Spring Bean Configuration File(servlet-context.xml)에 Spring Bean으로 등록
<beans:bean class="xyz.itwill09.util.AdminAuthInterceptor" id="adminAuthInterceptor"/>
<beans:bean class="xyz.itwill09.util.LoginAuthInterceptor" id="loginAuthInterceptor"/>
  • interceptors: interceptor 엘리먼트를 등록하기 위한 엘리먼트

  • interceptor: Front Controller로 Interceptor 객체의 메소드를 호출하기 위한 규칙을 제공하기 위한 엘리먼트

  • mapping: 인터셉터가 동작될 요청 페이지 경로를 제공하는 엘리먼트

    • path 속성: 요청 페이지 경로를 속성값으로 설정
    • mapping 엘리먼트의 path 속성값으로 [*] 기호를 사용해 요청 페이지 설정 가능
      • [*]: 폴더의 모든 페이지를 요청한 경우
      • [**]: 폴더 및 하위 폴더의 모든 페이지를 요청한 경우
  • ref: Froont Controller에 의해 사용될 Interceptor 객체를 제공하기 위한 엘리먼트

    • bean 속성: Interceptor 클래스의 Spring Bean 식별자(beanName)를 속성값으로 설정
  • exclude-mapping: 인터셉터가 동작되지 않는 요청 페이지 경로를 제공하는 엘리먼트

<interceptors>
	<interceptor>
		<mapping path="/userinfo/write"/>
		<mapping path="/userinfo/modify"/>
		<mapping path="/userinfo/remove"/>
		<beans:ref bean="adminAuthInterceptor"/>
	</interceptor>

	<interceptor>
		<mapping path="/userinfo/*"/>
		<exclude-mapping path="/userinfo/login"/>
		<beans:ref bean="loginAuthInterceptor"/>
	</interceptor>
</interceptors>

ExceptionController.java

  • @ControllerAdvice: 에외 처리 메소드만 작성된 Controller 클래스를 Spring Bean으로 등록하기 위한 어노테이션
    => 모든 Controller 클래스의 요청 처리 메소드에서 발생된 예외를 제공받아 예외 처리 가능

  • @ExceptionHandler: 예외 처리 기능을 제공하는 메소드를 설정하기 위한 어노테이션
    => Controller 클래스의 요청 처리 메소드에서 예외가 발생될 경우 예외 처리를 위해 Front Controller가 자동으로 호출하는 메소드 - 예외 처리 메소드(Spring AOP 기능 사용)

    • 예외 처리 메소드에 매개변수를 작성하면 예외 처리에 필요한 객체를 Front Controller로부터 제공받아 사용할 수 있으며 클라이언트에게 응답할 뷰의 뷰이름 반환 - 리다이렉트 이동 가능

    • value 속성: 예외 처리하기 위한 클래스의 Class 객체를 속성값으로 설정
      => value 속성외에 다른 속성이 없는 경우 속성값만 설정 가능
      => 생략 가능

@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(value = BadRequestException.class)
    public String badRequestException() {
        return "userinfo/user_error";
    }

    @ExceptionHandler(ExistsUserinfoException.class)
    public String existsUserinfoException(ExistsUserinfoException exception, Model model) {
        model.addAttribute("message", exception.getMessage());
        model.addAttribute("userinfo", exception.getUserinfo());
        return "userinfo/user_write";
    }

    @ExceptionHandler(LoginAuthFailException.class)
    public String loginAuthFailException(LoginAuthFailException exception, Model model) {
        model.addAttribute("message", exception.getMessage());
        model.addAttribute("userid", exception.getUserid());
        return "userinfo/user_login";
    }

    @ExceptionHandler(UserinfoNotFoundException.class)
    public String userinfoNotFoundException() {
        return "userinfo/user_error";
    }

    // 모든 예외를 처리하는 클래스
    /*
    @ExceptionHandler(Exception.class)
    public String exception() {
        return "userinfo/user_error";
    }
    */
}

UserinfoController.java

  • 위에서 많은 과정을 한 이유는 위 과정처럼 세분화하여 진행할 경우 UserinfoController에서 요청 처리 메소드에 예외처리를 하였지만 예외처리 하지 않고 Front Controller에서 자동으로 예외를 인식하여 처리할 수 있다.
@Controller
@RequestMapping("/userinfo")
@RequiredArgsConstructor
public class UserinfoController {
    private final UserinfoService userinfoService;
    
    //예외 처리 메소드를 사용해 예외 처리 기능을 구현하고 인터셉터를 사용해 권한 관련 처리 기능 구현
    // => 요청 처리 메소드에서는 예외 처리 및 권한 관련 명령 미작성
    /*
    @RequestMapping(value = "/write", method = RequestMethod.GET)
    public String write(HttpSession session) {
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");

        //페이지를 요청한 사용자가 비로그인 사용자이거나 관리자가 아닌 경우 인위적 예외 발생
        if(loginUserinfo == null || loginUserinfo.getAuth() != 9) {
            throw new BadRequestException("비정상적인 방법으로 페이지를 요청 하였습니다.");
        }
        return "userinfo/user_write";
    }
    */
    @RequestMapping(value = "/write", method = RequestMethod.GET)
    public String write() {
        return "userinfo/user_write";
    }
    
    //회원정보를 전달받아 USERINFO 테이블의 행으로 삽입하고 로그인 페이지를 요청할 수 있는
    //URL 주소를 반환하는 요청 처리 메소드
    @RequestMapping(value = "/write", method = RequestMethod.POST)
    public String write(@ModelAttribute Userinfo userinfo, Model model) {
        //매개변수로 전달받은 회원정보의 아이디가 중복될 경우 ExistsUserinfoException 발생
        userinfoService.addUserinfo(userinfo);
        return "redirect:/userinfo/login";
    }

    //인증정보를 입력받기 위한 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "userinfo/user_login";
    }
    
    //인증정보를 전달받아 USERINFO 테이블에 저장된 행을 검색하여 로그인 처리하고 환영메세지를
    //출력하는 JSP 문서의 뷰이름 반환하는 요청 처리 메소드
    // => 로그인 처리 : 인증 성공시 세션에 권한 관련 정보(회원정보)를 속성값으로 저장
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@ModelAttribute Userinfo userinfo, Model model, HttpSession session) {
        //매개변수로 전달받은 회원정보로 인증이 실패한 경우 LoginAuthFailException 발생
        Userinfo authUserinfo=userinfoService.loginAuth(userinfo);
        session.setAttribute("loginUserinfo", authUserinfo);
        return "userinfo/user_login";
    }

    //로그아웃 처리하고 로그인 페이지를 요청할 수 있는 URL 주소를 반환하는 요청 처리 메소드
    @RequestMapping("/logout")
    public String logout(HttpSession session) {
        //session.removeAttribute("loginUserinfo");
        session.invalidate();
        return "redirect:/userinfo/login";
    }
    
    //USERINFO 테이블에 저장된 모든 행을 검색하여 Request Scope 속성값으로 저장하고 회원목록을
    //출력하는 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 로그인 사용자만 요청 가능한 페이지로 설정
    @RequestMapping("/list")
    public String list(Model model) {
        model.addAttribute("userinfoList", userinfoService.getUserinfoList());
        return "userinfo/user_list";
    }
    
    //아이디를 전달받아 USERINFO 테이블에 저장된 행을 검색하여 Request Scope 속성값으로 저장하고
    //회원정보를 출력하는 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 로그인 사용자만 요청 가능한 페이지로 설정
    @RequestMapping("/view")
    public String view(@RequestParam String userid, Model model) {
        model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
        return "userinfo/user_view";
    }
    
    //아이디를 전달받아 USERINFO 테이블에 저장된 행을 검색하여 Request Scope 속성값으로 저장하고
    //회원정보를 변경하는 JSP 문서의 뷰이름을 반환하는 요청 처리 메소드
    // => 관리자만 요청 가능한 페이지로 설정
    @RequestMapping(value = "/modify", method = RequestMethod.GET)
    public String modify(@RequestParam String userid, Model model) {
        model.addAttribute("userinfo", userinfoService.getUserinfo(userid));
        return "userinfo/user_modify";
    }


    //변경할 회원정보를 전달받아 USERINFO 테이블에 저장된 행을 변경하고 회원정보 출력 페이지를
    //요청할 수 있는 URL 주소를 반환하는 요청 처리 메소드
    @RequestMapping(value = "/modify", method = RequestMethod.POST)
    public String modify(@ModelAttribute Userinfo userinfo, HttpSession session) {
        userinfoService.modifyUserinfo(userinfo);

        //로그인 사용자와 변경 처리된 사용자가 동일한 경우 세션에 저장된 권한 관련 속성값 변경
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");
        if(loginUserinfo.getUserid().equals(userinfo.getUserid())) {
            session.setAttribute("loginUserinfo", userinfoService.getUserinfo(userinfo.getUserid()));
        }

        return "redirect:/userinfo/view?userid="+userinfo.getUserid();
    }
    
    //아이디를 전달받아 USERINFO 테이블에 저장된 행을 삭제하고 회원목록 출력 페이지를 요청할
    //수 있는 URL 주소를 반환하는 요청 처리 메소드
    // => 관리자만 요청 가능한 페이지로 설정
    @RequestMapping("/remove")
    public String remove(@RequestParam String userid, HttpSession session) {
        Userinfo loginUserinfo=(Userinfo)session.getAttribute("loginUserinfo");

        userinfoService.removeUserinfo(userid);

        //로그인 사용자와 삭제 처리된 회원정보의 아이디가 동일한 경우 로그아웃 페이지로
        //리다이렉트 이동 처리
        if(loginUserinfo.getUserid().equals(userid)) {
            return "redirect:/userinfo/logout";
        }

        return "redirect:/userinfo/list";
    }
}
  • /userinfo/write 부분만 봐도 요청 처리 메소드에서 예외 처리를 하지 않으니 코드가 확 줄은 것을 알 수 있다.

  • 이 방법을 사용할 것인지는 선택해야될 거 같다. 너무 많이 줄인다고 좋은 건 아니라고 생각한다.

profile
최선을 다해 꾸준히 노력하는 개발자 망고입니당 :D

0개의 댓글