Spring Boot/STUDY

[Spring Boot] React연동 프로젝트(3) - 회원가입 및 로그인 기능 구현

코맹 2024. 7. 4. 14:45

 

(FrontEnd) layout/Header.js 로그인, 회원가입 버튼으로 변경
import React from 'react';

import { Link, useNavigate } from 'react-router-dom';


const Header = () => {
  const navigate = useNavigate();   // Hook 함수는 직접 사용 불가(X)

  function gotoLogin() {
    navigate("/login");
  }

  // return은 화면을 그리겠다.
  return (
    <div className='container header'>
      <header className='d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom'>
        <div id='logo-area' className='col-md-1 mb-2 mb-md-0'>
          <a href="/home" className='d-inline-flex link-body-emphasis text-decoration-none'>
            <img src={require('../logo.png')} alt="logo" width={40} />
          </a>
        </div>

        <ul className='nav col-12 col-md-6 mb-2 justify-conter'>
          <li><Link to="/home" className='nav-link px-2 link-secondary'>홈</Link></li>
          <li><Link to="/boardList" className='nav-link px-2 link-secondary'>게시판</Link></li>
          <li><Link to="/qnaList" className='nav-link px-2 link-secondary'>질문응답</Link></li>
        </ul>

        <div className='col-md-3 text-end me-3'>
          <button type='button' className='btn btn-outline-primary me-2' onClick={gotoLogin}>로그인</button>
          <button type='button' className='btn btn-outline-primary'>회원가입</button>
        </div>
      </header>
    </div>
  );
}

export default Header;
  • 로그인, 회원가입 버튼 생성
  • onClick 함수 생성

 

Login.js 화면 수정
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
// RestAPI -> axios
import axios from 'axios';

function Login() {
  // 변수
  const [user, setUser] = useState({
    username: '',
    password: '',
  });   // useState() 괄호 안은 초기하는 값. json헝태(딕셔너리)

  // 함수
  // 값이 바뀔 때마다 user에 값을 넣음
  const handleChange = (e) => {
    const { name, value } = e.target; //target=> username, password 둘 중 하나
    setUser({ ...user, [name]: value});
  }

  // 핵심
  // async function handleSubmit(e){}
  const handleSubmit = async (e) => {
    e.preventDefault(); // submit동안 다른 이벤트가 발생하지 않도록 중지시키는 것

    try{
        const formData = new FormData();
        formData.append('username', user.username);
        formData.append('password', user.password);

        console.log(formData.get('username'));
        console.log(formData.get('password'));

        // axios 백엔드 호출
        const resp = await axios({
          url: 'http://localhost:8080/api/member/login',    // Rest API 호출
          method: 'POST',   // GET, POST, DELETE, PUT, ...
          data: formData,
          withCredentials: true,
        });

        if(resp.status == 200) {
          // ...
        }

    } catch(error) {
      console.log('로그인 에러: ' + error);
      alert("로그인 실패!");

    }
  }

  return (
    <div className="container card form-register"
         style={{ maxWidth: '400px', padding: '1rem' }}>
      <div>
        <div className="my-3 border-bottom">
          <h4 className="text-start">로그인</h4>
        </div>
        <form onSubmit={handleSubmit}>
          <div className="text-start mb-3">
            <label htmlFor="username" className="form-label">사용자이름</label>
            <input type="text" name="username"
                   placeholder="사용자이름" className="form-control" required
                   value={user.username} onChange={handleChange} />
          </div>
          <div className='text-start mb-3'>
            <label htmlFor="password" className="form-label">비밀번호</label>
            <input type="password" name="password"
                   placeholder="비밀번호" className="form-control" required
                   value={user.password} onChange={handleChange} />
          </div>

          <button type="submit" className='btn btn-primary me-2'>로그인</button>
          <Link to={'/home'} className='btn btn-secondary'>취소</Link>
        </form>
      </div>
    </div>
  );
}

export default Login;
  • react에서는 <label> 태그 속성 for -> htmlFor

 

(BackEnd) restcontroller/RestMemberController.java 생성
package com.eunji.backboard.restcontroller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.eunji.backboard.dto.Header;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.service.MemberService;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import java.util.Map;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;


@RequiredArgsConstructor
@RequestMapping("/api/member")
@RestController
@Log4j2
public class RestMemberController {
 
    private final MemberService memberService;

    @PostMapping("/login")
    public Header<Member> login(@RequestParam Map<String, String> logInfo) {
        log.info(String.format("▶▶▶ React에서 넘어온 정보: %s", logInfo.get("username")));

        // 계정정보 객체
        // Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = logInfo.get("username");
        String password = logInfo.get("password");

        try{
            Member member = this.memberService.getMemberByUsernameAndPassword(username, password);
    
            if(member != null) {
                Header<Member> result = Header.OK(member);
                return result;
            } else {
                Header<Member> fail = Header.OK("Member Not Found");
                return fail;
            }

        } catch(Exception e) {
            log.catching(e);
            Header<Member> fail = Header.OK("Member Not Found");
            return fail;

        }
    }
}

 

