IT/JAVA
Spring Security로 로그인 기능 구현하기 (JWT 포함)
밥알이
2025. 6. 20. 10:39
Spring Security로 로그인 기능 구현하기 (JWT 포함)
Spring Boot로 웹 애플리케이션을 개발할 때, 보안은 필수입니다. 그 중심에 있는 것이 Spring Security이며, 여기에 JWT(JSON Web Token)을 결합하면 세션리스한 인증 시스템을 손쉽게 구축할 수 있습니다.
이 글에서는 Spring Security의 인증/인가 개념을 이해하고, 실제로 JWT 기반 로그인 기능을 구현하는 실습 코드까지 차근차근 알아보겠습니다.
Spring Security의 기본 개념: 인증(Authentication)과 인가(Authorization)
1. 인증(Authentication)이란?
사용자가 누구인지 확인하는 절차입니다. 예: 아이디/비밀번호로 로그인
2. 인가(Authorization)란?
인증된 사용자가 어떤 자원(Endpoint, 기능 등)에 접근할 수 있는지를 판단하는 과정입니다.
JWT란 무엇인가?
JWT (JSON Web Token)은 인증 정보를 담은 토큰입니다. 서버에서 로그인 성공 시 토큰을 발급하고, 이후 클라이언트가 요청할 때마다 헤더에 토큰을 담아 인증하는 방식입니다.
- 장점: 세션 저장소가 필요 없음 (stateless)
- 단점: 토큰 탈취 시 위험, 토큰 갱신 필요
Spring Security + JWT 로그인 흐름
- 사용자가 ID/PW로 로그인 요청
- 서버에서 인증 후 JWT 토큰 발급
- 클라이언트는 이후 요청 시 Authorization 헤더에 토큰 포함
- 서버는 JWT를 검증하고, 인증된 사용자로 처리
Spring Boot 프로젝트 실습: JWT 로그인 구현
이제 실제 코드로 로그인 기능을 구현해보겠습니다.
1. build.gradle 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}
2. JWT 유틸 클래스
@Component
public class JwtTokenProvider {
private final String secretKey = "mysecretkey";
private final long validityInMilliseconds = 3600000; // 1 hour
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
}
public Authentication getAuthentication(String token) {
String username = getUsername(token);
return new UsernamePasswordAuthenticationToken(username, "", List.of());
}
public String getUsername(String token) {
return Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
3. 로그인 컨트롤러
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
public AuthController(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) {
this.authenticationManager = authenticationManager;
this.jwtTokenProvider = jwtTokenProvider;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody AuthRequest authRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
String token = jwtTokenProvider.createToken(authRequest.getUsername(), List.of("ROLE_USER"));
return ResponseEntity.ok(Map.of("token", token));
}
}
AuthRequest DTO
public class AuthRequest {
private String username;
private String password;
// getters and setters
}
4. JWT 필터 구현
public class JwtTokenFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
return (bearer != null && bearer.startsWith("Bearer ")) ? bearer.substring(7) : null;
}
}
5. Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
마무리: Spring Security + JWT로 간결하고 안전한 인증 시스템 구축
Spring Security는 복잡해 보이지만, 핵심 개념을 이해하고 JWT와 결합하면 세션리스한 인증 구조를 비교적 간단하게 구현할 수 있습니다. 이번 글에서 제공한 구조는 실제 실무에서도 활용 가능한 기본 틀이며, 여기에 Refresh Token, 사용자 권한 관리 등을 추가하면 보다 완성도 높은 인증 시스템이 됩니다.
추가로 구현해볼 수 있는 내용:
- Refresh Token 발급 및 재발급 기능
- 유저 DB와 연동한 사용자 인증 처리
- 권한(ROLE_ADMIN, ROLE_USER 등) 기반 인가 처리
보안은 개발의 핵심입니다. Spring Security + JWT로 안전한 애플리케이션을 만들어보세요!