권한까지 구현되었으니 이제 로그인 기능을 구현해보겠습니다.
먼저, 서버 쪽을 하나씩 살펴보면
기본적으로 UserEntity와 UserDTO
그리고 LoginDTO가 있습니다.
LoginDTO.java
package com.example.demo.dto;
import lombok.*;
@Getter @Setter
@ToString
@NoArgsConstructor
@Data
public class LoginDTO {
private String userId;
private String password;
}
UserRepository.java
package com.example.demo.repository;
import com.example.demo.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> { // JpaRepository<Entity 클래스, PK 타입>
// 로그인
UserEntity findByUserId(String userId);
}
UserService.java
// 로그인
public JwtToken login(String userId, String password) {
try{
// 1. Login ID/PW 를 기반으로 Authentication 객체 생성
// 이때 authentication 는 인증 여부를 확인하는 authenticated 값이 false
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
// 2. 실제 검증 (사용자 비밀번호 체크)이 이루어지는 부분
// authenticate 매서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드가 실행
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// 3. 인증 정보를 기반으로 JWT 토큰 생성
JwtToken tokenInfo = jwtTokenProvider.generateToken(authentication);
return tokenInfo; }
catch (Exception e) {
System.err.println(e);
}
return null;
}
UserDetailService.java
package com.example.demo.service;
import com.example.demo.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
// 사용자 이름을 입력받아 해당 사용자의 정보를 검색하고
// 이를 Spring Security의 UserDetails 객체로 변환하여 반환
// loadUserByUsername: 로그인 시 실행되는 함수
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
return userRepository.findByUserId(userId);
}
}
UserController.java
@PostMapping("/login")
// param으로 보내는 경우
// public JwtToken login(@RequestParam(value="userId") String userId, @RequestParam(value="password") String password) {
// return userService.login(userId, password);
// }
// body에 담아 보내는 경우
public JwtToken login(@RequestBody LoginDTO loginDTO) {
String userId = loginDTO.getUserId();
String password = loginDTO.getPassword();
JwtToken tokenInfo = userService.login(userId, password);
return tokenInfo;
}
@RequestParam 어노테이션을 사용하면 아이디와 비밀번호를 각각의 쿼리 파라미터로 전달하면 요청 URL에 파라미터로 전달되게 됩니다.
@RequestBody 어노테이션을 사용하면 요청 본문(body)에 JSON 형식으로 아이디와 비밀번호를 담은 객체인 LoginDTO를 전달합니다. 이 경우에는 요청 본문에 JSON 형식으로 데이터가 포함되어 전달됩니다.
보안적인 특성상 본문에 담아 전달하겠습니다
다음으로는 클라이언트 측을 살펴보겠습니다.
import React, { useState } from "react";
import '../css/Login.css';
import Nav2 from "../components/Nav2";
import { NavLink, useNavigate } from "react-router-dom";
import axios from "axios";
import { useCookies } from "react-cookie";
let Login = () => {
// 아이디와 비밀번호 상태
let [userId, setUserId] = useState("");
let [password, setPassword] = useState("");
let navigate = useNavigate(); // useNavigate 훅 사용
// 아이디 입력 이벤트 핸들러
let onUserIdHandler = (e) => {
setUserId(e.target.value) // 입력된 아이디 상태 업데이트
console.log("userId: ", e.target.value);
}
// 비밀번호 입력 이벤트 핸들러
let onPasswordHandler = (e) => {
setPassword(e.target.value) // 입력된 비밀번호 상태 업데이트
console.log("password: ", e.target.value);
}
// 로그인 함수
let loginHandler = () => {
// 로그인 요청 보내기
axios({
url: 'http://localhost:8082/api/v1/auth/n/login', // 로그인 앤드포인트 URL
method: 'POST',
data: { // 요청 본문에 아이디와 비밀번호 데이터 전송
userId: userId,
password: password
}
})
.then((res) => {
// 로그인 성공
console.log(res.data)
if (res.status === 200) { // 응답 상태 코드가 200이면
setCookies('user', { userId, password }); // 쿠키에 사용자 정보 저장
alert("로그인 성공!");
navigate("/"); // 메인페이지로 이동
} else {
// 로그인 실패
alert("아이디 또는 비밀번호가 잘못되었습니다.");
}
})
.catch((error) => {
// 서버 오류 또는 네트워크 오류
alert("로그인에 실패하였습니다.");
console.error("Error:", error);
});
}
// JSX로 로그인 페이지 UI 렌더링
return (
<div>
<div>
<Nav2/> // 네비게이션 컴포넌트 렌더링
</div>
<div className="loginForm">
<div>
<h1 id='login_title'>로그인</h1>
</div>
<div>
<div className="input">
<input type="text" className="userId" id="userId" placeholder="아이디" value={userId} onChange={onUserIdHandler} autoFocus></input>
<input type="password" className="password" id="password" placeholder="비밀번호" value={password} onChange={onPasswordHandler}></input>
<button onClick={loginHandler}>Login</button>
</div>
<div className="link">
<NavLink to="/findId">아이디 찾기</NavLink>
<span> | </span>
<NavLink to="/findPw">비밀번호 찾기</NavLink>
<span> | </span>
<NavLink to="/register">회원가입</NavLink>
</div>
</div>
</div>
</div>
);
}
export default Login;
- useState 훅을 이용해 아이디와 비밀번호 상태를 설정하고 각각의 핸들러를 통해 상태 업데이트를 합니다.
- axios를 사용해 서버와 통신합니다.
- 컨트롤러에서 @RequestParam 을 사용한다면 data가 아닌 params를 써야 합니다.
- 컨트롤러에서 @RequestBody 를 사용한다면 data를 씁니다.
- 응답 상태 코드 (res.status) 가 200이면 로그인 성공이 되어 쿠키에 사용자 정보를 저장하게끔 합니다.
- html이 아니므로 form 태그를 쓰면 안됩니다.
- 로그인 버튼에 onClick 이벤트로 loginHandler가 연결되게끔 했습니다.
'Project 댕린이집' 카테고리의 다른 글
[Spring Security+JWT] HttpOnly방식을 이용한 refreshToken 발급 (0) | 2024.04.29 |
---|---|
[Spring Security+JWT] JWT는 어디에 저장해야할까? localStorage vs cookie (0) | 2024.04.16 |
[트러블슈팅] org.springframework.security.authentication.InternalAuthenticationServiceException (0) | 2024.04.08 |
[회원가입] 권한(Role) 추가 (0) | 2024.03.28 |
[로그인] Spring Security + JWT 로그인 구현 (0) | 2024.03.15 |