2025년 2월 16일에 작성됨
앞 포스팅에서는 차량에 대한 기본적인 CRUD 기능을 구현을 했다.
이제 웹애플리케이션에서 활용될 회원가입 및 인증 기능과 관련된 내용을 정리해서 포스팅할 것이다.
User 엔티티를 만들어야하는데 그 전에 테이블 설계부터 하도록 하겠다.
User 테이블 설계
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL
);
기본적으로 관리자와 일반 사용자 이렇게 두 개의 역할을 나누기로 했다.
관리자는 차량의 기본적인 CRUD 기능을 이용 가능하고, 일반 사용자는 조회 기능만 가능하다.
User 엔티티 생성
그 다음 단계로 User 엔티티를 생성한다.
package com.example.autofinder.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role = "USER"; // 기본 역할 설정
}
users 테이블과 매핑을 해주고, 기본 역할을 일반 사용자인 USER로 설정해준다.
UserRepository 생성
다음은 회원 정보를 데이터베이스에 저장하고 불러올 수 있도록 UserRepository를 생성한다.
package com.example.autofinder.repository;
import com.example.autofinder.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
UserService 생성
UserService
내 에서 회원가입 시 비밀번호 암호화를 해줄 것이다.
package com.example.autofinder.service;
import com.example.autofinder.model.User;
import com.example.autofinder.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User registerUser(String username, String password) {
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("이미 존재하는 사용자 이름입니다.");
}
String encodedPassword = passwordEncoder.encode(password); // 비밀번호 암호화
User user = new User();
user.setUsername(username);
user.setPassword(encodedPassword);
return userRepository.save(user);
}
// 로그인 (비밀번호 검증)
public boolean authenticateUser(String username, String password) {
Optional<User> user = userRepository.findByUsername(username);
return user.isPresent() && passwordEncoder.matches(password, user.get().getPassword());
}
}
위는 전체 코드이다.
이 중 passwordEncoder.encode(password);
부분에서 비밀번호를 암호화한다.
사용자가 입력한 비밀번호를 암호화하는 과정은 BCrypt 알고리즘을 이용해 암호화한다.
여기에서 BCrypt 알고리즘에 대해 짚고 넘어가자면,
비밀번호를 안전하게 암호화하는 해시 알고리즘이라고 한다.
비밀번호를 단순히 변환하는 게 아니라, 해커가 쉽게 풀 수 없도록 여러 차례 변환 과정을 거쳐 보안성을 높인다.
BCrypt의 주요 특징이 몇 가지 있는데 우선 첫 번째는 암호화된 비밀번호는 복호화할 수 없다.
즉, 원래 비밀번호를 다시 얻을 수 없고, 대신 로그인할 때 입력한 비밀번호를 암호화된 값과 비교하여 일치 여부만 확인이 가능하다.
그 다음 특징은 같은 비밀번호라도 암호화할 때마다 다른 값이 생성된다. 이는 해킹 방지에 효과적이다.
마지막으로는 일부로 연산을 느리게 만들어 해킹 시도를 어렵게 한다.
암호화된 비밀번호를 User
객체에 저장하고, 데이터베이스에 저장한다.
암호화된 비밀번호를 비교할 때에는 passwordEncoder.matches(입력한 비밀번호, DB에 저장된 암호화된 비밀번호)
에서 true, false 값이 나오게 된다.
값이 true라면 비밀번호 일치, false라면 비밀번호 불일치에 해당하는 값이다.
AuthController 생성
다음은 회원가입 요청을 처리하는 컨트롤러인 AuthController
를 만들어야한다.
package com.example.autofinder.controller;
import com.example.autofinder.model.User;
import com.example.autofinder.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
// 회원가입
@PostMapping("/register")
public ResponseEntity<User> register(@RequestBody Map<String, String> request) {
String username = request.get("username");
String password = request.get("password");
User newUser = userService.registerUser(username, password);
return ResponseEntity.ok(newUser);
}
// 로그인
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody Map<String, String> request) {
String username = request.get("username");
String password = request.get("password");
if (userService.authenticateUser(username, password)) {
return ResponseEntity.ok("로그인 성공! (JWT 토큰 필요)");
} else {
return ResponseEntity.status(401).body("로그인 실패: 사용자명 또는 비밀번호가 잘못되었습니다.");
}
}
}
AuthController
는 프론트엔트(클라이언트)에서 회원가입과 로그인 API를 호출하면, 이 컨트롤러가 해당 요청을 받아 회원 정보를 저장하거나 검증하는 기능을 수행한다.@RequestMapping("/api/auth")
를 작성하여 모든 API 경로가 /api/auth
로 시작하도록 설정해준다.
String username = request.get("username");
, String password = request.get("password");
부분은 요청에서 사용자명과 비밀번호를 추출한다.
본격적으로 User newUser = userService.registerUser(username, password);
에서 회원가입 로직을 처리를 하고, 가입된 사용자 정보를 JSON 응답으로 반환하게 된다.
로그인 로직도 비슷하다.
요청에서 사용자명과 비밀번호를 추출한 다음,userService.authenticateUser(username, password)
사용자 정보가 데이터베이스에 있는지 확인하고, 비밀번호가 일치하는지 검증까지 한다.
SecurityConfig 생성
마지막으로 Spring Security 설정을 정의하는 파일인 SecurityConfig
을 다를 것이다.
스프링 부트(Spring Boot) 애플리케이션에서 보안 정책을 관리하고, 사용자의 인증과 권한을 설정하는 역할을 한다.
여기에서 Spring Security에 대해 간단히 짚고 넘어가자면
- Spring Boot 애플리케이션의 보안을 담당하는 프레임워크이다.
- 기본적으로 모든 요청을 인증(로그인) 없이 접근하지 못하도록 설정한다.
- 개발자가 설정을 변경하여 인증이 필요한 경로 혹은 인증이 필요 없는 경로를 직접 정의할 수 있다.
package com.example.autofinder.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.disable()) // CORS 설정 (추후 필요하면 활성화)
.csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화 (API 사용 시 필요)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 안 함
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // 회원가입, 로그인은 인증 없이 가능
.anyRequest().authenticated() // 나머지 요청은 인증 필요
)
.formLogin(form -> form.disable()); // 기본 폼 로그인 비활성화
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration
: Spring 설정 클래스임을 나타낸다..cors(cors -> cors.disable())
: 다른 도메인에서 API를 호출할 수 있도록 하려면 CORS 설정을 추가해야하지만, 현재는 비활성화 상태이다. API를 쓰지 않기 때문이다..csrf(csrf -> csrf.disable())
: CSRF 공격을 방어하는 보안 기능이지만, REST API 서버에서는 보통 비활성화한다고 한다.
다음은 인증 설정이다..requestMatchers("/api/auth/**").permitAll()
: /api/auth/**
경로의 요청은 인증 없이 누구나 접근 가능하다. 하지만 그 이외의 모든 요청은 인증이 필요하다. 즉, 로그인한 사용자만 접근 가능하다는 의미이다.)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
마지막으로 BCryptPasswordEncoder
를 사용하여 비밀번호를 안전하게 암호화한다.
결론
이번 포스팅을 마치며 비밀번호 암호화 과정뿐만 아니라 기본적인 회원가입 로직에 대해 다시 점검할 수 있었던 것 같다.
사실 비밀번호 암호화 알고리즘인 BCrypt 알고리즘에 대해 깊게 생각해보진 않았다. 확실히 블로그를 작성하며 내가 몰랐던 부분에 대해 더 깊게, 자세하게 배울 수 있어 좋은 것 같다.
추가로 앞으로 포스팅할 내용은 JWT에 대해서 다뤄보도록 하겠다.
'SIDE PROJECT > AUTOFINDER' 카테고리의 다른 글
백엔드 코드 개선 및 쿼리문 수정 (0) | 2025.04.17 |
---|---|
JWT를 활용한 로그인 및 API 인증 처리 (0) | 2025.04.17 |
CRUD 기능 구현 (0) | 2025.04.17 |
스프링 부트(Spring Boot)를 이용한 서버 구축 (0) | 2025.04.17 |
데이터 저장 방식의 변경 (MySQL 사용) (0) | 2025.04.17 |