Spring & Springboot/올인원 스프링 프레임워크

전자 도서관 프로젝트 - 관리자 로그인 처리 기능 구현

YJ_ma 2023. 10. 10. 16:32

관리자 로그인 실습 준비와 화면 구현

앞서 관리자 회원가입 기능을 구현했다. 이번에는 관리자 로그인 기능을 구현해본다.

① 관리자 로그인 화면을 구현한다.

② 관리자 로그인 인증을 처리하기 위한 컨트롤러, 서비스, DAO을 구현한다.

③ 쿠키를 이용한 로그인 상태 유지 및 로그아웃 기능을 구현한다.

④ 세션을 이용한 로그인 상태 유지 및 로그아웃 기능을 구현한다.

⑤ 일반 관리자를 로그인할 수 있도록 승인한다.

⑥ 최고 관리자와 일반 관리자를 구분해서 메뉴를 구성한다.

 

최고 관리자와 일반 관리자

· 최고 관리자(super admin)은 회원가입 직후 바로 로그인이 가능하지만, 일반 관리자는 회원 가입 후 최고 관리자의 승인이  완료되어야 로그인이 가능하다.

 

일반 관리자 추가하기

· 일반 관리자(admin1)을 추가해본다.

· 일반 관리자(admin1)는 a_m_approval값이 0으로 최고 관리자(super admin)의 승인 전이다.

따라서 회원가입은 했으나 아직 로그인이 불가능한 상태이다.

admin1(일반 관리자) 생성
생성 결과

관리자 로그인 화면 구현하기

· 관리자 로그인에 대한 링크는 nav.jsp에 있다.  

· 경로 : webapp\WEB-INF\views\admin\include\nav.jsp

로그인 클릭 시 발생하는 요청 정보

서버에서 클라이언트의 /loginForm 요청을 처리하기 위해 AdminMemberController에 메서드를 만들고 URL를 매핑한다.

다음과 같이 AdminMemberController에 loginForm()을 추가해본다.

· loginForm()은 /loginForm 요청이 발생하면 [admin/member] 폴더에 있는 login_form.jsp 파일을 이용해서 뷰를 만들고 클라이언트에 응답을 할 뿐 서비스 또는 DAO를 이용하지 않는 단순한 구조이다. 

■ AdminMemberController 코드

// 로그인
@GetMapping("/loginForm")
public String loginForm() {
    System.out.println("[AdminMemberController] loginForm()");

    String nextPage = "admin/member/login_form";

    return nextPage;
}

로그인 화면

· login_form.jsp는 관리자 아이디(a_m_id)와 비밀번호(a_m_pw)를 입력할 수 있고, <login> 버튼을 클릭하면 <form>의 action에 명시한 /admin/member/loginConfirm을 이용해서 서버로 로그인 인증을 요청한다.

■ login_form.jsp 코드

 

관리자 로그인 처리 기능 구현

컨트롤러 구현하기

클라이언트의 /loginConfirm 요청을 처리하기 위해 AdminMemberController에 login Confirm()을 선언한다.

· /loginConfirm 요청을 loginConfirm()에 매핑하고 AdminMemberVo를 파라미터로 받는다.

· AdminMemberVo에는 관리자가 입력한 아이디(a_m_id)와 비밀번호(a_m_pw) 정보가 저장되어 있다.

· loginConfirm()에서 관리자 로그인 인증에 성공하면 [admin/member] 폴더에 있는 login_ok.jsp를 이용해서 View를 만들고 클라이언트에 '관리자 로그인 성공'을 응답한다.

· AdminMemberService에 관리자 로그인 인증 업무를 지시하기 위해 loginConfirm()을 호출한다. 이때 매개변수로 받은 adminMemberVo를 함께 전달한다.

· 만약 adminMemberService가 null을 반환하면, 관리자 로그인에 실패한 경우로 [admin/member] 폴더에 있는 login_ng.jsp를 이용해서 View를 만들고 클라이언트에게 '관리자 로그인 실패'를 응답한다.

■ AdminMemberController 코드

