Spring Security + JWT를 이용한 회원가입 + 로그인 #1

Woongbin·2023년 1월 2일
0
post-thumbnail

JWT

JWT(Json Web Token)는 인증에 필요한 정보들을 암호화 시킨 Json Token을 말한다. JWT는 세션과 다르게 클라이언트에 저장되어 서버의 부담을 줄일 수 있다.

JWT의 동작방식은 이렇다.

클라이언트에서 로그인 요청을 보내면, 서버에서 검증을 한후 Access TokenRefresh Token을 발급해준다.
그리고 클라이언트에서 Access Token을 헤더에 담아서 서버에 요청하면 API를 사용할 수 있게 된다.
이 때 Refresh Token은 Access Token이 만료 되었을 때 Access Token을 재발급해주는 토큰이다.
Access Token은 정보가 유출될 수 있어 유효기간을 짧게 설정하기 때문에 Access Token이 만료 되면 Refresh Token을 통해 재발급 받을 수 있다.

※ Access Token이 만료 되었을 때 Refresh Token을 통해 재발급 받을 수 있기 때문에 기본적으로 Access Token보다 Refresh Token의 유효기간이 길어야 한다는 것을 알고 있어야 한다.

설명은 간단하게 하고 이제 회원가입과 로그인 예제를 통해 알아보자.

설정

dependencies

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

securityjwt를 사용할 예정이기 때문에 그에 대한 설정을 추가해준다. 데이터베이스는 mysql 을 사용할 것이다.


application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/데이터베이스 이름?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: ${USER} // 유저 이름
    password: ${PASSWORD} // 비밀번호

  security:
    jwt:
      header: Authorization
      secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
      token-validity-in-seconds: 86400

  jpa:
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    generate-ddl: true
    hibernate:
      ddl-auto: create
    show_sql: true
    format_sql: true

logging.level:
  org.hibernate.SQL: debug
  org.hibernate.type: trace

회원가입

User

@Getter
@Entity
@NoArgsConstructor
@Table(name = "user_tbl")
public class User {

    @Id @Column(name = "user_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 30, unique = true)
    private String email;

    @Column(length = 20)
    private String nickname;

    private int age;

    @Column(length = 100)
    private String password;

    @Enumerated(EnumType.STRING)
    private Role role;

    @Builder
    public User(String email, String nickname, int age, String password, Role role) {
        this.email = email;
        this.nickname = nickname;
        this.age = age;
        this.password = password;
        this.role = role;
    }


    public void encodePassword(PasswordEncoder passwordEncoder) {
        this.password = passwordEncoder.encode(password);
    }
}

UserRepository

public interface UserRepository extends JpaRepository<User, Long> {

    // 중복되는 이메일 찾을 때 사용.
    Optional<Member> findByEmail(String email);
}

Repository에선 JPA를 사용하여 간단하게 작성할 수 있다.


UserSignUpRequestDto

@Data
public class UserSignUpRequestDto {

    // 이메일을 입력하지 않았을 때 해당 내용을 띄어줌.
    @NotBlank(message = "이메일을 입력해주세요.")
    private String email;

    @NotNull(message = "나이를 입력해주세요.")
    @Range(min = 1, max = 120) // 최소 나이와 최대 나이 지정
    private int age;

    @NotBlank(message = "닉네임을 입력해주세요.")
    private String nickname;

    @NotBlank(message = "비밀번호를 입력해주세요.")
    // 비밀번호 패턴 지정.
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,30}$",
            message = "비밀번호는 8~30 자리이면서 1개 이상의 알파벳, 숫자, 특수문자를 포함해야합니다.")
    private String password;

    private Role role;

    @Builder
    public User toEntity(){
        return User.builder()
                .email(email)
                .age(age)
                .nickname(nickname)
                .password(password)
                .role(Role.USER)
                .build();
    }
}

RequestDto를 만들어 회원가입 할 때 객체 자체를 넘기는 것이 아닌 별도의 객체를 만들어 사용함.


UserService

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    // readOnly가 true되어있기 때문에 @Transactional을 써줘야 false가 됌.
    @Transactional
    public Long signUp(UserSignUpRequestDto request) throws Exception {

        // 중복 이메일이 있는지 검사
        if (userRepository.findByEmail(request.getEmail()).isPresent()) {
            throw new Exception("이미 존재하는 이메일 입니다.");
        }

        User user = userRepository.save(request.toEntity());
        // 비밀번호 암호화
        user.encodePassword(passwordEncoder);

        return user.getId();
    }
}

user.encodePassword( )를 사용하기 위해선 User 클래스에

public void encodePassword(PasswordEncoder passwordEncoder) {
        this.password = passwordEncoder.encode(password);
    }

를 추가해주면 된다. 이렇게 되면 비밀번호가 암호화 된다.


UserController

@RestController
@RequestMapping("/User")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/join")
    public Long singUp(@Valid @RequestBody UserSignUpRequestDto request) throws Exception {
        return userService.signUp(request);
    }
}

이렇게 RestAPI까지 만들었다. 회원가입은 끝이 났고 다음 장에서 로그인을 구현하도록 하겠다.

0개의 댓글