관리 메뉴

SIMPLE & UNIQUE

4-1강 : 회원 가입 기능 구현, 비밀번호 단반향 암호화 본문

탈잉 강의 자료/2020_비전공자도 가능한 웹 프로젝트

4-1강 : 회원 가입 기능 구현, 비밀번호 단반향 암호화

착한코딩 2020. 8. 3. 22:53

4_1강 소스.zip
0.51MB
4-1강 소스_덮어쓰기.zip
0.26MB

4_1 목표 : 회원가입 페이지에서 회원 정보를 입력하고, 회원가입 버튼을 누르면 아이디 중복체크를 한다. 중복체크가 완료되면 회원 가입 API를 호출한다. 비밀번호를 단반향 암호화 처리해 DB에 삽입한다.

 

## 참고 ##

  회원정보 관리는 개념이 중요하다. 회원가입 > 로그인 > 사용자 세션을 관리하는 프로세스는 아래와 같다.

1. 회원가입 폼에서 아이디, 비밀번호 등 정보를 입력받는다.

2. 비밀번호를 제외한 모든 데이터는 그대로 DB에 삽입한다.

    비밀번호는 복호화할 수 없는 단방향으로 암호화해 DB에 넣는다. (암호화 방식은 bcript가 처리)

3. 사용자는 로그인 할 때, 아이디와 비밀번호를 입력한다.

    아이디, 비밀번호를 DB에 저장된 값과 비교해야한다. 아이디는 그대로 비교하고, 비밀번호는 2.에서와 동일한

    방식으로 암호화해 비교한다. 

4.  비교한 값이 동일하면 로그인 처리를 해야한다. 

    쿠키에 jwt(토큰기반인증)을 이용해 토큰을 만들어 저장한다. (비밀키 사용)

     세션 유효 시간을 세팅한다. 

5. 로그인이 된 상태면 software tool 리스트 페이지로, 아니라면 로그인 페이지로 페이지 이동시킨다.

   App.js 라우팅 페이지에 쿠키값 인증 로직을 넣는다. 페이지를 로딩할 때마다 유효한 세션인지 인증한다.

1. react 경로에 회원가입 페이지를 추가한다.

 

1) 아래와 같이 리액트 경로(C:\react200\client\src\components)에 Register 폴더를 만들고, Register.js파일을 생성한다.

2) Register.js 에 아래 코드를 붙여 넣는다.

import React, { Component } from 'react';
import axios from "axios";
import Swal from 'sweetalert2';
import $ from 'jquery';

class Register extends Component {
    constructor (props) {
        super(props);
        this.state = {
        }
    }

