Jun's Blog

홈페이지 만들어보기[로그인](with SpringBoot) - (5) 본문

WEB/React

홈페이지 만들어보기[로그인](with SpringBoot) - (5)

luckydadit 2025. 2. 19. 13:02

1. 로그인 이벤트 처리하기

 

앞서 회원가입 처리에서 추가한 회원 정보 2명입니다.

application.properties에 수정한 부분(with. IntelliJ)
# DDL 관련 처리 과정에 대해서 설정하는 방식. 초기에는 create, 프로젝트 중후반에는 update
spring.jpa.hibernate.ddl-auto=update

 

LoginPage.js에 수정한 부분(with. VSCode)
import axios from "axios";
import { useState } from "react";
import { Button, Card, Form, Alert } from "react-bootstrap";
import { useNavigate } from "react-router-dom";
function App({setUesr}) {
    // setUser : 메인 페이지에서 넘겨 주는 props(로그인 여부를 저장)
    console.log('로그인');

    // 로그인 관련 state
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const navigate = useNavigate();

    // 오류 메시지 관련 state
    const [error, setError] = useState('');

    const handleLogin = async (event) => {
        event.preventDefault();

        try{
            const response = await axios.post(
                "http://localhost:9000/member/login",
                {email, password}
            );

            // message : 로그인 성공 여부 메시지
            // member : 로그인 한 회원의 정보
            const {message, member} = response.data;

            if(message === '로그인 성공'){
                console.log(member);
                setUesr(member); // 로그인 성공시 사용자 정보 저장하기
                navigate('/'); // 홈페이지로 이동
            }else{
                // 로그인 실패 메세지(예시 : email, 비밀 번호 오류 등등)
                setError(message);
            }
        }catch(error){
            if(error.response){
                setError(error.response.data.message || '로그인 실패');
            }else{
                setError('Server Error');
            }
        };

    }
            {/* 오류 메세지 표시 */}
            {error && <Alert variant="danger">{error}</Alert>}
                    <Form.Control
                        type="text"
                        placeholder="이메일을(를) 입력해 주세요."
                        onChange={(event) => setEmail(event.target.value)}
                        required
                        style={{ marginRight: "10px" }} // 오른쪽 여백 추가
                    />
                    <Form.Control
                        type="password"
                        placeholder="비밀번호를(를) 입력해 주세요."
                        onChange={(event) => setPassword(event.target.value)}
                        required
                        style={{ marginRight: "10px" }} // 오른쪽 여백 추가
                    />

 

LoginPage.js에 추가 및 수정완료한 전체 소스 내용 (with. VSCode)
import axios from "axios";
import { useState } from "react";
import { Button, Card, Form, Alert, Container } from "react-bootstrap";
import { useNavigate } from "react-router-dom";

function App({ setUser }) {
    // setUser : 메인 페이지에서 넘겨 주는 props(로그인 여부를 저장)
    console.log('로그인');

    // 로그인 관련 state
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const navigate = useNavigate();

    // 오류 메시지 관련 state
    const [error, setError] = useState('');

    const handleLogin = async (event) => {
        event.preventDefault();

        try {
            const response = await axios.post(
                "http://localhost:9000/member/login",
                { email, password }
            );

            // message : 로그인 성공 여부 메시지
            // member : 로그인 한 회원의 정보
            const { message, member } = response.data;

            if (message === '로그인 성공') {
                console.log(member);
                setUser(member); // 로그인 성공시 사용자 정보 저장하기
                navigate('/'); // 홈페이지로 이동
            } else {
                // 로그인 실패 메세지(예시 : email, 비밀 번호 오류 등등)
                setError(message);
            }
        } catch (error) {
            if (error.response) {
                // error.response.data.message가 없으면 로그인 실패로 표시
                setError(error.response.data.message || '로그인 실패');
            } else {
                setError('Server Error');
            }
        };

    }

    return (
        <Container className="d-flex justify-content-center align-items-center" style={{ height: '70vh' }}>
            <Card style={{ width: '30rem' }}>
                <Card.Body>
                    <h2 className="text-center mb-4">로그인</h2>

                    {/* 오류 메세지 표시 */}
                    {error && <Alert variant="danger">{error}</Alert>}

                    <Form onSubmit={handleLogin}>
                    <Form.Group controlId="email" className="mb-3">
                            <Form.Label>이메일</Form.Label>
                            <Form.Control
                                type="email"
                                placeholder="이메일을 입력하세요"
                                onChange={(event) => setEmail(event.target.value)}
                                required
                            />
                        </Form.Group>
                        <Form.Group controlId="password" className="mb-3">
                            <Form.Label>비밀번호</Form.Label>
                            <Form.Control
                                type="password"
                                placeholder="비밀번호를 입력하세요"
                                onChange={(event) => setPassword(event.target.value)}
                                required
                            />
                        </Form.Group>

                        <Button variant="primary" type="submit" className="d-block w-100">
                            로그인
                        </Button>
                    </Form>
                </Card.Body>
            </Card>
        </Container>
    );
}

export default App;

 

App.js에 수정한 부분(with. VSCode)
            <Routes>
                {/* path에는 요청 url 정보, element에는 컴포넌트 이름 */}
                <Route path='/' element={<HomePage/>}/>
                <Route path='/member/login'  element={<LoginPage setUser={handleLoginSuccess} />}/>
                <Route path='/member/signup' element={<SignupPage/>}/>
            </Routes>
    // 로그인 여부를 알수 있는 사용자 정보
    const [user, setUser] = useState(null);

    const handleLoginSuccess = (userData) => {
        // userData : 로그인 페이지에서 넘겨 주는 로그인 성공한 사용자 정보
        setUser(userData);
        // 로컬 스토리지에 데이터 저장
        // JSON.stringify : JSON 파일을 문자열화 시킨다는 의미입니다.
        localStorage.setItem('user', JSON.stringify(userData));
        console.log('로그인 성공하였습니다.');
    };

 

App.js에 추가 및 수정완료한 전체 소스 내용 (with. VSCode)

 

import 'bootstrap/dist/css/bootstrap.min.css';

import { Container, Nav, Navbar } from 'react-bootstrap';

import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';

// 라우팅 관련 컴포넌트
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import SignupPage from './pages/SignupPage';
import { useState } from 'react';

function App(){
    // 로그인 여부를 알수 있는 사용자 정보
    const [user, setUser] = useState(null);

    const handleLoginSuccess = (userData) => {
        // userData : 로그인 페이지에서 넘겨 주는 로그인 성공한 사용자 정보
        setUser(userData);
        // 로컬 스토리지에 데이터 저장
        // JSON.stringify : JSON 파일을 문자열화 시킨다는 의미입니다.
        localStorage.setItem('user', JSON.stringify(userData));
        console.log('로그인 성공하였습니다.');
    };

    return(
        <Router>
            {/* 상단 네비게이션 바 */}
            <Navbar bg='dark' variant='bg' expand='lg'>
                <Container>
                    <Navbar.Brand style={{color:"white"}} href='/'>
                        Lucky Coffee Shop
                    </Navbar.Brand>
                    <Nav className="ms-auto">
                        <Nav.Link style={{color:"white"}} href='/member/login'>로그인</Nav.Link>
                        <Nav.Link style={{color:"white"}} href='/member/signup'>회원 가입</Nav.Link>
                    </Nav>
                </Container>
            </Navbar>

            <Routes>
                {/* path에는 요청 url 정보, element에는 컴포넌트 이름 */}
                <Route path='/' element={<HomePage/>}/>
                <Route path='/member/login'  element={<LoginPage setUser={handleLoginSuccess} />}/>
                <Route path='/member/signup' element={<SignupPage/>}/>
            </Routes>
           
            {/* 푸터 */}
            <footer className="bg-dark text-light text-center py-3 mt-5">
                <p>© 2025 Lucky Coffee Shop. All rights reserved.</p>
            </footer>
        </Router>
    );
}

export default App;

 

memberController.java에 추가 및 수정한 부분(with. IntelliJ)
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody Member bean, HttpServletRequest request){
    // Map<String, Object> : react의 LoginPage.js 파일 내의 const {message, member} = response.data;와 관련됨
    // bean : 클라이언트에서 넘겨준 로그인 정보를 담고 있는 객체
    // member : 데이터 베이스에서 내가 조회한 회원 정보 객체
    Member member = null;

    // response : 클라이언트에게 넘겨주고자 하는 응답 정보의 모음
    Map<String, Object> response = new HashMap<String, Object>();

    if(member == null){
        response.clear();
        response.put("message", "해당 이메일이 존재하지 않습니다.");
        // 응답 코드 401 : Unauthorized(인증 실패)
        return ResponseEntity.status(401).body(response);
    }

    boolean isCheckPassword = false;

    isCheckPassword = member.getPassword().equals((bean.getPassword()));

    if(isCheckPassword == false){
        response.clear();
        response.put("message", "비밀 번호가 일치하지 않습니다.");
        // 응답 코드 401 : Unauthorized(인증 실패)
        return ResponseEntity.status(401).body(response);
    }

    /*
    컨트롤러 메소드 : 클라이언트의 요청에 대한 응답을 해주는 메소드 ex)login
    컨트롤러 메소드의 매개 변수에 다양하게 사용 됩니다. ex) HttpServletRequest
    */
    
    // 로그인 성공시 session 영역에 로그인 정보 바인딩
    HttpSession session = request.getSession();
    session.setAttribute("user", member);

    response.clear();
    response.put("message", "로그인 성공");
    response.put("member", member);
    return ResponseEntity.ok(response);
}

 

