๐Ÿš€ Spring Boot JWT ์ธ์ฆ & SecurityConfig ์ •๋ฆฌ

Kim Chanwooยท2025๋…„ 9์›” 25์ผ

Spring Boot

๋ชฉ๋ก ๋ณด๊ธฐ
4/6
post-thumbnail

1๏ธโƒฃ JWT ๋กœ ๋ฐฑ์—”๋“œ ๋ณดํ˜ธ

  • ๊ธฐ์กด Basic Auth ๋ฐฉ์‹: username/password ํ™•์ธ๋งŒ ๊ฐ€๋Šฅ, ์„ธ์…˜/ํ† ํฐ ์ฒ˜๋ฆฌ ์—†์Œ โ†’ React ๋“ฑ ํ”„๋ก ํŠธ์—์„œ ์‚ฌ์šฉ ๋ถˆํŽธ
  • JWT: RESTful API์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์ธ์ฆ/์ธ๊ฐ€ ๋ฐฉ์‹

JWT ๊ฐœ๋…

  • Authentication (์ธ์ฆ): ๋กœ๊ทธ์ธ ๊ณผ์ •
  • Authorization (์ธ๊ฐ€/๊ถŒํ•œ): ์ธ์ฆ ํ›„ ์—ญํ•  ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ๋ถ€์—ฌ
  • JWT๋Š” URL, POST ํŒŒ๋ผ๋ฏธํ„ฐ, Header ๋“ฑ์œผ๋กœ ์ „์†ก ๊ฐ€๋Šฅ
  • JWT ๊ตฌ์กฐ: xxxxx.yyyyyy.zzzzz
๋ถ€๋ถ„๋‚ด์šฉ
Headerํ† ํฐ ์œ ํ˜•, ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜
Payload์‚ฌ์šฉ์ž ์ •๋ณด (username, role ๋“ฑ)
Signatureํ† ํฐ ๋ณ€์กฐ ํ™•์ธ

2๏ธโƒฃ JWT ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆ

jjwt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์˜์กด์„ฑ

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

JwtService

@Component
public class JwtService {
    static final long EXPIRATIONTIME = 86400000; // 1์ผ
    static final String PREFIX = "Bearer";
    static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    public String getToken(String username) {
        return Jwts.builder()
            .setSubject(username)
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
            .signWith(key)
            .compact();
    }

    public String getAuthUser(HttpServletRequest request) {
        String token = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (token != null) {
            String user = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token.replace(PREFIX, ""))
                .getBody()
                .getSubject();
            if (user != null) return user;
        }
        return null;
    }
}

3๏ธโƒฃ ๋กœ๊ทธ์ธ ์š”์ฒญ ๋ฐ์ดํ„ฐ์šฉ Record

public record AccountCredentials(String username, String password) {}
  • Record ํŠน์ง•

    1. ๊ฐ„๊ฒฐํ•จ: ํ•„๋“œ๋งŒ ์ •์˜ํ•˜๋ฉด getter/setter ์ž๋™ ์ƒ์„ฑ
    2. ๋ถˆ๋ณ€์„ฑ: ๋ชจ๋“  ํ•„๋“œ๋Š” private final
    3. ์ž๋™ ์ƒ์„ฑ: AllArgsConstructor, toString(), equals() ๋“ฑ

4๏ธโƒฃ Optional ์‚ฌ์šฉ

Optional<AppUser> user = userRepository.findByUsername(username);
user.ifPresent(u -> System.out.println(u.getUsername()));
String name = user.orElse(new AppUser("Guest","")).getUsername();
  • NullPointerException ๋ฐฉ์ง€, ํ•จ์ˆ˜ํ˜• ์Šคํƒ€์ผ ๊ฐ€๋Šฅ

5๏ธโƒฃ LoginController

@RestController
@RequiredArgsConstructor
public class LoginController {
    private final JwtService jwtService;
    private final AuthenticationManager authentication;

    @PostMapping("/login")
    public ResponseEntity<?> getToken(@RequestBody AccountCredentials credentials) {
        UsernamePasswordAuthenticationToken creds =
            new UsernamePasswordAuthenticationToken(credentials.username(), credentials.password());
        Authentication auth = authentication.authenticate(creds);

        String jwts = jwtService.getToken(auth.getName());
        return ResponseEntity
            .ok()
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwts)
            .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Authorization")
            .build();
    }
}

6๏ธโƒฃ SecurityConfig & AuthenticationManager

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final JwtService jwtService;

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception{
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.POST, "/login").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(new AuthenticationFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

7๏ธโƒฃ AuthenticationFilter

@RequiredArgsConstructor
public class AuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 
            throws ServletException, IOException {
        String jws = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (jws != null) {
            String user = jwtService.getAuthUser(request);
            Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }
}

8๏ธโƒฃ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: AuthEntryPoint

public class AuthEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) 
            throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.getWriter().println("Error : " + authException.getMessage());
    }
}
  • ๋กœ๊ทธ์ธ ์‹คํŒจ ์‹œ 401 ๋ฐ˜ํ™˜, ์ปค์Šคํ…€ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ๊ฐ€๋Šฅ

9๏ธโƒฃ ์ธ์ฆ ํ๋ฆ„ ๐Ÿ”

  1. ํด๋ผ์ด์–ธํŠธ โ†’ POST /login โ†’ username/password ์ „์†ก
  2. AuthenticationManager ๊ฒ€์ฆ โ†’ JWT ๋ฐœ๊ธ‰
  3. ์ดํ›„ ์š”์ฒญ โ†’ Authorization: Bearer <token> ํ—ค๋” ํฌํ•จ
  4. AuthenticationFilter ๊ฐ€ JWT ๊ฒ€์ฆ ํ›„ SecurityContext ์„ค์ •
  5. Controller ์ ‘๊ทผ ๊ฐ€๋Šฅ

๐Ÿ”Ÿ ํ•ต์‹ฌ ํฌ์ธํŠธ ๐Ÿ’ก

๊ธฐ๋Šฅํด๋ž˜์Šค/์ธํ„ฐํŽ˜์ด์Šค์„ค๋ช…
์ธ์ฆUserDetailsService, AuthenticationManagerJWT ๋ฐœ๊ธ‰ ์ „ ๋กœ๊ทธ์ธ ๊ฒ€์ฆ
๊ถŒํ•œRoleVoter, @PreAuthorizeRole ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด
์„ธ์…˜SecurityContextHolderStateless โ†’ ์„ธ์…˜ ์‚ฌ์šฉ ์•ˆํ•จ
CSRFcsrf().disable()REST API์—์„œ๋Š” ๋น„ํ™œ์„ฑํ™”
๋กœ๊ทธ์ธ/login, LoginControllerJWT ๋ฐœ๊ธ‰
์š”์ฒญ ๋ณดํ˜ธAuthenticationFilter๋ชจ๋“  ์š”์ฒญ ์ธ์ฆ ์ฒ˜๋ฆฌ

0๊ฐœ์˜ ๋Œ“๊ธ€