23. 스프링 회원가입 만들기 / 암호화 기능 추가
안녕하세요 MelonPeach입니다.
오늘은 암호화 기능에대해 포스팅하겠습니다.
사용자의 개인정보를 특수문자, 영어 숫자등으로 암호화를 하여 데이터베이스에
저장을 하게되면 개인정보의 피해를 막을수 있겠지요.
1. pom.xml 작성
스프링 시큐리티를 pom.xml에 적용시킵니다.(core, web, config)
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
2. spring-security.xml 작성
src -> main -> webapp -> WEB-INF -> spring 경로에
spring-security.xml파일을 하나 만들고 코드를 추가해줍니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
</beans:beans>
3. web.xml 작성
spring-security.xml을 읽을수 있게 설정해줍니다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/appServlet/servlet-context.xml
/WEB-INF/spring/spring-security.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 한글 인코딩 Start -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 한글 인코딩 End -->
</web-app>
4. MemberController.java 작성
암호화 기능을 사용할수 있게 BCryptPasswordEncoder를 추가해줍니다.
회원가입 요청이 들어오면 비밀번호를 암호화하여 vo에 다시 넣어줍니다. 그리고 회원가입 서비스를 실행합니다.
회원가입을 암호화를 하여 했기때문에 로그인을 할 때에는 입력된 비밀번호와 조회한 비밀번호가 맞아야하는데요.
먼저 비밀번호를 조회해야겠지요. service.login서비스를 실행하여 login.getUserPass를 가저올 수 있도록합니다.
pwdEncoder.matches(입력된 비밀번호(), 암호화된 비밀번호()) 비교를 해줍니다. 맞으면 true 틀리면 false를 반환하여 로그인을 진행합니다.
회원정보 수정은 memberUpdateView.jsp 에서 ajax를 이용하여 passChk() 요청을 해주기 때문에 간단한 서비스만 실행해줍니다. 수정을 완료하면 세션을 끊어줍니다.
회원탈퇴도 회원수정과 동일하게 passChk()요청에서 비밀번호를 비교해주기때문에 서비스만 실행시켜줍니다.
package kr.co.controller;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import kr.co.service.MemberService;
import kr.co.vo.MemberVO;
@Controller
@RequestMapping("/member/*")
public class MemberController {
private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
@Inject
MemberService service;
@Inject
BCryptPasswordEncoder pwdEncoder;
// 회원가입 get
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void getRegister() throws Exception {
logger.info("get register");
}
// 회원가입 post
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String postRegister(MemberVO vo) throws Exception {
logger.info("post register");
int result = service.idChk(vo);
try {
if(result == 1) {
return "/member/register";
}else if(result == 0) {
String inputPass = vo.getUserPass();
String pwd = pwdEncoder.encode(inputPass);
vo.setUserPass(pwd);
service.register(vo);
}
// 요기에서~ 입력된 아이디가 존재한다면 -> 다시 회원가입 페이지로 돌아가기
// 존재하지 않는다면 -> register
} catch (Exception e) {
throw new RuntimeException();
}
return "redirect:/";
}
// 로그인 post
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception{
logger.info("post login");
session.getAttribute("member");
MemberVO login = service.login(vo);
boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
if(login != null && pwdMatch == true) {
session.setAttribute("member", login);
} else {
session.setAttribute("member", null);
rttr.addFlashAttribute("msg", false);
}
return "redirect:/";
}
// 로그아웃 post
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) throws Exception{
session.invalidate();
return "redirect:/";
}
// 회원정보 수정 get
@RequestMapping(value="/memberUpdateView", method = RequestMethod.GET)
public String registerUpdateView() throws Exception{
return "member/memberUpdateView";
}
// 회원정보 수정 post
@RequestMapping(value="/memberUpdate", method = RequestMethod.POST)
public String registerUpdate(MemberVO vo, HttpSession session) throws Exception{
/* MemberVO login = service.login(vo);
boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
if(pwdMatch) {
service.memberUpdate(vo);
session.invalidate();
}else {
return "member/memberUpdateView";
}*/
service.memberUpdate(vo);
session.invalidate();
return "redirect:/";
}
// 회원 탈퇴 get
@RequestMapping(value="/memberDeleteView", method = RequestMethod.GET)
public String memberDeleteView() throws Exception{
return "member/memberDeleteView";
}
// 회원 탈퇴 post
@RequestMapping(value="/memberDelete", method = RequestMethod.POST)
public String memberDelete(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception{
service.memberDelete(vo);
session.invalidate();
return "redirect:/";
}
// 패스워드 체크
@ResponseBody
@RequestMapping(value="/passChk", method = RequestMethod.POST)
public boolean passChk(MemberVO vo) throws Exception {
MemberVO login = service.login(vo);
boolean pwdChk = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
return pwdChk;
}
// 아이디 중복 체크
@ResponseBody
@RequestMapping(value="/idChk", method = RequestMethod.POST)
public int idChk(MemberVO vo) throws Exception {
int result = service.idChk(vo);
return result;
}
}
5. memberUpdateView.jsp 작성
ajax를 이용하여 passChk로 요청을 보내고 passChk에서의 return값을 success: function(data) 파라미터로 넘겨줍니다.
data가 true면 회원수정. 아니면 비밀번호가 틀렸다는 메세지를 보여줍니다.
form태그에 아이디를 넣어줍니다(form id="updateForm")그리고 안에 있던 버튼을 밖으로 꺼내주고 type을 button으로 수정해줍니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>회원가입</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
// 취소
$(".cencle").on("click", function(){
location.href = "/";
})
$("#submit").on("click", function(){
if($("#userPass").val()==""){
alert("비밀번호를 입력해주세요.");
$("#userPass").focus();
return false;
}
if($("#userName").val()==""){
alert("성명을 입력해주세요.");
$("#userName").focus();
return false;
}
$.ajax({
url : "/member/passChk",
type : "POST",
dateType : "json",
data : $("#updateForm").serializeArray(),
success: function(data){
if(data==true){
if(confirm("회원수정하시겠습니까?")){
$("#updateForm").submit();
}
}else{
alert("패스워드가 틀렸습니다.");
return;
}
}
})
});
})
</script>
<body>
<section id="container">
<form id="updateForm" action="/member/memberUpdate" method="post">
<div class="form-group has-feedback">
<label class="control-label" for="userId">아이디</label>
<input class="form-control" type="text" id="userId" name="userId" value="${member.userId}" readonly="readonly"/>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userPass">패스워드</label>
<input class="form-control" type="password" id="userPass" name="userPass" />
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userName">성명</label>
<input class="form-control" type="text" id="userName" name="userName" value="${member.userName}"/>
</div>
</form>
<div class="form-group has-feedback">
<button class="btn btn-success" type="button" id="submit">회원정보수정</button>
<button class="cencle btn btn-danger" type="button">취소</button>
</div>
</section>
</body>
</html>
6. memberDeleteView.jsp 작성
회원탈퇴도 회원수정과 같은 방식입니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>회원탈퇴</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
// 취소
$(".cencle").on("click", function(){
location.href = "/";
})
$("#submit").on("click", function(){
if($("#userPass").val()==""){
alert("비밀번호를 입력해주세요.");
$("#userPass").focus();
return false;
}
$.ajax({
url : "/member/passChk",
type : "POST",
dateType : "json",
data : $("#delForm").serializeArray(),
success: function(data){
if(data==true){
if(confirm("회원탈퇴하시겠습니까?")){
$("#delForm").submit();
}
}else{
alert("패스워드가 틀렸습니다.");
return;
}
}
})
});
})
</script>
<body>
<section id="container">
<form action="/member/memberDelete" method="post" id="delForm">
<div class="form-group has-feedback">
<label class="control-label" for="userId">아이디</label>
<input class="form-control" type="text" id="userId" name="userId" value="${member.userId}" readonly="readonly"/>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userPass">패스워드</label>
<input class="form-control" type="password" id="userPass" name="userPass" />
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userName">성명</label>
<input class="form-control" type="text" id="userName" name="userName" value="${member.userName}" readonly="readonly"/>
</div>
</form>
<div class="form-group has-feedback">
<button class="btn btn-success" type="button" id="submit">회원탈퇴</button>
<button class="cencle btn btn-danger" type="button">취소</button>
</div>
<div>
<c:if test="${msg == false}">
비밀번호가 맞지 않습니다.
</c:if>
</div>
</section>
</body>
</html>
7. memberMapper.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="memberMapper">
<!-- 회원가입 -->
<insert id="register">
INSERT INTO MP_MEMBER( USERID
, USERPASS
, USERNAME )
VALUES( #{userId}
, #{userPass}
, #{userName})
</insert>
<!-- 로그인 -->
<select id="login" resultType="kr.co.vo.MemberVO">
SELECT USERID, USERPASS, USERNAME
FROM MP_MEMBER
WHERE USERID = #{userId}
<!-- AND USERPASS = #{userPass} -->
</select>
<!-- 회원정보 수정 -->
<update id="memberUpdate">
UPDATE MP_MEMBER SET
<!-- USERPASS = #{userPass}, -->
USERNAME = #{userName}
WHERE USERID = #{userId}
</update>
<!-- 회원탈퇴 -->
<delete id="memberDelete">
DELETE FROM MP_MEMBER
WHERE USERID = #{userId}
<!-- AND USERPASS = #{userPass} -->
</delete>
<!-- 패스워드 체크 -->
<select id="passChk" resultType="int">
SELECT COUNT(*) FROM MP_MEMBER
WHERE USERID = #{userId}
AND USERPASS = #{userPass}
</select>
<!-- 아이디 중복 체크 -->
<select id="idChk" resultType="int">
SELECT COUNT(*) FROM MP_MEMBER
WHERE USERID = #{userId}
</select>
</mapper>
8. 실행 테스트
회원가입을 하고 데이터를 조회해보면 암호화가된 상태를 보실수 있습니다.
압축파일도 같이 첨부합니다. 7z프로그램을 이용하여 푸셔야해요.