JWT
(Json Web Token)는 인증에 필요한 정보들을 암호화 시킨 Json Token을 말한다. JWT는 세션과 다르게 클라이언트에 저장되어 서버의 부담을 줄일 수 있다.
JWT의 동작방식은 이렇다.
클라이언트에서 로그인 요청을 보내면, 서버에서 검증을 한후 Access Token
과 Refresh Token
을 발급해준다.
그리고 클라이언트에서 Access Token을 헤더에 담아서 서버에 요청하면 API를 사용할 수 있게 된다.
이 때 Refresh Token은 Access Token이 만료 되었을 때 Access Token을 재발급해주는 토큰이다.
Access Token은 정보가 유출될 수 있어 유효기간을 짧게 설정하기 때문에 Access Token이 만료 되면 Refresh Token을 통해 재발급 받을 수 있다.
※ Access Token이 만료 되었을 때 Refresh Token을 통해 재발급 받을 수 있기 때문에 기본적으로 Access Token보다 Refresh Token의 유효기간이 길어야 한다는 것을 알고 있어야 한다.
설명은 간단하게 하고 이제 회원가입과 로그인 예제를 통해 알아보자.
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'
security
와 jwt
를 사용할 예정이기 때문에 그에 대한 설정을 추가해준다. 데이터베이스는 mysql
을 사용할 것이다.
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
@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);
}
}
public interface UserRepository extends JpaRepository<User, Long> {
// 중복되는 이메일 찾을 때 사용.
Optional<Member> findByEmail(String email);
}
Repository에선 JPA
를 사용하여 간단하게 작성할 수 있다.
@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를 만들어 회원가입 할 때 객체 자체를 넘기는 것이 아닌 별도의 객체를 만들어 사용함.
@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);
}
를 추가해주면 된다. 이렇게 되면 비밀번호가 암호화 된다.
@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까지 만들었다. 회원가입은 끝이 났고 다음 장에서 로그인을 구현하도록 하겠다.