(BackEnd) service/MemberService.java getMemberByUsernameandPassword() 메서드 생성
// 24.07.04 React에서 넘어온 정보로 로그인 확인하기
  public Member getMemberByUsernameAndPassword(String username, String password) {
    Optional<Member> _member = this.memberRepository.findByUsername(username);
    Member realMember;

    if(_member.isPresent()) {
      realMember = _member.get();   // 같은 이름의 사용자 정보가 다 넘어옴(암호화된 비밀번호까지)

      // plain text와 암호화된 값이 같은 값을 가지고 있는지 체크
      boolean isMatched = passwordEncoder.matches(password, realMember.getPassword());

      if(isMatched) 
        return realMember;
      else 
        throw new NotFoundException("Member not found!");

    } else {
      throw new NotFoundException("Member not found!");

    }
  }

 

 

 

  • postman으로 테스트

  • member 객체 정보 나오는 것 확인할 수 있음

 

(FrontEnd) Login.js axios 부분 작성
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
// RestAPI -> axios
import axios from 'axios';

function Login() {
  // 변수
  const navigate = useNavigate();
  const [user, setUser] = useState({
    username: '',
    password: '',
  });   // useState() 괄호 안은 초기하는 값. json헝태(딕셔너리)

  // 함수
  // 값이 바뀔 때마다 user에 값을 넣음
  const handleChange = (e) => {
    const { name, value } = e.target; //target=> username, password 둘 중 하나
    setUser({ ...user, [name]: value});
  }

  // 핵심
  // async function handleSubmit(e){}
  const handleSubmit = async (e) => {
    e.preventDefault(); // submit동안 다른 이벤트가 발생하지 않도록 중지시키는 것

    try{
        const formData = new FormData();
        formData.append('username', user.username);
        formData.append('password', user.password);

        console.log(formData.get('username'));
        console.log(formData.get('password'));

        // axios 백엔드 호출
        const resp = await axios({
          url: 'http://localhost:8080/api/member/login',    // Rest API 호출
          method: 'POST',   // GET, POST, DELETE, PUT, ...
          data: formData,
          withCredentials: true,
        });

        console.log(resp);
        if(resp.data.resultCode == 'OK') {
          const { email, mid, role, username} = resp.data.data;
          const transactionTime = resp.data.transactionTime;
          console.log(email, mid, role, username);
          // localStorage에 저장
          localStorage.setItem("username", username);
          localStorage.setItem("email", email);
          localStorage.setItem("mid", mid);
          localStorage.setItem("role", role);
          localStorage.setItem("loginDt", transactionTime);
          console.log(localStorage);

          alert('로그인 성공');
          // 다른페이지로 데이터 전달
          navigate("/home", {data: {userData: resp.data.data}});

        } else {
          alert('로그인 실패')
        }
    } catch(error) {
      console.log('로그인 에러: ' + error);
      alert("로그인 실패!");

    }
  }
  • localStorage에 데이터 저장

 

Home.js, localStorage 사용해서 로그인 정보 출력
function Home() {
  var username, email, role, loginDt;

  if(localStorage != null) {
    username = localStorage.getItem("username");
    email = localStorage.getItem("email");
    role = localStorage.getItem("role");
    loginDt = localStorage.getItem("loginDt");
  }

  return (
    <div className='container card' style={{maxWidth: '350px'}}>
      <h4>로그인정보</h4>
      <div>
        <label className='form-label'>
          {username}
        </label><br />
        <label className='form-label'>
          {email}
        </label><br />
        <label className='form-label'>
          {role}
        </label><br />
        <label className='form-label'>
          {loginDt}
        </label>
      </div>
    </div>
  );
}

export default Home;
  • localStorage에 저장된 값 변수에 담아서 출력시켜줌

Header.js에서 로그아웃 기능 추가

  // 로그아웃
  function logout() {
    localStorage.removeItem('username');
    localStorage.removeItem('email');
    localStorage.removeItem('mid');
    localStorage.removeItem('role');
    localStorage.removeItem('loginDt');
    window.location.reload();
  }
  
  <div className='col-md-3 text-end me-3'>
          {localStorage.getItem("username") != null ? (
            <button type='button' className='btn btn-outline-primary' onClick={logout}>로그아웃</button>
          ) : (
            <>
            <button type='button' className='btn btn-outline-primary me-2' onClick={gotoLogin}>로그인</button>
            <button type='button' className='btn btn-outline-primary'>회원가입</button>
            </>
          )}
</div>
  • 로그인, 로그아웃 시 버튼 분기태우기

 

더보기

localStorage 사용방법

// 데이터 저장하기
localStorage.setItem("key", value);

// 데이터 읽기
localStorage.getItem("key");

// 데이터 삭제
localStorage.removeItem("key");

// 모든 데이터 삭제
localStorage.clear();

// 저장된 키/값 쌍의 개수
localStorage.length;