๐Ÿ“Œ [JWT ์ธ์ฆ ์‹œ์Šคํ…œ ์‹œ๋ฆฌ์ฆˆ - 1ํŽธ] Spring Security ์„ค์ • + ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์ฒ˜๋ฆฌ ํ๋ฆ„

My Pale Blue Dotยท2025๋…„ 5์›” 20์ผ
0

SPRING BOOT

๋ชฉ๋ก ๋ณด๊ธฐ
36/40
post-thumbnail

๐Ÿ“… ๋‚ ์งœ

2025-05-20


๐Ÿ“ ํ•™์Šต ๋‚ด์šฉ

1๏ธโƒฃ SecurityConfig.java โ€“ Spring Security ์„ค์ •์˜ ํ•ต์‹ฌ

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomSuccessHandler customSuccessHandler;
    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    @Autowired
    private CustomLogoutHandler customLogoutHandler;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Bean
    protected SecurityFilterChain configure(HttpSecurity http) throws Exception {
        // โœ… CSRF ๋น„ํ™œ์„ฑํ™”
        http.csrf((config) -> config.disable());

        // โœ… ์ธ๊ฐ€ ์„ค์ •
        http.authorizeHttpRequests((auth) -> {
            auth.requestMatchers("/", "/join", "/login").permitAll();
            auth.requestMatchers("/user").hasRole("USER");
            auth.requestMatchers("/manager").hasRole("MANAGER");
            auth.requestMatchers("/admin").hasRole("ADMIN");
            auth.anyRequest().authenticated();
        });

        // โœ… ๋กœ๊ทธ์ธ ์„ค์ •
        http.formLogin((login) -> {
            login.permitAll();
            login.loginPage("/login");
            login.successHandler(customSuccessHandler); // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ ์‹คํ–‰
            login.failureHandler(new CustomLoginFailureHandler());
        });

        // โœ… ๋กœ๊ทธ์•„์›ƒ ์„ค์ •
        http.logout((logout) -> {
            logout.permitAll();
            logout.addLogoutHandler(customLogoutHandler);
            logout.logoutSuccessHandler(customLogoutSuccessHandler);
        });

        // โœ… ์˜ˆ์™ธ์ฒ˜๋ฆฌ ์„ค์ •
        http.exceptionHandling((ex) -> {
            ex.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
            ex.accessDeniedHandler(new CustomAccessDeniedHandler());
        });

        // โœ… OAuth2 ๋กœ๊ทธ์ธ ์„ค์ • (๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๊ณต์œ )
        http.oauth2Login((oauth2)-> oauth2.loginPage("/login"));

        // โœ… ์„ธ์…˜ ์„ค์ • (JWT ๊ธฐ๋ฐ˜ โ†’ ๋ฌด์ƒํƒœ)
        http.sessionManagement((sessionManagerConfigure) ->
           sessionManagerConfigure.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        // โœ… JWT ์ธ์ฆ ํ•„ํ„ฐ ์ถ”๊ฐ€ (LogoutFilter ์ „์— ์‹คํ–‰๋˜๋„๋ก)
        http.addFilterBefore(new JwtAuthorizationFilter(userRepository, jwtTokenProvider), LogoutFilter.class);

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2๏ธโƒฃ CustomSuccessHandler.java โ€“ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ฒ˜๋ฆฌ ํ๋ฆ„

@Slf4j
@Component
public class CustomSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Autowired
    private JwtTokenRepository jwtTokenRepository;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        log.info("CustomSuccessHandler's onAuthenticationSuccess invoke..." + authentication);

        // โœ… JWT ๋ฐœ๊ธ‰
        TokenInfo tokenInfo = jwtTokenProvider.generateToken(authentication);

        // โœ… AccessToken์„ ์ฟ ํ‚ค๋กœ ์ „๋‹ฌ
        Cookie cookie = new Cookie(JwtProperties.ACCESS_TOKEN_COOKIE_NAME, tokenInfo.getAccessToken());
        cookie.setMaxAge(JwtProperties.REFRESH_TOKEN_EXPIRATION_TIME); // ์ฟ ํ‚ค ์œ ์ง€ ์‹œ๊ฐ„ = RefreshToken ์‹œ๊ฐ„
        cookie.setPath("/");
        response.addCookie(cookie);

        // โœ… Access + Refresh Token DB ์ €์žฅ
        JwtToken jwtToken = JwtToken.builder()
                .accessToken(tokenInfo.getAccessToken())
                .refreshToken(tokenInfo.getRefreshToken())
                .username(authentication.getName())
                .createAt(LocalDateTime.now())
                .build();
        jwtTokenRepository.save(jwtToken);

        // โœ… ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
        response.sendRedirect(request.getContextPath() + "/");
    }
}

