저번 포스팅(https://sdy-study.tistory.com/269) 에서 이메일로 회원가입을 보내는 방법을 알아봤다
이번에는 JWT 로 로그인을 하는 방법에 대해 알아본다
* 로그인을 위한 사전지식들
1) 인증
: 웹에서 인증을 하기 위해선 과거엔 쿠키, 세션을 사용했지만 현재는 토큰을 이용한 방식을 가장 많이 사용한다
토큰 기반의 인증의 가장 대표적인게 바로 JWT (JSON Web Token) 이다
(참조 : https://sdy-study.tistory.com/44)
2) JWT
: JWT (Json Web Token) 은 JSON 포맷을 이용하여, 유저에 대한 속성을 저장하는 Claim 기반의 Web Token 이다
JWT 는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 저장한다
(JWT 에 대한 자세한 구조는 링크 참조 : https://mangkyu.tistory.com/56)
3) JWT 를 통한 로그인 과정
: JWT 를 통한 로그인 과정을 요약하면 아래와 같다 (세부적인 내용은 구현하는 사람에 따라 약간 다를 수 있다)
1. 먼저 클라이언트가 서버로 로그인 요청을 보낸다
2. JWT 를 클라이언트가 가지고 있지 않으면 서버에서 생성한다.
-> 이 포스팅에선 공개키, 개인키를 기반으로 토큰을 생성할 것이다
-> 만약 토큰이 이미 있는 상태로 요청했다면, 서버에선 토큰 검증 절차를 치르고 그에따른 응답을 보낸다
3. (토큰을 생성한 경우) 클라이언트로 넘긴다
4. 다음 요청시에 HTTP Header 에 토큰을 첨부하여 요청한다
(Authorization: Bearer token-value)
5. 토큰 검증하고 그에 따른 응답을 한다
4) Spring Security 가 제공하는 로그인 관련 기본 클래스 및 인터페이스들
a) UsernamePasswordAuthenticationToken 클래스
: username 과 password 를 담고 있는 클래스로, 유저가 로그인시에 입력한 내용들을 담고 있다
b) AuthenticationManager 인터페이스
: authenticate 라는 메소드를 가지고 있으며, 이 메소드의 매개변수는 Authentication 이라는 인터페이스가 들어가고, 자체적인 인증 로직을 거치는 기능을 갖고 있다.
c) AuthenticationManagerBuilder 클래스
: AuthenticationManager 인터페이스를 생성하는 빌더이며, 인증 방식에 대한 설정을 지정한다
(DB 방식인지, In-memory 방식인지 등)
d) UserDetailsService 인터페이스
: 유저가 입력한 로그인 데이터를 다루는 인터페이스이며 이 인터페이스는 직접 구현을 해줘야한다
구현 방법은 해당 프로젝트가 어떤 방식의 인증을 쓰느냐에 따라 달라진다
Redis 와 같은 in-memory 를 이용해서 인증을 하는지
아니면 DB 를 이용해서 인증을 하는지
혹은 LDAP 를 이용해서 인증을 하는지 등등에 따라 달라질 수 있다.
(여기서는 유저 정보를 DB에 담아서 사용하므로 DB 인증 방식을 사용했다)
e) UserDetails 인터페이스
: DB 로 부터 가져온 유저 정보를 담는 인터페이스이다.
5) 4) 에서 언급한 기본 클래스 및 인터페이스를 이용하여 로그인을 하는 과정 요약
- 먼저 4)에서 언급한 AuthenticationManager 를 Spring Bean 으로 등록해야 한다 (인증 처리를 위함임)
- AuthenticationManagerBuilder 를 이용해서 위에서 언급한 인증 방식을 설정한다 (여기선 DB 방식으로)
- 설정을 모두 마친뒤, Controller 가 유저가 입력한 로그인정보를 받게 하고 Service 로 넘긴다
- Service 클래스에서 UsernamePasswordAuthenticationToken 인스턴스를 생성하여 유저의 입력정보를 담는다
- 그리고 이 인스턴스를 AuthenticationManager 가 인증처리하도록 authenticate() 메소드를 호출한다
AuthenticationManager 가 처리를할때, UserDetailsService 를 구현한 자체 클래스에서 정의한 방식대로 (DB 방식인지, in-memory 방식인지) 에 맞춰서 처리한다.
(DB 방식으로 구현했으므로, 유저 정보를 가져와서 인증 절차를 실시하고 이에 대한 결과물은 UserDetails 와 Authentication 에 담긴다, 그리고 이들은 다시 AuthService 로 전달된다)
- SecurityContextHolder 에 이 인증 내용을 담는다
- JWT 를 생성하고 유저에게 넘겨준다.
* Spring Boot 와 JWT 를 통한 로그인 코드들
: 위 내용을 Spring Boot 에서 코드로 구현하면 다음과 같다
0) SecurityConfig
: 이전 포스팅(https://sdy-study.tistory.com/269) 에서 아래 내용을 추가
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
|
cs |
1) Controller
@RestController
@RequestMapping("/api/auth")
@AllArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public AuthenticationResponse login(@RequestBody LoginRequest loginRequest) {
return authService.login(loginRequest);
}
}
|
cs |
LoginRequest 는 유저가 입력한 로그인 값을 담는 DTO 이다.
2) Service
@Service
@Slf4j
@AllArgsConstructor
public class AuthService {
private final AuthenticationManager authenticationManager;
private final JWTProvider jwtProvider;
public AuthenticationResponse login(LoginRequest loginRequest) {
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authenticate);
String authenticationToken = jwtProvider.generateToken(authenticate);
return new AuthenticationResponse(authenticationToken, loginRequest.getUsername());
}
}
|
cs |
3) JWT 생성
* JWT 생성을 위한 비대칭 암호화 방식(RSA)
: 자바에서 제공하는 KeyStore 를 기반으로 RSA 방식으로 JWT 에 서명을하여 인증을 할 수 있다.
(RSA 에 대한 자세한 설명은 링크 참조 : https://mkil.tistory.com/461)
먼저 jks 파일을 만들어줘야 하는데 다음과 같은 명령어를 커맨드 창에 작성한다
keytool -genkey -alias [alias-이름] -keyalg [사용할 알고리즘, RSA 사용] -keystore [keystore 파일이름 test.jks 이런식] -keysize [keystore 크기, 여기선 2048 사용]
|
cs |
입력하고 나면 비밀번호와 여러가지 내용들을 적을 것을 요청한다
임의로 작성가능 하되 비밀번호는 반드시 기억하고 있어야한다
비밀번호는 application.properties 같은 암호 정보를 담는 파일에 넣는게 좋다
간혹 경고문으로 PKCS12 로 바꾸라고 뜨기도 하는데 그냥 생략해도 된다.
만든 jks 파일은 spring boot 의 resources 폴더 안에 넣어야 한다.
이를 토대로 JWT 를 생성하는 자바 코드를 아래 처럼 작성한다
@Service
public class JwtProvider {
private KeyStore keyStore;
@PostConstruct
public void init() {
try {
keyStore = KeyStore.getInstance("JKS");
InputStream resourceAsStream = getClass().getResourceAsStream("/jks 파일 이름.jks");
keyStore.load(resourceAsStream, "jks 생성시 지정한 비밀번호".toCharArray());
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
throw new SpringRedditException("keystore 로딩 중 에러 발생");
}
}
public String generateToken(Authentication authentication) {
org.springframework.security.core.userdetails.User principal = (User) authentication.getPrincipal();
return Jwts.builder()
.setSubject(principal.getUsername())
.signWith(getPrivateKey())
.compact();
}
private PrivateKey getPrivateKey() {
try {
return (PrivateKey) keyStore.getKey("jks 파일 생성시 만든 alias", "jks 생성시 지정한 비밀번호".toCharArray());
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
throw new SpringRedditException("Exception occured while retrieving public key from keystore");
}
}
}
|
cs |
여기까지 하면 아래 처럼 "JWT 생성" 까지만 끝난 것이다.
아직 JWT 에 대한 검증 작업이 전혀 이뤄지지 않았다
이는 다음 포스팅에서 로그아웃과 함께 다룬다.
'Spring' 카테고리의 다른 글
Spring Boot 2.5 이후 버전과 Hibernate 와 data.sql (0) | 2021.09.15 |
---|---|
Spring Cloud Eureka 와 기본 REST API (0) | 2021.09.14 |
Spring Boot 로 이메일 회원가입 하기 (1) | 2021.07.06 |
Spring Cloud Netflix Eureka (0) | 2021.07.05 |
Spring Cloud 와 Microservice 란 (0) | 2021.07.01 |