LUMI_dev

사용자 관리하기 - #1. 회원가입 구현 (구현 편) / 양방향, 단방향 암호 알고리즘 본문

스파르타 코딩 클럽 | 자바 심화 과정/Spring Master (숙련 주차)

사용자 관리하기 - #1. 회원가입 구현 (구현 편) / 양방향, 단방향 암호 알고리즘

luminous_dev 2025. 2. 5. 01:52

User 테이블

컬럼명 컬럼타입 중복허용 설명
id Long X 테이블 ID (PK)
username String X 회원 ID
password String O 패스워드
email String X 이메일 주소
role String O 역할 1) 사용자: USER 2) 관리자: ADMIN

 

 

User.java

package com.sparta.springauth.entity;

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

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users") //여기에 작성한 이름대로 테이블이 생길 거임
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING) //만약 여기에 UserRoleEnum의 USER(Authority.USER)를 넣으면 그 값이 생긴 그대로 들어감 - USER로 저장될 것임  
    private UserRoleEnum role;
}

 

@Enumerated(value = EnumType.STRING)
  • 데이터 enum타입을 데이터 베이스 컬럼에 저장할 때 사용
  • EnumType.STRING이라는 옵션 사용 : enum의 이름 그대로를 데이터베이스에 저장 
  • ex) USER(Authority.USER) → USER

관리자 회원 가입 인가 방법 (토큰 x)

  • 관리자 권한을 부여할 수 있는 관리자 페이지 구현
  • 승인자에 의한 결재 과정 구현 → 관리자 권한 부여 

패스워드 암호화 

비밀번호는 암호화 (Encryption)가 의무 

  • 암호화 후 패스워드 저장이 필요 합니다.
    • 평문 → (암호화 알고리즘) → 암호문
      복호화가 불가능한 '단방향'암호 알고리즘 사용이 필요

양방향 ↔ 단방향 암호화 알고리즘 

양방향 암호 알고리즘

  • 암호화: 평문 → (암호화 알고리즘) → 암호문
  • 복호화: 암호문 → (암호화 알고리즘) → 평문

단방향

  • 암호화: 평문 → (암호화 알고리즘) → 암호문
  • 복호화: 불가 (암호문 → (암호화 알고리즘) → 평문)
Q. 그럼 사용자가 로그인할 때는 암호화된 패스워드를 기억해야하나?

Password 확인절차 사용자가 로그인을 위해 "아이디, 패스워드 (평문)" 입력
→ 서버에 로그인 요청 서버에서 패스워드 (평문) 을 암호화 평문 → (암호화 알고리즘)
암호문 DB 에 저장된 "아이디, 패스워드 (암호문)"와 일치 여부 확인

User.java (Entity)

package com.sparta.springauth.entity;

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

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users") //여기에 작성한 이름대로 테이블이 생길 거임
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING) //만약 여기에 UserRoleEnum의 USER(Authority.USER)를 넣으면 그 값이 생긴 그대로 들어감 - USER로 저장될 것임
    private UserRoleEnum role;

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

 

UserRoleEnum.java(Entity) 

package com.sparta.springauth.entity;

public enum UserRoleEnum {


    USER(Authority.USER),  // 사용자 권한 //Authority.USER 여기에 값을 넣어줄 수 있음 - 여기 넣는 값은 생성자가 됨 
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() { //getAuthority하면 ROLE_USER,ROLE_ADMIN가져올 수 있음
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";//USER은 ROLE_USER라는 이름을 갖게 됨
        public static final String ADMIN = "ROLE_ADMIN"; //ADMIN은 ROLE_ADMIN이라는 이름을 갖게 됨

    }
}

 

 

HomeController.java

package com.sparta.springauth.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
//메인 페이지에 가기 위해 만듦
    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("username", "username");
        return "index";
    }
}

 

AuthController 단

package com.sparta.springauth.auth;

import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api")
public class AuthController {
    public static String AUTHORIZATION_HEADER = "Authorization";

    private final JwtUtil jwtUtil;