    submitClick = async (type, e) => {

        this.email_val_checker = $('#email_val').val();
        this.email2_val_checker = $('#email2_val').val();
        this.pwd_val_checker = $('#pwd_val').val();
        this.pwd_cnf_val_checker = $('#pwd_cnf_val').val();
        this.name_val_checker = $('#name_val').val();
        this.org_val_checker = $('#org_val').val();
        this.major_val_checker = $('#major_val').val();
        this.phone1_val_checker = $('#phone1_val').val();
        this.phone2_val_checker = $('#phone2_val').val();
        this.phone3_val_checker = $('#phone3_val').val();

        this.fnValidate = (e) => {
            var pattern1 = /[0-9]/;
            var pattern2 = /[a-zA-Z]/;
            var pattern3 = /[~!@#$%^&*()_+|<>?:{}]/;

            if(this.email_val_checker === '') {
                $('#email_val').addClass('border_validate_err');
                this.sweetalert('이메일 주소를 다시 확인해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.email_val_checker.search(/\s/) !== -1) {
                $('#email_val').addClass('border_validate_err');
                this.sweetalert('이메일 공백을 제거해 주세요.', '', 'info', '닫기')
                return false;
            }
            $('#email_val').removeClass('border_validate_err');

            if(this.email2_val_checker ==='') {
                $('#email2_val').addClass('border_validate_err');
                this.sweetalert('이메일 주소를 다시 확인해주세요.', '', 'info', '닫기')
                return false;
            }
            $('#email2_val').removeClass('border_validate_err');

            if(this.pwd_val_checker ==='') {
                $('#pwd_val').addClass('border_validate_err');
                this.sweetalert('비밀번호를 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.pwd_val_checker !=='') {
                var str = this.pwd_val_checker;
                if(str.search(/\s/) !== -1) {
                    $('#pwd_val').addClass('border_validate_err');
                    this.sweetalert('비밀번호 공백을 제거해 주세요.', '', 'info', '닫기')
                    return false;
                } 
                if(!pattern1.test(str) || !pattern2.test(str) || !pattern3.test(str)
                || str.length < 8 || str.length > 16) {
                    $('#pwd_val').addClass('border_validate_err');
                    this.sweetalert('8~16자 영문 대 소문자\n숫자, 특수문자를 사용하세요.', '', 'info', '닫기')
                    return false; 
                } 
            }
            $('#pwd_val').removeClass('border_validate_err');

            if(this.pwd_cnf_val_checker ==='') {
                $('#pwd_cnf_val').addClass('border_validate_err');
                this.sweetalert('비밀번호 확인을 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.pwd_val_checker !== this.pwd_cnf_val_checker) {
                $('#pwd_val').addClass('border_validate_err');
                $('#pwd_cnf_val').addClass('border_validate_err');
                this.sweetalert('비밀번호가 일치하지 않습니다.', '', 'info', '닫기')
                return false;
            }
            $('#pwd_cnf_val').removeClass('border_validate_err');

            if(this.name_val_checker ==='') {
                $('#name_val').addClass('border_validate_err');
                this.sweetalert('성명을 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.name_val_checker.search(/\s/) !== -1) {
                $('#name_val').addClass('border_validate_err');
                this.sweetalert('성명에 공백을 제거해 주세요.', '', 'info', '닫기')
                return false;
            }
            $('#name_val').removeClass('border_validate_err');
    
            if(this.org_val_checker ==='') {
                $('#org_val').addClass('border_validate_err');
                this.sweetalert('소속기관을 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.org_val_checker.search(/\s/) !== -1) {
                $('#org_val').addClass('border_validate_err');
                this.sweetalert('소속기관에 공백을 제거해 주세요.', '', 'info', '닫기')
                return false;
            }
            $('#org_val').removeClass('border_validate_err');
            if(this.major_val_checker ==='') {
                $('#major_val').addClass('border_validate_err');
                this.sweetalert('전공을 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.major_val_checker.search(/\s/) !== -1) {
                $('#major_val').addClass('border_validate_err');
                this.sweetalert('전공에 공백을 제거해 주세요.', '', 'info', '닫기')
                return false;
            }
            $('#major_val').removeClass('border_validate_err');
            if(this.phone1_val_checker ==='' || this.phone2_val_checker ===''
            || this.phone3_val_checker ==='') {
                $('#phone1_val').addClass('border_validate_err');
                $('#phone2_val').addClass('border_validate_err');
                $('#phone3_val').addClass('border_validate_err');
                this.sweetalert('휴대전화 번호를 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            $('#phone1_val').removeClass('border_validate_err');
            $('#phone2_val').removeClass('border_validate_err');
            $('#phone3_val').removeClass('border_validate_err');
            return true;
        }

        if(this.fnValidate()){
            this.state.full_email = this.email_val_checker+'@'+this.email2_val_checker
            axios.post('/api/register?type=dplicheck', {
                is_Email: this.email_val_checker+'@'+this.email2_val_checker
            })
            .then( response => {
                try {
                    const dupli_count = response.data.json[0].num;
                    if(dupli_count !== 0){
                        $('#email_val').addClass('border_validate_err');
                        $('#email2_val').addClass('border_validate_err');
                        this.sweetalert('이미 존재하는 이메일입니다.', '', 'info', '닫기')
                    }else{
                        $('#email_val').removeClass('border_validate_err');
                        $('#email2_val').removeClass('border_validate_err');
                        this.fnSignInsert('signup', e)
                    }
                } catch (error) {
                    this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기')
                }
            })
            .catch( response => { return false; } );
        }

        this.fnSignInsert = async (type, e) => {
            var jsonstr = $("form[name='frm']").serialize();
            jsonstr = decodeURIComponent(jsonstr);
            var Json_form = JSON.stringify(jsonstr).replace(/\"/gi,'')
            Json_form = "{\"" +Json_form.replace(/\&/g,'\",\"').replace(/=/gi,'\":"')+"\"}";
           
            try {
                const response = await fetch('/api/register?type='+type, {
                    method: 'POST',
                    headers: {
                    'Content-Type': 'application/json',
                    },
                    body: Json_form,
                });
                const body = await response.text();
                if(body === "succ"){
                    this.sweetalert('회원가입이 완료되었습니다.', '', 'info', '닫기')
                    this.props.history.push('/');
                }else{
                    this.sweetalert('작업중 오류가 발생하였습니다.', body, 'error', '닫기');            
                }  
            } catch (error) {
                this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');
            }
        }
    };

    emailKeyPress = (e) => {
        $('#email_val').removeClass('border_validate_err');
    };

    pwdKeyPress = (e) => {
        $('#pwd_val').removeClass('border_validate_err');
    };

    pwdCnfKeyPress = (e) => {
        $('#pwd_cnf_val').removeClass('border_validate_err');
    };

    nameKeyPress = (e) => {
        $('#name_val').removeClass('border_validate_err');
    };

    handleSubmit = (e) => {
        e.preventDefault();
    };
    
    mustNumber = (id) => {
        var pattern1 = /[0-9]/;
        var str = $('#'+id).val();
        if(!pattern1.test(str.substr(str.length - 1, 1))){
            $('#'+id).val(str.substr(0, str.length-1));
        }
    }

    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    render () {
        return (
            <div>
                <section className="sub_wrap" >
                    <article className="s_cnt re_1 ct1">
                        <div className="li_top">
                            <h2 className="s_tit1">회원가입</h2>
                            <form method="post" name="frm">
                                <div className="re1_wrap">
                                    <div className="re_cnt ct2">
                                        <table className="table_ty1">
                                            <tr className="re_email">
                                                <th>이메일</th>
                                                <td>
                                                    <input id="email_val" type="text" name="is_Useremail1"
                                                    placeholder="이메일을 입력해주세요." onKeyPress={this.emailKeyPress}/>
                                                    <span className="e_goll">@</span>
                                                    <select id="email2_val" name="is_Useremail2" className="select_ty1">
                                                            <option value="">선택하세요</option>
                                                            <option value='naver.com'>naver.com</option>
                                                            <option value='hanmail.net'>hanmail.net</option>
                                                            <option value='nate.com'>nate.com</option>
                                                            <option value='hotmail.com'>hotmail.com</option>
                                                            <option value='gmail.com'>gmail.com</option>>
                                                            <option value='yahoo.co.kr'>yahoo.co.kr</option>
                                                            <option value='yahoo.com'>yahoo.com</option>
                                                    </select>
                                                </td>
                                            </tr>
                                            <tr>
                                                <th>비밀번호</th>
                                                <td>
                                                    <input id="pwd_val" type="password" name="is_Password"
                                                    placeholder="비밀번호를 입력해주세요." onKeyPress={this.pwdKeyPress} />
                                                </td>
                                            </tr>
                                            <tr>
                                                <th>비밀번호 확인</th>
                                                <td>
                                                    <input id="pwd_cnf_val" type="password" name="is_Password"
                                                    placeholder="비밀번호를 다시 입력해주세요." onKeyPress={this.pwdCnfKeyPress}/>
                                                </td>
                                            </tr>
                                            <tr>
                                                <th>성명</th>
                                                <td>
                                                    <input id="name_val" type="text" name="is_Username"
                                                    placeholder="성명을 입력해주세요." onKeyPress={this.nameKeyPress}/>
                                                </td>
                                            </tr>
                                            <tr>
                                                <th>소속 기관</th>
                                                <td>
                                                    <input id="org_val" type="text" name="is_Organization"
                                                    placeholder="소속 기관명을 입력해주세요." />
                                                </td>
                                            </tr>
                                            <tr>
                                                <th>전공</th>
                                                <td>
                                                    <input id="major_val" type="text" name="is_Usermajor"
                                                    placeholder="전공을 입력해주세요." />
                                                </td>
                                            </tr>
                                            <tr className="tr_tel">
                                                <th>핸드폰</th>
                                                <td>
                                                    <select id="phone1_val" name="is_Userphone1" className="select_ty1">
                                                        <option value="">선택</option>
                                                        <option value="010">010</option>
                                                        <option value="011">011</option>
                                                        <option value="016">016</option>
                                                        <option value="017">017</option>
                                                        <option value="018">018</option>
                                                        <option value="019">019</option>
                                                    </select>
                                                    <span className="tel_dot">-</span>
                                                    <input id="phone2_val" name="is_Userphone2" max="9999"
                                                    maxlength="4" onChange={(e) => this.mustNumber("phone2_val")}/>
                                                    <span className="tel_dot">-</span>
                                                    <input id="phone3_val" name="is_Userphone3" max="9999"
                                                    maxlength="4" onChange={(e) => this.mustNumber("phone3_val")}/>
                                                </td>
                                            </tr>
                                        </table>
                                    </div>
                                </div>
                                <div className="btn_confirm">
                                    <div className="bt_ty bt_ty2 submit_ty1" 
                                    onClick={(e) => this.submitClick('signup', e)}>회원가입</div>
                                </div>
                            </form>
                        </div>
                    </article>
                </section>
            </div>
        );
    }
}

export default Register;

2. node 경로(C:\react200)에 회원가입 관련 코드를 추가한다.

1) server.js 파일을 아래와 같이 수정한다.

var express = require('express');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var swtoolRouter = require("./routes/SwtoolRout");
var fileuploadRouter = require("./routes/UploadRout");
var usersRouter = require("./routes/UsersRout");
require("./routes/BatchRout");

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use("/api/Swtool", swtoolRouter);
app.use("/api/upload", fileuploadRouter);
app.use(express.static("./uploads"));
app.use("/api/register", usersRouter);

const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));

2) 단방향 암호화를 사용하기 위해, node 경로(C:\react200)에 bcrypt 패키지를 설치한다.

 

npm install bcrypt --save

3) 노드경로 C:\react200\routes에  UsersRout.js 파일을 추가해 아래 코드를 붙여넣는다. 회원가입 정보를 삽입하고 아이디 중복체크를 조회하는 mapper정보가 추가됐다. 

var express = require('express');
var router = express.Router();
const bodyParser = require('body-parser');

const bcrypt = require('bcrypt');
const saltRounds = 10;

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

router.post('/', (req, res, next) => {
    var type = req.query.type;
    if(type == "signup"){
      //회원가입 정보 삽입
      try {
        // Mysql Api 모듈(CRUD)
        var dbconnect_Module = require('./dbconnect_Module');
    
        //Mysql 쿼리 호출정보 입력
        req.body.mapper = 'UserMapper';//mybatis xml 파일명
        req.body.crud = 'insert';//select, insert, update, delete 중에 입력
        req.body.mapper_id = 'insertUser';
    
        var myPlaintextPassword = req.body.is_Password;
        if(myPlaintextPassword != '' && myPlaintextPassword != undefined ){
          bcrypt.genSalt(saltRounds, function(err, salt) {
            bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
              req.body.is_Password = hash;
              router.use('/', dbconnect_Module);
              next('route')
            });
          });
        }else{
          router.use('/', dbconnect_Module);
          next('route')
        }
      } catch (error) {
        console.log("Module > dbconnect error : "+ error);      
      }
    }else if(type == "dplicheck"){
      //이메일 중복체크
      try {
        // Mysql Api 모듈(CRUD)
        var dbconnect_Module = require('./dbconnect_Module');
    
        //Mysql 쿼리 호출정보 입력
        req.body.mapper = 'UserMapper';//mybatis xml 파일명
        req.body.crud = 'select';//select, insert, update, delete 중에 입력
        req.body.mapper_id = 'selectUserDpliCheck';
        router.use('/', dbconnect_Module);
        next('route')
      } catch (error) {
        console.log("Module > dbconnect error : "+ error);      
      }
    }
  });
  
  module.exports = router;

4) 노드경로 C:\react200\models에  UserMapper.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="UserMapper">  
  <insert id="insertUser"> 
    INSERT INTO react.react_user
    (
      username
      , userorg
      , useremail
      , userpassword
      , usermajor
      , userphone
      , userflag
      , reg_date
      , reg_user
      , update_date
      , update_user
    )
    VALUES (
      #{is_Username}
      , #{is_Organization}
      , CONCAT(#{is_Useremail1}, '@', #{is_Useremail2})
      , #{is_Password}
      , #{is_Usermajor}
      , CONCAT(#{is_Userphone1}, '-', #{is_Userphone2},'-', #{is_Userphone3})
      , 'Y'
      , DATE_FORMAT(now(), '%Y%m%d%H%i%s')
      , CONCAT(#{is_Useremail1}, '@', #{is_Useremail2})
      , DATE_FORMAT(now(), '%Y%m%d%H%i%s')
      , CONCAT(#{is_Useremail1}, '@', #{is_Useremail2})
      )
  </insert>

  <select id="selectUserDpliCheck">
      SELECT 
        count(*) as num
      FROM
      react.react_user
      WHERE useremail = #{is_Email}
  </select>
</mapper>

5) workbench를 열어 아래 쿼리를 실행해, 사용자 정보 테이블을 생성한다.

use react;
CREATE TABLE `react_user` (
  `username` varchar(100) DEFAULT NULL COMMENT '사용자 이름',
  `userorg` varchar(100) DEFAULT NULL COMMENT '소속기관',
  `useremail` varchar(100) COMMENT '이메일',
  `userpassword` varchar(100) DEFAULT NULL COMMENT '로그인 비밀번호',
  `usermajor` varchar(100) DEFAULT NULL COMMENT '전공',
  `userphone` varchar(100) DEFAULT NULL COMMENT '휴대전화번호',
  `userflag` varchar(100) DEFAULT NULL COMMENT '승인여부',
  `reg_date` varchar(100) DEFAULT NULL COMMENT '등록날짜',
  `reg_user` varchar(100) DEFAULT NULL COMMENT '등록자',
  `update_date` varchar(100) DEFAULT NULL COMMENT '수정날짜',
  `update_user` varchar(100) DEFAULT NULL COMMENT '수정자',
  PRIMARY KEY (`useremail`)
);
ALTER TABLE react.react_user convert to charset utf8;

6) 회원가입 페이지 정상적으로 완료되면, 다음과 같이 중복체크 쿼리와 회원정보 삽입 쿼리가 정상적으로 로그에 출력된다. 

7) 회원가입이 완료되면 로그인 화면으로 페이지를 이동한다.

https://taling.me/Talent/Detail/19341

 

[비전공자도 가능한 웹 프로젝트] 개발부터 배포까지, 배우면서 완성(react,nodejs,mysql,aws)! | 탈잉

✔ 수업 목표 & 5주 뒤 결과물 만들고 싶은 서비스를 직접 구현하게 됩니다. 사람들이 방문하고 액션을 할 수 있는 웹 페이지를 제작 & 배포하는 것까지 진행됩니다. 여러분이 개발 경험이 있든 ��

taling.me:443

강의 문의 : ljung5@naver.comhttps://taling.me/Talent/Detail/19341

 

Comments