MemberService.java 파일 추가 (with. IntelliJ)

package com.coffee.service;

import com.coffee.entity.Member;
import com.coffee.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor // final 키워드에 대하여 자동으로 객체 주입을 해줌
public class MemberService {
    private final MemberRepository memberRepository;
    
    public Member findByEmail(String email){
        return this.memberRepository.findByEmail(email);
    }
    
}

 

memberController.java에 추가 및 수정한 부분(with. IntelliJ)
@Autowired
private MemberService memberService;

@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody Member bean, HttpServletRequest request){
    // Map<String, Object> : react의 LoginPage.js 파일 내의 const {message, member} = response.data;와 관련됨
    // bean : 클라이언트에서 넘겨준 로그인 정보를 담고 있는 객체
    // member : 데이터 베이스에서 내가 조회한 회원 정보 객체
    Member member = this.memberRepository.findByEmail(bean.getEmail());

 

<적용 후, 로그인 테스트 결과>

 

 

<잘못된 로그인 정보를 입력한 후, 로그인 시도한 결과>

 

2. 로그인 처리 후, 사용자별 결과 화면 처리하기

App.js에 수정한 부분(with. VSCode)
    const MenuItems = () => {
        switch(user?.role){ // user가 유의미한 데이터 일경우에는 role 정보를 가져오라는 의미임.
            case 'ADMIN':
                return(
                <>
                    <Nav.Link style={{color:"white"}} href='/member/logout'>로그 아웃</Nav.Link>
                </>
                );
            case 'USER':
                return(
                <>
                    <Nav.Link style={{color:"white"}} href='/member/logout'>로그 아웃</Nav.Link>
                </>
                );
            default:
                return(
                <>
                    <Nav.Link style={{color:"white"}} href='/member/login'>로그인</Nav.Link>
                    <Nav.Link style={{color:"white"}} href='/member/signup'>회원 가입</Nav.Link>
                </>
                );
        }
    };
                    <Nav className="me-auto">
                        <MenuItems />
                    </Nav>

 

<로그인 성공 후, 화면 처리 1차 테스트 결과>

관리자 계정(Role : ADMIN) 로그인 시

 

사용자 계정(Role : USER) 로그인 시

 

 

로그인 성공 후, 새로 고침(F5)를 누르면 화면이 다시 로그인 전 화면으로 되돌아가는 문제가 발생합니다.
이 문제를 해결 하기 위해 아래와 같이 진행합니다.

 

https://www.w3schools.com/react/react_useeffect.asp

 

W3Schools.com

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

App.js에 수정한 부분(with. VSCode)
import { useEffect, useState } from 'react';
    // 페이지 새로 고침시(F5) 로그인 상태를 localStrorage에서 다시 읽어 오기
    // 우리는 한번만 rendering되어야 하므로, 빈 배열 []을 2번째 매개 변수로 넣어 줍니다.
    useEffect(() => {
        const loginUser = localStorage.getItem('user');
        // JSON.parse : 문자열을 JSON 파일화 시킨다는 의미입니다.
        setUser(JSON.parse(loginUser)); // 로컬 스토리지에서 데이터 읽어 오기
    }, []);

 

<로그인 성공 후, 화면 처리 2차 테스트 결과>

새로 고침(F5)를 수행해도 로그인 상태가 유지됨