    public AuthController(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    // jwt를 받아오지 못해서 jwtUtil에 빨간 줄
    //JwtUtil.java 보면 jwt를 컴포넌트(@Component) 로 지정해놓음 - Bean이니까 가져오기

    //JWT를 만드는 코드
    @GetMapping("/create-jwt")
    public String createJwt(HttpServletResponse res) {

        // Jwt 생성
        String token = jwtUtil.createToken("Robbie", UserRoleEnum.USER);//UserRoleEnum.USER 권한 = 일반 유저 

        // Jwt 쿠키 저장
        jwtUtil.addJwtToCookie(token, res); //JWT에서 만든 것 사용


        return "createJwt : " + token;
    }


    @GetMapping("/get-jwt")
    public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
                                        //토큰의 name부분이 Authorization

        // JWT 토큰 substring
        String token = jwtUtil.substringToken(tokenValue);

        // 토큰 검증
        if(!jwtUtil.validateToken(token)){
            throw new IllegalArgumentException("Token Error");
        }

        // 토큰에서 사용자 정보 가져오는 법
        Claims info = jwtUtil.getUserInfoFromToken(token);

        // 사용자 username 가져오는 법
        String username = info.getSubject();
        System.out.println("username = " + username);

        // 사용자 권한 가져오는 법
        String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY); //JwtUtil의  .claim(AUTHORIZATION_KEY, role) // 사용자 권한 .claim(key,value) 참고
        
        System.out.println("authority = " + authority);

        return "getJwt : " + username + ", " + authority;
    }
}

 

 

 

UserController 단

package com.sparta.springauth.controller;

import com.sparta.springauth.dto.SignupRequestDto;
import com.sparta.springauth.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/api")
public class UserController {

    //주입
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    //로그인 페이지
    @GetMapping("/user/login-page")
    public String loginPage() {
        return "login";
    }

    //회원 가입 페이지
    @GetMapping("/user/signup")
    public String signupPage() {
        return "signup";
    }

    @PostMapping ("/user/signup")
    public String signup(SignupRequestDto requestDto) { //객체로 받고 있음
       userService.signup(requestDto);

        return "redirect:/api/user/login-page";
    }
}

 

서비스 단

package com.sparta.springauth.service;

import com.sparta.springauth.dto.SignupRequestDto;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class UserService {

    private final UserRepository userRepository; //interface
    private final PasswordEncoder passwordEncoder;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    // ADMIN_TOKEN
    private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";


    //회원 가입
    public void signup(SignupRequestDto requestDto) {
        String username = requestDto.getUsername();
        
        //암호화된 패스워드
        String password = passwordEncoder.encode(requestDto.getPassword());

        // 회원 중복 확인
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
        }

        // email 중복확인
        String email = requestDto.getEmail();
        Optional<User> checkEmail = userRepository.findByEmail(email);
        if (checkEmail.isPresent()) { //.isPresent() : 값이 존재하는지 아닌지
            throw new IllegalArgumentException("중복된 Email 입니다.");
        }

        // 사용자 ROLE 확인
        UserRoleEnum role = UserRoleEnum.USER;//사용자 권한을 일단 설정해둠
        if (requestDto.isAdmin()) { //.isAdmin하면 Dto의 boolean admin값 가져옴
            if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
                throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
            }
            role = UserRoleEnum.ADMIN; //어드민 권한으로 덮어씀
        }

        // 사용자 등록
           //테이블의 한 행에 해당하는 데이터를 만들어야 함
        User user = new User(username, password, email, role);
        userRepository.save(user); //repository에 의해 저장
    }
}

 

 

service단/ repository 단

 

회원 중복 체크

User.java에서 유니크 키로 설정 

 

 

.isAdmin() 하면 DTO의 boolean 값을 가져옴 

규칙) boolean은 is로 시작함  

 

 

 

서비스단 사용자 등록 /User.java에 생성자 생성

 

Repository 단

 

 

package com.sparta.springauth.repository;

import com.sparta.springauth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
                                                    //users 테이블의 Entity 객체를 넣어야 함
         Optional<User> findByUsername(String username);
         Optional<User> findByEmail(String email);

}

 

 

SignupRequestDto

package com.sparta.springauth.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SignupRequestDto {
    private String username;
    private String password;
    private String email;
    private boolean admin = false;
    private String adminToken = "";
}

 

 

관리자 로그인