관리 메뉴

SIMPLE & UNIQUE

5회차_2강 : 로그인을 구현한다. 쿠키로 사용자 세션을 관리한다. 본문

탈잉 강의 자료/react.js(프론트) + node.js(백앤드) 개발에서 배포까지

5회차_2강 : 로그인을 구현한다. 쿠키로 사용자 세션을 관리한다.

착한코딩 2020. 2. 14. 02:10

5_2 목표 : 홈 화면에서 로그인을 구현한다. 이때  bcrypt 패키지를 사용해 비밀번호를 암호화 한다. 로그인 후 세션 관리 기능을 구현한다.

 

1. 로그인을 구현해, 그 결과에 따라 유효성을 체크하고 동작을 다르게 처리한다.

 

1) react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components에 있는 LoginForm.js를 아래와 같이 수정한다.

import React, { Component } from 'react';
import {Redirect, Link} from 'react-router-dom'
import cookie from 'react-cookies';
import $ from 'jquery';
import axios from "axios";
import Swal from 'sweetalert2'

class LoginForm extends Component {
    constructor (props) {
        super(props);

        this.state = {
            prop:props
        }
    }
    submitClick = async e => {
        this.email_val = $('#email_val').val();
        this.pwd_val = $('#pwd_val').val();
        if(this.email_val === '' || this.pwd_val === ''){
            this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
        }else{
            axios.post('/api/LoginForm?type=signin', {
                is_Email: this.email_val,
                is_Password: this.pwd_val
            })
            .then( response => {
                try {
                    var userid = response.data.json[0].useremail
                    var username = response.data.json[0].username
                    var userflag = response.data.json[0].userflag
                    var upw = response.data.json[0].userpassword
                    
                    if(userid != null && userid != ''){
                        this.sweetalert('로그인 되었습니다.', '', 'info', '닫기')
                        //로그인 id 세션에 저장
                        const expires = new Date()
                        expires.setMinutes(expires.getMinutes() + 60)
                        
                        //userid 와 username을 비밀키로 암호화해 쿠키값에 세팅한다.
                        axios.post('/api/LoginForm?type=SessionState', {
                            is_Email: userid,
                            is_UserName: username,
                        })
                        .then( response => {

                            cookie.save('userid', response.data.token1
                            , {
                                path: '/',
                                expires,
                                // httpOnly: true // 도메인 연결 후 주석해제
                            })
                            cookie.save('username', response.data.token2
                            , {
                                path: '/',
                                expires,
                                // httpOnly: true // 도메인 연결 후 주석해제
                            })

                            cookie.save('userpassword', upw
                            , {
                                path: '/',
                                expires,
                                // httpOnly: true // 도메인 연결 후 주석해제
                            })
                            cookie.save('user_flag', 'Y'
                                , {
                                    path: '/',
                                    expires
                                }
                            );
                        })  
                        .catch( error => {this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기'); return false; } );
                        
                        setTimeout(function() {
                            window.location.href = '/';
                        }.bind(this),1000
                        );

                    }else{
                        this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
                    }
                } catch (error) {
                    this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
                }
            })
            .catch( response => { alert(response);return false; } );
        }
    }

    // input value state
    handleChange = (e) => {
        this.setState({
            [e.target.name]: e.target.value
        })
    }

    // login form post
    handleSubmit = (e) => {
        // submit 페이지 리로딩 방지
        e.preventDefault();
    }

    // 비밀번호 재설정
    pwdResetClick = () => {
        $('.m_login').hide();
        $('.m_pw').fadeIn();
        $('.m_pw').css('display','table-cell');
    }

    //비밀전호 재설정용 메일 발송
    sendEmail = (email, subject, text, e) => {
        axios.post('/api/message?type=email&roll=resetpw', {
            is_Email : email,
            is_Subject : subject,
            is_Text: text
        })
        .then( response => {
            this.sweetalert('입력하신 이메일로 비밀번호 \n 재설정 메일 보내드렸습니다.', '', 'info', '닫기')
        })
        .catch( error => {this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');return false;});
    }

    // //비밀번호 재설정 확인버튼
    pwdResetConfim = (e) => {
        this.reset_email = $('#reset_email_val').val();
        this.reset_name = $('#reset_name_val').val();
        if(this.reset_email === '' || this.reset_name === ''){
            this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
        }else{
            axios.post('/api/LoginForm?type=pwreset', {
                is_Email: this.reset_email,
                is_Name: this.reset_name,
            })
            .then( response => {
                var userid = response.data.json[0].useremail
                var username = response.data.json[0].username
                var userpassword = response.data.json[0].userpassword

                if(userid != null && userid != ''){
                    this.sendEmail(userid, 'rtrod 비밀번호 재설정 메일', userpassword)
                }else{
                    this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
                }
            })
            .catch( response => {this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기');return false; } );
        }
    }

    //alert 기본 함수
    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    render () {
        return (
            <section className="main">
            {/* <!-- 로그인폼 --> */}
                <div className="m_login">
                <h3><span><img src={require("../img/main/log_img.png")} alt="" /></span>LOGIN</h3>
                <div className="log_box">
                    <form onSubmit={this.handleSubmit}>
                    <div className="in_ty1">
                        <span><img src={require("../img/main/m_log_i3.png")} alt="" /></span>
                        <input type="text" id="email_val" name="email" placeholder="이메일" onChange={this.handleChange} />
                    </div>
                    <div  className="in_ty1">
                        <span className="ic_2"><img src={require("../img/main/m_log_i2.png")} alt="" /></span>
                        <input type="password" id="pwd_val" name="password" placeholder="비밀번호" onChange={this.handleChange} />
                    </div>
                    <ul className="af">
                        <li><Link to={'/register_check'}>회원가입</Link></li>
                        <li className="pwr_b" onClick={this.pwdResetClick}><a href="#n">비밀번호 재설정</a></li>
                    </ul>
                    {/* <input className="s_bt" type="submit" value="로그인" />	 */}
                    <button className="s_bt" type="submit" onClick={this.submitClick}>로그인</button>
                    </form>
                </div>
                </div>
            {/* <!-- 비밀번호 재설정 --> */}
                <div className="m_login m_pw">
                <h3 className="pw_ls">비밀번호 재설정 <span className="compl1">완료</span></h3>
                <div className="log_box">
            {/* <!-- 1단 --> */}
                    {/* <form method="post"> */}
                    <div className="pw_one">
                        <div className="in_ty1">
                        <span><img src={require("../img/main/m_log_i3.png")} alt="" /></span>
                        <input type="text" id="reset_email_val" name="" placeholder="이메일"/>
                        </div>
                        <div  className="in_ty1">
                        <span className=""><img src={require("../img/main/m_log_i1.png")} alt="" /></span>
                        <input type="text" id="reset_name_val" name="" placeholder="성명"/>
                        </div>
                        <div className="btn_confirm btn_confirm_m">
                        <div className="bt_ty bt_ty_m bt_ty1 cancel_ty1" onClick={this.pwdResetCancleClick}>취소</div>
                        <a href="#n" className="bt_ty bt_ty_m bt_ty2 submit_ty1" onClick={this.pwdResetConfim}>확인</a>
                        </div>
                    </div>
            {/* <!-- 2단 가려둠-->  */}
                    <div className="pw_two">
                        <div className="in_ty1">
                        <span className="ic_2"><img src={require("../img/main/m_log_i2.png")} alt="" /></span>
                        <input type="password" name="" placeholder="새 비밀번호" />
                        </div>
                        <div className="in_ty1">
                        <span className="ic_2"><img src={require("../img/main/m_log_i2.png")} alt="" /></span>
                        <input type="password" name="" placeholder="새 비밀번호 확인" />
                        </div>
                        <div className="btn_confirm btn_confirm_m">
                        <div className="bt_ty bt_ty_m bt_ty1 cancel_ty1">취소</div>
                        <a href="#n" className="bt_ty bt_ty_m bt_ty2 submit_ty1">재설정</a>
                        </div>
                    </div>
            {/* <!-- 3단 가려둠 --> */}
                    <div className="pw_tree">
                        <div className="">
                        <p>
                            '<span>홍길동</span>'
                            님의 비밀번호가 재설정되었습니다.
                        </p>		
                        
                        </div>
                        <input className="s_bt" type="submit" value="로그인 이동" />	
                    </div>
                    {/* </form> */}
                </div>
                </div>
                
            </section>
        );
    }
}

LoginForm.defaultProps = {
}

export default LoginForm;

2) react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components에 있는 App.js를 아래와 같이 수정

import React, { Component } from 'react';
import { Router, Route, Switch } from "react-router";
import {Redirect} from 'react-router-dom'

import Api_test from './Api_test'
import cookie from 'react-cookies';
import axios from "axios";

// css
import '../css/new.css';
import '../css/owl.carousel.min.css';
import '../css/owl.theme.default.min.css';

// header
import HeaderAdmin from './Header/Header admin';

// footer
import Footer from './Footer/Footer';

// login
import LoginForm from './LoginForm';

// admin floatingPopulationList
import floatingPopulationList from './Floating_population/floatingPopulationList';

// admin softwareinfo
import AdminSoftwareList from './SoftwareToolsManage/AdminSoftwareList';
import AdminSoftwareView from './SoftwareToolsManage/AdminSoftwareView';

// register
import Register from './Register/Register';
import RegisterCheck from './Register/RegisterCheck';

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

  componentDidMount() {
      
    // 비밀번호 재설정 패이지를 제외하고, 세션이 유효하지 않으면 home url로 이동.
    if(window.location.pathname.indexOf('/PwChangeForm') == -1){

      //쿠키에서 userid, username을 가져와 복호화한다.
      axios.post('/api/LoginForm?type=SessionConfirm', {
        token1 : cookie.load('userid') 
        , token2 : cookie.load('username') 
      })
      .then( response => {
          this.state.userid = response.data.token1
          let password = cookie.load('userpassword')
          if(password == undefined){
            password = ''
          }
          axios.post('/api/LoginForm?type=pwemail', {
            is_Email: this.state.userid,
            is_Token : password
          })
          .then( response => {
            if(response.data.json[0] !== undefined){
              var userid = response.data.json[0].useremail
                
              if(userid == undefined || password == undefined){
                window.location.href = '/admin';
              }
            }else{
              // 계정정보가 유효하지 않다면 세션값 삭제후, 홈으로 이동
              if(window.location.hash != 'nocookie'){
                this.remove_cookie();
                window.location.href = '/nocookie/#nocookie';
              }  
            }
          })
          .catch( response => {
            this.remove_cookie()
            window.location.href = '/nocookie/#nocookie';
          } );
      })
      .catch( response => {window.location.href = '/nocookie/#nocookie';} );
    }
  }

  //쿠키 초기화
  remove_cookie = (e) => {
    cookie.remove('userid', { path: '/'});
    cookie.remove('username', { path: '/'});
    cookie.remove('user_flag', { path: '/'});
    cookie.remove('userpassword', { path: '/'});
  }

  render () {
    return (
      <div className="App">
          <HeaderAdmin/> 
          <Switch>
          {
              (cookie.load('userid') !== undefined) ? (
                <Route exact path='/' component={AdminSoftwareList} />
              ):(
                  <Route exact path='/admin' component={LoginForm} />
              )
            }
            <Route exact path='/' component={LoginForm} />
            <Route path='/nocookie' component={LoginForm} />
            <Route path='/Api_test' component={Api_test} />
            <Route path='/floatPopulationList' component={floatingPopulationList} />
            <Route path='/AdminSoftwareList' component={AdminSoftwareList} />
            <Route path='/AdminSoftwareView/:swtcode' component={AdminSoftwareView} />
            <Route path='/register' component={Register} />
            <Route path='/register_check' component={RegisterCheck} />
          </Switch>
          <Footer 
            footer_address={this.props.footer_address} 
            footer_tel={this.props.footer_tel}  
            footer_email={this.props.footer_email} 
            footer_mobile={this.props.footer_mobile} 
          />
      </div>
    );
  }
}

App.defaultProps = {
  // footer value
  footer_address: '[34234] 서울특별시 강남구 삼성동 111-114',
  footer_tel: '02-1234-5678',
  footer_email: 'ljung5@naver.com',
  footer_mobile: '010-3288-3398',
};

export default App

 

3) 로그아웃 처리를 위해 react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components\Header

에 있는 Header admin.js를 아래와 같이 수정한다.

import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import cookie from 'react-cookies';
import 'react-app-polyfill/ie11';
import axios from "axios";
import Swal from 'sweetalert2'
import $ from 'jquery';

class Header extends Component {

    constructor(props) {
        super(props);
        this.state = {
            responseNotice: '',    //subcode response 변수
            append_NoticeFld: '',   //연구분야 append 변수
            notice_cnt: '',   //알림 갯수
            //관리자 세션 처리
            admin_usernm:'', //관리자 이름
            admin_userid:'', //관리자 아이디
        };

        this.temp_ref = () => {
            $(this).css('display', 'block');
        }
    }

    componentDidMount() {
        var cookie_admin_userid = cookie.load('userid')
        var cookie_admin_usernm = cookie.load('username')

        //사용자가 페이지 로드를 한 시점부터 다시 세션시간 계산
        if(cookie_admin_userid != undefined){
            const expires = new Date()
            expires.setMinutes(expires.getMinutes() + 60)
            cookie.save('userid', cookie_admin_userid
                , {
                    path: '/',
                    expires
                }
            );
            cookie.save('username', cookie_admin_usernm
            , {
                path: '/',
                expires
                }
            );
            cookie.save('user_flag', 'Y'
                , {
                    path: '/',
                    expires
                }
            );
            $('.menulist').show()
            $('.hd_top').show()
        }else{
            $('.menulist').hide()
            $('.hd_top').hide()
        }
        this.callSessionInfoApi()
    }

    // 커뮤니티 탭 드롭다운 이벤트
    mouseEnter () {
        $('.gn_2').stop().slideDown(300);
    };

    // 커뮤니티 탭 드롭다운 이벤트
    mouseLeave () {
        $('.gn_2').stop().slideUp(300);
    };

    // 내 정보 영역 마우스 hover 이벤트
    myInfoHover () {
        $(".hd_left > li > .box1").stop().fadeIn(400);
    }
    
    // 내 정보 영역 마우스 leave 이벤트
    myInfoLeave () {
        $(".hd_left > li > .box1").stop().fadeOut(400);
    }
    
    // 알림 영역 마우스 hover 이벤트
    alarmHover = (e) => {
        // this.callNoticeApi('display')
    }

    // 알림 영역 마우스 hover 이벤트
    alarmLeave () {
        $(".hd_left > li > .box0").stop().fadeOut(400);
    }

    //로그아웃 아이디 세션 정보 삭제
    logout = async e => {
        cookie.remove('userid', { path: '/'});
        cookie.remove('username', { path: '/'});
        cookie.remove('user_flag', { path: '/'});
        cookie.remove('userpassword', { path: '/'});

        window.location.href = '/admin';
    }

    // 쿠키값 userid, username 호출
    callSessionInfoApi = (type) => {
        axios.post('/api/LoginForm?type=SessionConfirm', {
            token1 : cookie.load('userid') 
            , token2 : cookie.load('username') 
        })
        .then( response => {
            this.state.admin_usernm = response.data.token2
            this.state.admin_userid = response.data.token1
        })
        .catch( error => {this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');return false;} );
    }

    //alert 기본 함수
    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    render () {
        return(
            <header className="gnb_box">
                    <div className="hd_top">
                        <div className="top_wrap ct1 af">
                        <ul className="hd_left af">
                            <li className="my1" onMouseEnter={this.myInfoHover} onMouseLeave={this.myInfoLeave}><b>내정보</b>
                            <div className="box0 box1">
                                <ul>
                                <li><Link to={'/register'}>내 정보 수정</Link></li>
                                <li><a href="javascript:" onClick={this.logout}>로그아웃</a></li>
                                </ul>
                            </div>
                            </li>
                            <li  className="my2" onMouseEnter={this.alarmHover} onMouseLeave={this.alarmLeave}><b><span>{this.state.notice_cnt}</span>알림</b>
                            <div className="box0 box2">
                                <ul className="al_box">
                                    {this.state.append_NoticeFld}
                                </ul>
                                <span className="bt_ty1">
                                {/* <a href="javascript:" onClick={this.deleteNotice}>알림 모두 제거</a> */}
                                </span>
                            </div>
                            </li>
                        </ul>
                        <div className="hd_right">
                            <p><span>'{this.state.admin_usernm}'</span>님 반갑습니다.</p>
                        </div>
                        </div>
                    </div>
                <div className="h_nav ct1 af">
                    <div className="logo">
                        <Link to={'/admin'}><img src={require("../../img/layout/logo.jpg")} height="65px" width="200px" alt=""/></Link>
                    </div>
                    <nav className="gnb gnb_admin">
                    <ul className="af">
                        <li className="menulist">
                            <Link to={'/UserApproval'}>사용자 관리</Link>
                        </li>
                        <li className="menulist">
                            <Link to={'/AdminResearchProject'}>Research Projects 관리</Link>
                        </li>
                        <li className="menulist">
                            <Link to={'/AdminSoftwareList'}>Software Tools 관리</Link>
                        </li>
                        <li className="menulist">
                            <Link to={'/AdminDataSourceList'}>Data Sources 관리</Link>
                        </li>
                        {/* 드롭다운 이벤트 */}
                        <li  className="menulist" onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} ><Link to={'/floatPopulationList'}>유동인구 조회</Link>
                        <ul className="gn_2">
                            <li><Link to={'/community/notice'}>공지사항</Link></li>
                        </ul>
                        </li>
                        <li  className="menulist">
                            <Link to={'/SubCodeManage'}>Sub code 관리</Link>
                        </li>
                    </ul>
                    </nav>
                </div>
            </header>
        );
    }
}

export default Header;

## 참고 ##

  이 상태에서 로그인이 정상적으로 되면 softwaretool 리스트 페이지로 이동하고, 로그아웃을 하면 다시 로그인 페이지로 이동한다. 로그아웃 상태에서는 페이지 진입을 막기위해 헤더를 hide() 처리했다.

 

4) 로그인시 bcript사용을 위해 C:\Users\ljung\OneDrive\문서\taling0102\modules\dbconnect_Module.js에 require("bcrypt")부분을 추가한다. 

var express = require("express");
var router = express.Router();
const mysql = require("mysql");
const bodyParser = require("body-parser");
const bcrypt = require("bcrypt");

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

// Connection Pool 세팅
const pool  = mysql.createPool({
  connectionLimit: 66,
  waitForConnections: true,
  host: "databaszonaws.com",
....생략....

다음과같이 EC2 서버에 bcript설치 오류가 날 때는,  npm install bcry

node-pre-gyp WARN Using request for node-pre-gyp https download
node-pre-gyp ERR! install error
node-pre-gyp ERR! stack Error: The N-API version of this Node instance is 1. This module supports N-API version(s) 3. This Node instance cannot run this module.
node-pre-gyp ERR! stack     at Object.module.exports.validate_package_json (/home/ubuntu/20200524/node_modules/node-pre-gyp/lib/util/napi.js:82:9)
node-pre-gyp ERR! stack     at validate_config (/home/ubuntu/20200524/node_modules/node-pre-gyp/lib/util/versioning.js:229:10)
node-pre-gyp ERR! stack     at Object.module.exports.evaluate (/home/ubuntu/20200524/node_modules/node-pre-gyp/lib/util/versioning.js:279:5)
node-pre-gyp ERR! stack     at install (/home/ubuntu/20200524/node_modules/node-pre-gyp/lib/install.js:241:31)
node-pre-gyp ERR! stack     at Object.self.commands.(anonymous function) [as install] (/home/ubuntu/20200524/node_modules/node-pre-gyp/lib/node-pre-gyp.js:52:37)
node-pre-gyp ERR! stack     at run (/home/ubuntu/20200524/node_modules/node-pre-gyp/bin/node-pre-gyp:82:30)
node-pre-gyp ERR! stack     at Object.<anonymous> (/home/ubuntu/20200524/node_modules/node-pre-gyp/bin/node-pre-gyp:134:1)
node-pre-gyp ERR! stack     at Module._compile (module.js:652:30)
node-pre-gyp ERR! stack     at Object.Module._extensions..js (module.js:663:10)
node-pre-gyp ERR! stack     at Module.load (module.js:565:32)
node-pre-gyp ERR! System Linux 4.15.0-1058-aws
node-pre-gyp ERR! command "/usr/bin/node" "/home/ubuntu/20200524/node_modules/.bin/node-pre-gyp" "install" "--fallback-to-build"
node-pre-gyp ERR! cwd /home/ubuntu/20200524/node_modules/bcrypt
node-pre-gyp ERR! node -v v8.10.0
node-pre-gyp ERR! node-pre-gyp -v v0.14.0
node-pre-gyp ERR! not ok
The N-API version of this Node instance is 1. This module supports N-API version(s) 3. This Node instance cannot run this module.
npm WARN notsup Unsupported engine for bcrypt@4.0.1: wanted: {"node":">= 10.0.0"} (current: {"node":"8.10.0","npm":"6.14.5"})
npm WARN notsup Not compatible with your version of node/npm: bcrypt@4.0.1
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.1.3 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
Comments