1. 서론
최근 마이크로서비스 아키텍처와 RESTful API의 발전으로 인해 백엔드 개발의 중요성이 더욱 부각되고 있습니다. 오늘 강좌에서는 스프링 부트를 이용한 백엔드 개발을 통해 JSON Web Token(JWT)을 사용하여 사용자 인증을 구현하는 방법에 대해 알아보겠습니다. 이 강좌는 로그인 및 로그아웃 기능을 포함한 JWT 기반의 토큰 제공자를 추가하는 것에 중점을 두고 있습니다.
2. 스프링 부트란?
스프링 부트는 스프링 프레임워크를 기반으로 하여 빠르고 쉽게 스프링 애플리케이션을 개발할 수 있도록 돕는 도구입니다. 복잡한 XML 설정 없이 데모 및 실제 애플리케이션을 손쉽게 개발할 수 있게 해줍니다. 또한, 스프링 부트는 내장형 서버를 지원하여 외부 서버에 배포하지 않고도 로컬에서 애플리케이션을 실행할 수 있습니다.
3. JWT의 이해
JSON Web Token(JWT)은 데이터 전송을 위한 개방형 표준으로, 클라이언트와 서버 간에 안전하게 정보를 전송하는 데 사용됩니다. JWT는 주로 인증 및 정보 교환에 사용됩니다. JWT는 세 부분으로 구성됩니다:
- Header (헤더): 토큰의 유형과 암호화 알고리즘을 정의합니다.
- Payload (페이로드): 사용자 정보를 저장하는 부분으로, 사용자 ID와 같은 정보가 포함됩니다.
- Signature (서명): 헤더와 페이로드를 조합하여 비밀 키로 서명한 부분입니다.
4. 스프링 부트 프로젝트 설정
우선, 스프링 부트 프로젝트를 설정해야 합니다. Spring Initializr 웹사이트를 방문하여 새 프로젝트를 생성합니다. 다음의 종속성을 추가해야 합니다:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database (또는 원하는 데이터베이스)
- jjwt (JWT 사용을 위한 라이브러리)
5. 사용자의 엔티티 클래스 생성
사용자 정보를 저장하기 위한 엔티티 클래스를 생성합니다. 다음은 User 엔티티 클래스의 예입니다:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
// Getter and Setter methods
}
6. JWT 유틸리티 클래스 작성
JWT를 생성하고 검증하기 위한 유틸리티 클래스를 작성합니다.
@Component
public class JwtUtil {
private String secretKey = "mySecretKey"; // 비밀 키
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) // 만료 시간 설정
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
private boolean isTokenExpired(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getExpiration().before(new Date());
}
}
7. 스프링 시큐리티 설정
Spring Security를 설정하여 JWT 기반의 인증 프로세스를 구현합니다. 먼저, SecurityFilterChain을 설정합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("password")).roles("USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
8. JWT 요청 필터 작성
사용자 요청에서 JWT를 필터링하여 인증 여부를 확인하는 JWT Request Filter를 작성합니다.
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 명시된 사용자 인증 처리
}
chain.doFilter(request, response);
}
}
9. 로그인 및 토큰 생성 컨트롤러 작성
사용자의 로그인 요청을 처리하고, JWT를 생성하는 컨트롤러를 작성합니다.
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/authenticate")
public ResponseEntity> authenticate(@RequestBody AuthRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()));
} catch (BadCredentialsException e) {
throw new Exception("잘못된 사용자 이름 또는 비밀번호", e);
}
final String jwt = jwtUtil.generateToken(authRequest.getUsername());
return ResponseEntity.ok(new AuthResponse(jwt));
}
}
10. 로그아웃 관리
JWT은 클라이언트 측에서 관리되기 때문에 로그아웃 기능은 클라이언트에서 토큰을 삭제하여 단순히 수행할 수 있습니다. 하지만, 서버 측에서 로그아웃을 구현하는 방법도 있습니다. 예를 들어, 블랙리스트를 유지하여 만료되기 전에 로그아웃된 토큰을 차단할 수 있습니다.
11. 미들웨어 구성
미들웨어를 사용하여 요청을 전처리하고 후처리하는 기능을 추가할 수 있습니다. 예를 들어, 모든 요청에 대해 JWT를 검사하거나, 특정 엔드포인트에 대한 접근을 제한할 수 있습니다.
12. 테스트와 디버깅
상당한 수준의 테스트를 적용하여 코드의 품질을 보장해야 합니다. 각 서비스, 컨트롤러 및 리포지토리에 대한 단위 테스트를 작성합니다. 스프링 부트는 Junit과 Mockito를 통해 효과적인 테스트 환경을 제공합니다.
13. 결론
이 강좌에서는 스프링 부트와 JWT를 사용하는 로그인 및 로그아웃 구현 방법에 대해 자세히 살펴보았습니다. JWT는 클라이언트와 서버 간의 통신을 안전하게 처리하는 좋은 방법이며, 이를 통해 웹 애플리케이션의 보안을 한층 강화할 수 있습니다. 이러한 방식으로 백엔드 개발에 대한 이해도를 높이고, 실제 프로젝트에서는 이러한 구현을 통해 사용자 인증 기능을 보다 효율적으로 관리할 수 있을 것입니다.