진행 순서는 다음과 같다.
AdminIndexAction -> main.jsp(로그인 폼) ->adminLoginProc.jsp -> AdminLoginAction (로그인 액션) -> adminLoginProc.jsp
-> 로그인 후 상품 목록 adminLoginProc.jsp
AdminIndexAction
package net.macaronics.web.admin.controller; import java.io.IOException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.RSAPublicKeySpec; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import net.macaronics.web.controll.action.Action; public class AdminIndexAction implements Action { public static final int KEY_SIZE = 1024; @Override public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String url = "admin/main.jsp"; try { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(KEY_SIZE); KeyPair keyPair = generator.genKeyPair(); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); HttpSession session = request.getSession(); // 세션에 공개키의 문자열을 키로하여 개인키를 저장한다. session.setAttribute("__rsaPrivateKey__", privateKey); // 공개키를 문자열로 변환하여 JavaScript RSA 라이브러리 넘겨준다. RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class); String publicKeyModulus = publicSpec.getModulus().toString(16); String publicKeyExponent = publicSpec.getPublicExponent().toString(16); request.setAttribute("publicKeyModulus", publicKeyModulus); request.setAttribute("publicKeyExponent", publicKeyExponent); request.getRequestDispatcher(url).forward(request, response); } catch (Exception ex) { throw new ServletException(ex.getMessage(), ex); } } }
main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib uri="http://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project/Owasp.CsrfGuard.tld" prefix="csrf" %> <!DOCTYPE html> <html> <head> <title>Admin Login</title> <!-- Bootstrap --> <link href="../Bootstrap-Admin/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen"> <link href="../Bootstrap-Admin/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen"> <link href="../Bootstrap-Admin/assets/styles.css" rel="stylesheet" media="screen"> <!-- HTML5 shim, for IE6-8 support of HTML5 elements --> <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <script src="../Bootstrap-Admin/vendors/modernizr-2.6.2-respond-1.1.0.min.js"></script> <!-- script 태그에서 가져오는 자바스크립트 파일의 순서에 주의해야한다! 순서가 틀릴경우 자바스크립트 오류가 발생한다. --> <script type="text/javascript" src="${request.getContextPath() }/js/rsa/jsbn.js"></script> <script type="text/javascript" src="${request.getContextPath() }/js/rsa/rsa.js"></script> <script type="text/javascript" src="${request.getContextPath() }/js/rsa/prng4.js"></script> <script type="text/javascript" src="${request.getContextPath() }/js/rsa/rng.js"></script> <script type="text/javascript" src="${request.getContextPath() }/js/login.js"></script> </head> <body id="login"> <div class="container"> <form class="form-signin"> <c:choose> <c:when test='${param.message=="error" }'> <h3 class="form-signin-heading text-center"> <small style="color:red;">아이디와 비밀번호가 일치하지 않습니다.</small> </h3> </c:when> <c:otherwise> <h2 class="form-signin-heading text-center"> 관리자 로그인 </h2> </c:otherwise> </c:choose> <input type="text" class="input-block-level" placeholder="아이디" id="username" value="admin"> <input type="password" class="input-block-level" placeholder="비밀번호" id="password" value="1111"> <label class="checkbox"> </label> <div class="text-center"><button class="btn btn-large btn-primary" type="button" onclick="validateEncryptedForm(); return false;">로그인</button></div> </form> <input type="hidden" id="rsaPublicKeyModulus" value="${publicKeyModulus}" /> <input type="hidden" id="rsaPublicKeyExponent" value="${publicKeyExponent}" /> <csrf:form id="securedLoginForm" name="securedLoginForm" action="${request.getContextPath() }/admin/adminLoginProc.jsp" method="post" style="display: none;"> <input type="hidden" name="securedUsername" id="securedUsername" value="" /> <input type="hidden" name="securedPassword" id="securedPassword" value="" /> </csrf:form> </div> <!-- /container --> <script src="../Bootstrap-Admin/vendors/jquery-1.9.1.min.js"></script> <script src="../Bootstrap-Admin/bootstrap/js/bootstrap.min.js"></script> </body> </html>
AdminLoginAction
package net.macaronics.web.admin.controller; import java.io.IOException; import java.math.BigInteger; import java.security.PrivateKey; import javax.crypto.Cipher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import net.macaronics.web.admin.controller.dao.AdminDAO; import net.macaronics.web.controll.action.Action; import net.macaronics.web.dao.MemberDAO; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public class AdminLoginAction { final static Logger logger =LogManager.getLogger(AdminLoginAction.class); // 암호화된 비밀번호를 복호화 한다. public boolean processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String securedUsername = request.getParameter("securedUsername"); String securedPassword = request.getParameter("securedPassword"); // 파라미터로 넘어온 값 logger.info("securedUsername: {} ",securedUsername ); logger.info("securedPassword: {}", securedPassword); HttpSession session = request.getSession(); PrivateKey privateKey = (PrivateKey) session.getAttribute("__rsaPrivateKey__"); session.removeAttribute("__rsaPrivateKey__"); // 키의 재사용을 막는다. 항상 새로운 키를 받도록 강제. if (privateKey == null) { throw new RuntimeException("암호화 비밀키 정보를 찾을 수 없습니다."); } try { String username = decryptRsa(privateKey, securedUsername); String password = decryptRsa(privateKey, securedPassword); request.setAttribute("username", username); request.setAttribute("password", password); // 파라미터로 넘어온 setAttribute 로 가져오기 값 logger.info("request.getAttribute(): {}, {} ",username, password); return confirm(username,password); } catch (Exception ex) { throw new ServletException(ex.getMessage(), ex); } } //비밀번호 아이디 확인 private boolean confirm(String id , String pwd){ AdminDAO adminDAO= AdminDAO.getInstnce(); return adminDAO.workerCheck(id, pwd); } private String decryptRsa(PrivateKey privateKey, String securedValue) throws Exception { System.out.println("will decrypt : " + securedValue); Cipher cipher = Cipher.getInstance("RSA"); byte[] encryptedBytes = hexToByteArray(securedValue); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); String decryptedValue = new String(decryptedBytes, "utf-8"); // 문자 인코딩 주의. return decryptedValue; } // 16진 문자열을 byte 배열로 변환한다. public static byte[] hexToByteArray(String hex) { if (hex == null || hex.length() % 2 != 0) { return new byte[]{}; } byte[] bytes = new byte[hex.length() / 2]; for (int i = 0; i < hex.length(); i += 2) { byte value = (byte)Integer.parseInt(hex.substring(i, i + 2), 16); bytes[(int) Math.floor(i / 2)] = value; } return bytes; } // BigInteger를 사용해 hex를 byte[] 로 바꿀 경우 음수 영역의 값을 제대로 변환하지 못하는 문제가 있다. @Deprecated public static byte[] hexToByteArrayBI(String hexString) { return new BigInteger(hexString, 16).toByteArray(); } public static String base64Encode(byte[] data) throws Exception { BASE64Encoder encoder = new BASE64Encoder(); String encoded = encoder.encode(data); return encoded; } public static byte[] base64Decode(String encryptedData) throws Exception { BASE64Decoder decoder = new BASE64Decoder(); byte[] decoded = decoder.decodeBuffer(encryptedData); return decoded; } }
adminLoginProc.jsp
<%@page import="net.macaronics.web.admin.controller.AdminLoginAction"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% AdminLoginAction login =new AdminLoginAction(); String url="MacaronicsServlet?command=admin_login_form&message=error"; boolean result =login.processRequest(request, response); System.out.print("반환 값 : " + result); if(result){ System.out.print(request.getAttribute("username")); //로그인 성공 session.setAttribute("wokerid", request.getAttribute("username")); // url="MacaronicsServlet?command=admin_product_list"; url="adminProduct/admin_product_list.jsp"; } response.sendRedirect(url); %>
DAO
package net.macaronics.web.admin.controller.dao; import java.util.HashMap; import java.util.Map; import org.apache.ibatis.session.SqlSession; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import config.MybatisService; public class AdminDAO { private static final Logger logger =LogManager.getLogger(AdminDAO.class); private SqlSession sqlSession; private static AdminDAO instance ; private AdminDAO(){ } public static AdminDAO getInstnce(){ if(instance ==null){ instance =new AdminDAO(); } return instance; } //비밀번호 체크 public boolean workerCheck(String id, String pwd){ int result=0; try{ sqlSession=MybatisService.getFactory().openSession(); Map<String, Object> map=new HashMap<>(); map.put("id", id); map.put("pwd", pwd); result=sqlSession.selectOne("admin.workerCheck",map); }catch(Exception e){ e.printStackTrace(); }finally{ MybatisService.sessionClose(sqlSession); } //0보다 크면 로그인 성공 return result >0 ? true :false; } }
Mybatis
admin.xml
<?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="admin"> <!-- id="태그의 식별자" resultType="sql 명령어의 리턴타입(레코드의 자료형)" 샵{변수} => 입력매개변수 --> <select id="workerCheck" resultType="int"> select count(*) from TBL_ADMIN where id =#{id} and pwd=#{pwd} </select> </mapper>
로그인 성공시
제작 : macaronics.net - Developer Jun Ho Choi
소스 : https://github.com/braverokmc79/jsp_sin
${request.getContextPath() } 처리를 안한 부분이 있으므로
루트 설정( http://macaronics.net/index.php/m01/jsp/view/1352) 및 server.xml 에서 DB 컨넥션 설정은 필수 설정이다.
댓글 ( 0)
댓글 남기기