Spring boot 게시판 API 서버 제작 (4) - 로그인 - 비밀번호 암호화 및 토큰 발급과 검증
이번에는 비밀번호를 그대로 사용할 수 없기 때문에 암호화를 해보고, 로그인을 위한 토큰을 발급받고 검증까지 진행해보겠습니다.
비밀번호 암호화
비밀번호 암호화는 Spring Security에서 제공하는 PasswordEncoder를 사용하겠습니다. PasswordEncoder는 암호화된 비밀번호를 생성하거나 저장된 비밀번호화 사용자가 입력한 비밀번호를 비교하는 기능이 있습니다.
바로 코드로 보겠습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.formLogin().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll(); //일단 모든 url에 대해서 접근 허용
}
/** 비밀번호 암호화 **/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
PasswordEncoder를 사용하기 위해서 WebSecurityConfigurerAdapter를 상속받아야 합니다. 이에 따른 필수 설정들을 해주고 비밀번호 암호화를 하기 위해 BCryptPasswordEncoder(); 를 호출합니다.
비밀번호 암호화 테스트
비밀번호 암호화를 만들었으니 테스트를 진행해보겠습니다.
public class PasswordEncoderTest {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Test
public void encodeWithBcryptTest(){
//given
String password = "password";
//when
String encodedPassword = passwordEncoder.encode(password);
boolean isMatch = passwordEncoder.matches(password, encodedPassword);
//then
assertThat(isMatch).isTrue();
}
}
1. 먼저 passwordEncoder.encode(password);를 통해서 비밀번호를 암호화하고
2. passwordEncoder.matches(password, encodedPassword); 내가 입력한 비밀번호가 맞는지 확인합니다.
JWT 발급
JWT(JSON Web Token)은 JSON형식으로 인코딘된 정보를 안전하게 전달하기 위한 인증 방식중 하나입니다.
간단하게 소개를 하자면 JWT는 세 부분으로 이루어져 있습니다.
- Header: JWT의 유형 및 사용된 알고리즘 등의 정보를 포함합니다.
- Payload: JWT에 포함할 정보가 JSON 형식으로 인코딩되어 저장됩니다. 예를 들어 사용자 ID, 권한 정보 등이 포함될 수 있습니다.
- Signature: JWT를 검증하는 데 사용되는 비밀 키를 사용하여 Header와 Payload를 서명한 값입니다.
그럼 발급을 받아보겠습니다.
dependency 추가
JWT를 사용하기 위해서 dependency를 추가해야 합니다. build.gradle에 추가하겠습니다.
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'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'io.jsonwebtoken:jjwt:0.9.1' //<- 추가
}
JwtHandler 생성
토큰을 처리하기 위한 JwtHandler를 생성하겠습니다.
@Component
public class JwtHandler {
public String generateJwtToken(String encodedKey, String subject, long maxAgeSeconds) {
return Jwts.builder()
.setSubject(subject) // JWT에 대한 주제를 설정
.setIssuedAt(new Date()) //JWT의 발급 시간을 설정
.setExpiration(new Date(System.currentTimeMillis() + maxAgeSeconds)) // JWT의 만료 시간을 설정
.signWith(SignatureAlgorithm.HS512, encodedKey) // JWT에 서명할 알고리즘과 비밀 키를 설정
.compact();
}
public String extractSubject(String encodedKey, String token){
return Jwts
.parser()
.setSigningKey(encodedKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validate(String encodedKey, String token) {
try {
parse(encodedKey, token);
return true;
} catch (JwtException e) {
System.out.println("e = " + e);
return false;
}
}
private Jws<Claims> parse(String key, String token) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token);
}
}
JWT의 사용법에 대해서는 따로 포스트를 올리도록 하겠습니다. 일단은 이렇게 생성되는구나 라고 넘어가시면 됩니다.
1. generateJwtToken : 토큰 생성
2. extractSubject : subject값을 추출
3. validate : 토큰 검증
JwtHandler 테스트
JwtHandler를 생성했으니 테스트 코드도 작성해보겠습니다.
class JwtHandlerTest {
JwtHandler jwtHandler = new JwtHandler();
@Test
void extractSubjectTest() {
// given
String encodedKey = Base64.getEncoder().encodeToString("myKey".getBytes());
String subject = "subject";
String token = jwtHandler.generateJwtToken(encodedKey, subject, 6000L);
// when
String extractedSubject = jwtHandler.extractSubject(encodedKey, token);
// then
assertThat(extractedSubject).isEqualTo(subject);
}
@Test
void validateTest() {
// given
String encodedKey = Base64.getEncoder().encodeToString("myKey".getBytes());
String token = jwtHandler.generateJwtToken(encodedKey, "subject", 60000L);
// when
boolean isValid = jwtHandler.validate(encodedKey, token);
// then
assertThat(isValid).isTrue();
}
@Test
void invalidateByInvalidKeyTest() {
// given
String encodedKey = Base64.getEncoder().encodeToString("myKey".getBytes());
String token = jwtHandler.generateJwtToken(encodedKey, "subject", 60000L);
// when
boolean isValid = jwtHandler.validate("invalid", token);
// then
assertThat(isValid).isFalse();
}
@Test
void invalidateByExpiredTokenTest() {
// given
String encodedKey = Base64.getEncoder().encodeToString("myKey".getBytes());
String token = jwtHandler.generateJwtToken(encodedKey, "subject", 0L);
// when
boolean isValid = jwtHandler.validate(encodedKey, token);
// then
assertThat(isValid).isFalse();
}
}
테스트 코드도 간단하여 설명은 생략하겠습니다.
궁금한신점이나 잘못된 부분이 있으면 자유롭게 댓글 달아주세요.