// 로그인 확인
@PostMapping("/loginConfirm")
public String loginConfirm(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberController] loginConfirm()");

    String nextPage = "admin/member/login_ok";

    AdminMemberVo loginedAdminMemberVo = 
            adminMemberService.loginConfirm(adminMemberVo);

    if (loginedAdminMemberVo == null) {
        nextPage = "admin/member/login_ng";
    }

    return nextPage;
}

 

서비스 구현하기

관리자 로그인 인증을 하기 위해 위에 컨트롤러에서 서비스의 loginConfirm()을 호출한다.

따라서 이에 따른 AdminMemberService에 loginConfirm()을 선언해준다.

· 서비스는 로그인 인증을 처리하기 위해 관리자가 입력한 정보(아이디, 비밀번호)와 일치하는 관리자가 데이터베이스(tbl_admin_member 테이블)에 있는지 확인해야 한다.

따라서 서비스는 DAO를 이용해야 하며, AdminMemberDao의 selectAdmin()을 이용해야 한다.

· selectAdmin()이 반환되는 회원정보는 loginedAdminMemberVo에 담긴다.

만약, loginedAdminMemberVo가 null이면 관리자가 입력한 정보와 일치하는 관리자가 없다.

■ AdminMemberService 코드

public AdminMemberVo loginConfirm(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberService] loginConfirm()");

    AdminMemberVo loginedAdminMemberVo =
            adminMemberDao.selectAdmin(adminMemberVo);

    if (loginedAdminMemberVo != null)
        System.out.println("AdminMemberService] ADMIN MEMBER LOGIN SUCCESS!!");
    else
        System.out.println("AdminMemberService] ADMIN MEMBER LOGIN FAIL!!");

    return loginedAdminMemberVo;
}

 

DAO 구현하기

로그인하려는 관리자가 입력한 정보(아이디, 비밀번호)와 일치하는 관리자를 tbl_admin_member 테이블에서 조회하기 위한 selectAdmin()을 AdminMemberDao에 선언한다.

AdminMemberDao 코드

public AdminMemberVo selectAdmin(AdminMemberVo adminMemberVo) {
    System.out.println("[AdminMemberDao] selectAdmin()");

    String sql = "SELECT * FROM tbl_admin_member " 
            + "WHERE a_m_id = ? AND a_m_approval > 0";

    List<AdminMemberVo> adminMemberVos = new ArrayList<AdminMemberVo>();

    try {
        adminMemberVos = jdbcTemplate.query(sql,  new RowMapper<AdminMemberVo>() {
            @Override
            public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
                AdminMemberVo adminMemberVo = new AdminMemberVo();

                adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
                adminMemberVo.setA_m_approval(rs.getInt("a_m_approval"));
                adminMemberVo.setA_m_id(rs.getString("a_m_id"));
                adminMemberVo.setA_m_pw(rs.getString("a_m_pw"));
                adminMemberVo.setA_m_name(rs.getString("a_m_name"));
                adminMemberVo.setA_m_gender(rs.getString("a_m_gender"));
                adminMemberVo.setA_m_part(rs.getString("a_m_part"));
                adminMemberVo.setA_m_position(rs.getString("a_m_position"));
                adminMemberVo.setA_m_mail(rs.getString("a_m_mail"));
                adminMemberVo.setA_m_phone(rs.getString("a_m_phone"));
                adminMemberVo.setA_m_reg_date(rs.getString("a_m_reg_date"));
                adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));

                return adminMemberVo;
            }
        }, adminMemberVo.getA_m_id());

        if (!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
            adminMemberVos.clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;
}

[코드 설명]

· selectAdmin()은 서비스에서 넘겨준 adminMemberVo를 이용해서 아이디와 비밀번호가 일치하는 관리자를 tbl_admin_member 테이블에서 조회하고, 결과를 AdminMemberVo 타입으로 서비스에 반환한다.

· 조회한 회원 정보를 저장하는 List로 일치하는 회원이 검색된 경우 adminMemberVos의 길이는 1이고, 그렇지 않은 경우 0이 된다.

String sql = "SELECT * FROM tbl_admin_member " + "WHERE a_m_id = ? AND a_m_approval > 0";