๐Ÿ”ฅ ์ •๋ฆฌ

๊ตฌ์„ฑ ์š”์†Œ์—ญํ• 
SecurityConfig์ „์ฒด ๋ณด์•ˆ ์„ค์ • ๋‹ด๋‹น (HttpSecurity ๊ธฐ๋ฐ˜ FilterChain ๊ตฌ์„ฑ)
formLogin().successHandler()๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ CustomSuccessHandler ์‹คํ–‰
CustomSuccessHandlerAccessToken ๋ฐœ๊ธ‰, ์ฟ ํ‚ค์— ์ €์žฅ, DB ์ €์žฅ, ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
JwtTokenProvider์‹ค์ œ JWT ์ƒ์„ฑ ์ฑ…์ž„
JwtTokenRepositoryAccess/RefreshToken ์˜์†ํ™” (DB ์ €์žฅ์šฉ)

๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ


๐Ÿง  ๋А๋‚€ ์ 

  • SecurityConfig์˜ ํ๋ฆ„์„ ์™„์ „ํžˆ ์ดํ•ดํ•˜๊ณ  ๋‚˜๋‹ˆ, ์ธ์ฆ ํ•„ํ„ฐ์˜ ๋™์ž‘ ์ˆœ์„œ๊ฐ€ ๋ช…ํ™•ํ•ด์ง
  • ๋‹จ์ˆœ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์ฒ˜๋ฆฌ๊ฐ€ ์•„๋‹Œ, JWT ๋ฐœ๊ธ‰ โ†’ ์ €์žฅ โ†’ ์ „๋‹ฌ ํ๋ฆ„์„ ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์™„์ „ํžˆ ํ†ต์ œํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด ์ธ์ƒ ๊นŠ์—ˆ์Œ
  • ์ดํ›„ AccessToken ๋งŒ๋ฃŒ ๋Œ€์‘, RefreshToken ํ™œ์šฉ ๊ตฌ์กฐ๊ฐ€ ๋” ์ค‘์š”ํ•˜๊ฒŒ ์ž‘์šฉํ•  ๊ฒƒ์ด๋ผ ๋А๊ปด์ง

โœ… ์š”์•ฝ

  • Spring Security์˜ ํ•„ํ„ฐ ์ฒด์ธ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•ด JWT ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ๊ธฐ๋ฐ˜์„ ๊ตฌ์ถ•ํ–ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ AccessToken์„ ์ฟ ํ‚ค์— ์ €์žฅ, RefreshToken์€ ํ•จ๊ป˜ DB์— ์ €์žฅํ•จ์œผ๋กœ์จ ์ดํ›„ ์ธ์ฆ ํ๋ฆ„์— ๋Œ€๋น„ํ•จ.
  • ์ด ๊ตฌ์กฐ๋Š” ์ดํ›„ Redis ๊ธฐ๋ฐ˜ RefreshToken ๊ด€๋ฆฌ, ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ํ๋ฆ„ ๊ตฌํ˜„์„ ์œ„ํ•œ ์ดˆ์„์ด ๋œ๋‹ค.

profile
Here, My Pale Blue.๐ŸŒ

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