HTTPS, ์์ธ ์ฒ๋ฆฌ, ์ธ์ ๊ด๋ฆฌ, ์ด๋ฒคํธ ๋ฆฌ์ค๋๊น์ง โ Spring Security ์ค๋ฌด ํต์ฌ์ ๋ง๋ฌด๋ฆฌํ๋ค
@Configuration
@Profile("prod")
public class ProjectSecurityProdConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.requiresChannel(rcc ->
rcc.anyRequest().requiresSecure()
)
.authorizeHttpRequests(req -> req.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
๐ ํต์ฌ ๋ฉ์๋ ์์ฝ
requiresChannel() โ HTTP ์์ฒญ์ HTTPS๋ก ๋ฆฌ๋๋ ์
requiresSecure() โ ๋ชจ๋ ์์ฒญ์ด HTTPS๋ก๋ง ์ฒ๋ฆฌ๋๋๋ก ๊ฐ์ requiresInsecure()๋ฅผ ํตํด HTTP ํ์ฉ ๊ฐ๋ฅspring.profiles.active)๋ณ๋ก HTTP โ HTTPS ์ ํ์ ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ค๋ฌด ํ์ค| ์์ธ ์ ํ | HTTP ์ํ ์ฝ๋ | ์ค๋ช | ๋์ ์ธํฐํ์ด์ค |
|---|---|---|---|
AuthenticationException | 401 Unauthorized | ์ธ์ฆ ์คํจ (์๋ชป๋ ์๊ฒฉ ์ฆ๋ช , ํ ํฐ ๋ง๋ฃ ๋ฑ) | AuthenticationEntryPoint |
AccessDeniedException | 403 Forbidden | ์ธ์ฆ์ ๋์์ง๋ง ๊ถํ ๋ถ์กฑ | AccessDeniedHandler |

@Component
public class CustomBasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String jsonResponse = String.format("""
{
"timestamp": "%s",
"status": %d,
"error": "Unauthorized",
"message": "%s",
"path": "%s"
}
""",
LocalDateTime.now(),
HttpServletResponse.SC_UNAUTHORIZED,
authException != null ? authException.getMessage() : "Unauthorized",
request.getRequestURI());
response.getWriter().write(jsonResponse);
}
}
http.httpBasic(hbc ->
hbc.authenticationEntryPoint(new CustomBasicAuthenticationEntryPoint())
);
๐ ์ค๋ฌด ํ
accessDeniedPage("/403")๋ก HTML ํ์ด์ง ๋ฆฌ๋๋ ์
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String jsonResponse = """
{
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "%s"
}
""".formatted(request.getRequestURI());
response.getWriter().write(jsonResponse);
}
}
http.exceptionHandling(ehc ->
ehc.accessDeniedHandler(new CustomAccessDeniedHandler())
);
| ๋ชฉ์ | ๋ฉ์๋ | ์ค๋ช |
|---|---|---|
| ์ธ์ ํ์์์ | server.servlet.session.timeout=20m | ๊ธฐ๋ณธ 30๋ถ โ ํ๊ฒฝ๋ณ ์ง์ ๊ฐ๋ฅ |
| ์ ํจํ์ง ์์ ์ธ์ ๋ฆฌ๋๋ ์ | invalidSessionUrl("/invalidSession") | ์ธ์ ๋ง๋ฃ ์ ์๋ด ํ์ด์ง๋ก ์ด๋ |
| ๋์ ์ธ์ ์ ํ | maximumSessions(1) | ๋์ผ ์ฌ์ฉ์ ๋ค์ค ๋ก๊ทธ์ธ ์ ํ |
| ์ธ์ ๊ณ ์ ๊ณต๊ฒฉ ๋ฐฉ์ง | sessionFixation().changeSessionId() | ์ธ์ฆ ํ ์ธ์ ID ์ฌ๋ฐ๊ธ |
| ์ ๋ต | ์ค๋ช | ๋น๊ณ |
|---|---|---|
changeSessionId | ๊ธฐ์กด ์ธ์ ์์ฑ ์ ์ง, ID๋ง ๊ต์ฒด | โ ๊ธฐ๋ณธ๊ฐ (Servlet 3.1 ์ดํ) |
migrateSession | ์ ์ธ์ ์์ฑ + ๊ธฐ์กด ์์ฑ ๋ณต์ฌ | ๊ณผ๊ฑฐ Spring Boot 2.x ๊ธฐ๋ณธ |
newSession | ์์ ํ ์ ์ธ์ ์์ฑ | ๋ณด์ ๊ฐํ์ฉ |
none | ๋ณดํธ ๋นํ์ฑํ | ๐ซ ๊ถ์ฅ๋์ง ์์ |

@Slf4j
@Component
public class AuthenticationEvents {
@EventListener
public void onSuccess(AuthenticationSuccessEvent successEvent) {
log.info("โ
๋ก๊ทธ์ธ ์ฑ๊ณต: {}", successEvent.getAuthentication().getName());
}
@EventListener
public void onFailure(AbstractAuthenticationFailureEvent failureEvent) {
log.error("โ ๋ก๊ทธ์ธ ์คํจ: {} - ์ด์ : {}",
failureEvent.getAuthentication().getName(),
failureEvent.getException().getMessage());
}
}
๐ ํ์ฉ ์์
@EventListener๋ Spring Boot์ ์ผ๋ฐ ์ด๋ฒคํธ ์์คํ
๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก ์๋http.formLogin(flc -> flc
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error=true")
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
);
http.logout(loc -> loc
.logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID")
);
Authentication authentication = SecurityContextHolder
.getContext()
.getAuthentication();
String username = authentication.getName();
@GetMapping("/dashboard")
public String displayDashboard(Model model, Authentication authentication) {
model.addAttribute("username", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
return "dashboard.html";
}

| ๊ตฌ๋ถ | ์ค๋ฌด ๋ชฉํ | ์ฃผ์ ์ค์ |
|---|---|---|
| ํต์ ๋ณด์ | HTTPS ๊ฐ์ | requiresSecure() |
| ์์ธ ์ฒ๋ฆฌ | 401/403 ์๋ต ๊ตฌ์กฐํ | Custom EntryPoint / Handler |
| ์ธ์ ๋ณด์ | ํ์์์ / ๋์ ์ธ์ / ์ธ์ ๊ณ ์ ๋ฐฉ์ง | sessionManagement() |
| ์ด๋ฒคํธ ์ฒ๋ฆฌ | ๋ก๊ทธ์ธ ๊ฐ์ฌ ๋ก๊ทธ, ์๋ฆผ | @EventListener |
| UI ํตํฉ | Thymeleaf ์กฐ๊ฑด ๋ ๋๋ง | sec:authorize="isAuthenticated()" |
| Context ์ ๊ทผ | ์ธ์ฆ ์ ๋ณด ๋ก๋ | SecurityContextHolder / DI |