다음 코드는 tbl_admin_member 테이블에서 관리자를 조회하는 쿼리로, 관리자가 입력한 아이디가 a_m_id(관리자 아이디)와 일치하고 a_m_approval(관리자 승인)이 완료(1)된 회원을 조회한다는 내용이다.

 

adminMemberVos = jdbcTemplate.query(sql,  new RowMapper<AdminMemberVo>() {
    @Override
    public AdminMemberVo mapRow(ResultSet rs, int rowNum) throws SQLException {
        AdminMemberVo adminMemberVo = new AdminMemberVo();
		....
        return adminMemberVo;
    }
}, adminMemberVo.getA_m_id());

데이터베이스에 저장되어 있느 비밀번호는 암호화된 문자열로 관리자가 입력한 비밀번호와 비교할 수 없다.

따라서 JdbcTemplate의 query()를 이용해서 회원을 조회하는 쿼리를 실행하고 결과를 adminMemberVos에 할당한다.

· query()는 3개의 파라미터를 받는다.

query(쿼리문, RowMapper 인터페이스를 구현한 익명 클래스, 관리자가 입력한 아이디)

   - RowMapper 인터페이스를 구현한 클래스: 데이터베이스의 row(행)을 어딘가 매핑하는 역할

   - row: VO(AdminMemberVo) 객체에 매핑

 

public AdminMemberVo mapRow(ResultSet rs, int rowNum)

RowMapper를 구현한 익명 클래스는 RowMapper의 추상 메서드를 구현해야 하는 의무가 있는데, 바로 mapRow()이다.

· mapRow() : ResultSet행의 개수를 파라미터로 받는다.

· rs : 데이터베이스에서 조회된 데이터셋이 저장

· rowNum : 데이터셋의 현재 행 번호가 저장

따라서 mapRow()에서 조회된 데이터를 Java 데이터 형식으로 변경하는데 사용되는 데이터 형식의 객체가 AdminMemberVo이다.

 

AdminMemberVo adminMemberVo = new AdminMemberVo();

adminMemberVo.setA_m_no(rs.getInt("a_m_no"));
...
adminMemberVo.setA_m_mod_date(rs.getString("a_m_mod_date"));

return adminMemberVo;

AdminMemberVo 객체를 생성하고 rs에 있는 데이터를 AdminMemberVo 객체의 setter를 이용해서 저장한다.

모든 데이터의 저장(set)이 끝나면 AdminMemberVo 객체를 반환한다.

반환된 객체는 List 타입으로 저장된 후 adminMemberVos에 할당된다.

 

if (!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
    adminMemberVos.clear();

다음은 관리자가 입력한 비밀번호와 RowMapper에 의해서 VO에 매핑된 비밀번호를 비교하는 코드이다.

· PasswordEncoder의 matches()

   - 암호화된 문자열을 비교하는 메서드

   - 암호화된 비밀번호를 복호화해서 관리자가 입력한 비밀번호와 비교 연산하고 결과를 반환

형식 : matches(암호화되지 않은 비밀번호(관리자가 입력한 비밀번호), 암호화된 비밀번호(데이터베이스의 비밀번호))

비교 결과가 false라면 관리자가 입력한 비밀번호가 일치하지 않은 것으로 판단해 조회된 데이터(adminMemberVo)는 삭제(adminMemberVos.clear())한다.

 

return adminMemberVos.size() > 0 ? adminMemberVos.get(0) : null;

adminMemberVos의 길이가 0보다 크면 로그인 인증에 성공한 경우로 조회된 관리자 정보를 서비스에 반환한다.

   - 로그인 성공하면 adminMemberVos의 길이는 항상 1이다.

adminMemberVos의 길이가 0이하라면 로그인 인증에 실패한 경우로 서비스에 null을 반환한다.

 

관리자 로그인 결과 확인

1. 최고 관리자 로그인 및 결과 확인

콘솔 창 로그 확인

관리자 홈 > 로그인 양식 > 로그인 인증(컨트롤러) > 로그인 인증(서비스) > 관리자 조회 > 조회에 따른 로그인 인증 결과 출력

 

2. 일반 관리자 로그인 및 결과 확인

일반 관리자는 최고 관리자 승인 전에는 로그인이 불가능한 것을 확인했다.