본문 바로가기

스프링

스프링 JWT(1): 프로젝트 생성

이번에는 JWT를 위해 엔티티, 시큐리티 설정 등을 해보려고 한다.

 

프로젝트 생성

우선 의존성은 다음과 같다.

  • Spring Web
  • Data JPA
  • Lombok
  • Spring Security
  • MySQL Driver

MySQL 연동을 위해 다음과 같이 설정해주자.

spring.application.name=jwt_practice

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/{db이름}?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true

spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect

# DB username
spring.datasource.username={username}

# DB password
spring.datasource.password={password}

spring.jpa.show-sql=true

# DDL(create, alter, drop
spring.jpa.hibernate.ddl-auto=update

 

SecurityConfig

package com.example.jwt_practice.security;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig{

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception
    {
        http
                .csrf(csrf -> csrf.disable());

        http
                .formLogin(formLogin -> formLogin.disable());

        http
                .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        http
                .authorizeHttpRequests( authorizeRequests -> authorizeRequests.anyRequest().permitAll());

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception
    {
        return authenticationConfiguration.getAuthenticationManager();
    }
}
  • csrf.disable(): csrf는 인증정보를 쿠키에 저장하는 경우 발생하는 문제로, 헤더에 토큰을 직접 명시하는 JWT 인증에서는 크게 신경쓸 필요는 없으며, 개발환경이기에 disable
  • formLogin.disable():
  • ssession.sessionCreationPolicy(SessionCreationPolicy.STATELESS): 세션을 서버에서 관리하지 않으므로 STATELESS로 설정

 

UserDetailsService

유저정보를 DB에서 가져올 것이므로 CustomUserDetailsService를 직접 구현해야한다.

package com.example.jwt_practice.security;

import com.example.jwt_practice.user.domain.Role;
import com.example.jwt_practice.user.domain.User;
import com.example.jwt_practice.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        Optional<User> _user = userRepository.findByUsername(username);
        if (_user.isEmpty())
        {
            throw new UsernameNotFoundException(username);
        }
        User user = _user.get();
        List<GrantedAuthority> authorities = new ArrayList<>();

        if (username.equals("admin"))
        {
            authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
        }
        else
        {
            authorities.add(new SimpleGrantedAuthority(Role.USER.getValue()));
        }

        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
    }
}

username이 “admin”인 경우 관리자 권한을 주도록 구현했다.

 

User

package com.example.jwt_practice.user.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@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;

    public User(String username, String password)
    {
        this.username = username;
        this.password = password;
    }
}

간단하게 구현할 것이므로 username과 password만 속성으로 추가했다.

 

UserRegisterDto

package com.example.jwt_practice.user.domain;

import lombok.Data;

@Data
public class UserRegisterDto {

    private String username;
    private String password;
}

회원가입 요청 시 사용할 DTO

 

Role

package com.example.jwt_practice.user.domain;

import lombok.Getter;

@Getter
public enum Role {
    USER("ROLE_USER"),
    ADMIN("ROLE_ADMIN");

    private String value;

    Role(String value)
    {
        this.value = value;
    }
}

추후에 인증이 잘 이뤄지는지 확인하기 위해 두 가지 Role을 만들었다.

 

UserService

package com.example.jwt_practice.user.service;

import com.example.jwt_practice.user.domain.User;
import com.example.jwt_practice.user.domain.UserRegisterDto;
import com.example.jwt_practice.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public User register(UserRegisterDto userRegisterDto)
    {
        User user = new User(userRegisterDto.getUsername(), passwordEncoder.encode(userRegisterDto.getPassword()));
        return userRepository.save(user);
    }
}

 

회원가입을 처리한다. 예외처리는 생략.

 

UserRepository

package com.example.jwt_practice.user.repository;

import com.example.jwt_practice.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByUsername(String username);

}

 

UserController

package com.example.jwt_practice.user.controller;

import com.example.jwt_practice.user.domain.UserRegisterDto;
import com.example.jwt_practice.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/api/register")
    public ResponseEntity<String> register(@RequestBody UserRegisterDto userRegisterDto)
    {
        userService.register(userRegisterDto);
        return ResponseEntity.ok("회원가입 성공!");
    }
}

 

여기까지 진행 후 회원가입 api를 호출해 정상적으로 동작하는지 확인해보자.

 

제대로 동작하고, DB에도 저장되는 것을 확인이 가능하다.

 

다음으로

다음에는 본격적으로 JWT인증을 다룰 예정이다.

 

'스프링' 카테고리의 다른 글

[Spring Security] @PreAuthorize  (0) 2026.03.28
AWS S3: Post Presign Url이 만료되지 않는 문제  (0) 2026.01.20
스프링 JWT(3): JWTFilter  (3) 2025.01.09
스프링 JWT(2): JWTUtil & LoginFilter  (0) 2025.01.07