아래 요구 사항에 맞게 Spring Security 설정을 작성하세요.
요구 사항:
/auth/** 경로는 누구나 접근 가능해야 합니다.
/user/** 경로는 "ROLE_USER" 권한을 가진 사용자만 접근 가능합니다.
/admin/** 경로는 "ROLE_ADMIN" 권한을 가진 사용자만 접근 가능합니다.
로그인 페이지는 /login 경로로 설정하며, 성공 시 /로 리디렉션됩니다.
로그아웃 시 /login?logout으로 리디렉션됩니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll() // 누구나 접근 가능
.requestMatchers("/user/**").hasRole("USER") // ROLE_USER 권한 필요
.requestMatchers("/admin/**").hasRole("ADMIN") // ROLE_ADMIN 권한 필요
.anyRequest().authenticated() // 나머지 요청은 인증 필요
)
.formLogin(login -> login
.loginPage("/login") // 커스텀 로그인 페이지
.defaultSuccessUrl("/") // 로그인 성공 시 리디렉션
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout") // 로그아웃 성공 시 리디렉션
.permitAll()
);
return http.build();
}
}
Spring Security에서 사용자의 비밀번호를 암호화하고 검증하는 코드를 작성하세요.
요구 사항:
BCryptPasswordEncoder를 사용해 비밀번호를 암호화합니다.
로그인 시 암호화된 비밀번호와 사용자가 입력한 비밀번호를 비교해 검증합니다.
힌트: PasswordEncoder를 사용하세요.
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
public void registerUser(User user) {
// 비밀번호 암호화
String encryptedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encryptedPassword);
// 사용자 저장 로직 (Repository 사용)
}
public boolean validatePassword(String rawPassword, String encodedPassword) {
// 비밀번호 검증
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
다음 요구 사항을 만족하는 세션 관리 설정을 작성하세요.
요구 사항:
동일 계정으로 여러 디바이스에서 로그인할 수 없도록 설정하세요.
세션이 만료되면 사용자가 /session-expired 페이지로 이동하도록 설정하세요.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.maximumSessions(1) // 최대 1개의 세션만 허용
.expiredUrl("/session-expired") // 세션 만료 시 이동할 URL
.maxSessionsPreventsLogin(true) // 새 세션 로그인 방지
);
return http.build();
}
}
Spring Security를 사용해 정적 리소스에 대한 접근을 설정하세요.
요구 사항:
/css/**, /js/**, /images/** 경로의 리소스는 인증 없이 접근 가능해야 합니다.
나머지 모든 요청은 인증이 필요합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll() // 정적 리소스 접근 허용
.anyRequest().authenticated() // 나머지 요청은 인증 필요
);
return http.build();
}
}
다음 REST API 보안 설정을 작성하세요.
요구 사항:
API는 Bearer Token을 사용해 인증합니다.
프론트엔드 애플리케이션이 다른 도메인(http://localhost:3000)에서 요청할 수 있도록 CORS를 설정하세요.
/api/public/** 경로는 누구나 접근 가능하며, /api/private/** 경로는 인증된 사용자만 접근 가능합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:3000");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll() // 공개 API
.requestMatchers("/api/private/**").authenticated() // 인증된 사용자만 접근 가능
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
Spring Security에 커스텀 인증 필터를 추가하는 코드를 작성하세요.
요구 사항:
로그인 요청을 /api/login으로 처리하는 커스텀 필터를 추가하세요.
커스텀 필터에서 사용자가 입력한 username과 password를 읽어 인증을 처리합니다.
인증 성공 시 JWT를 생성하고 반환합니다.
public class CustomAuthFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterAt(customAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CustomAuthFilter customAuthFilter() {
CustomAuthFilter filter = new CustomAuthFilter();
filter.setFilterProcessesUrl("/api/login"); // 로그인 URL 설정
return filter;
}
}
위 코드는 CustomAuthFilter와 UsernamePasswordAuthenticationFilter가 같은 요청을 처리할 수 있어 충돌 가능성이 있음.
※ 해결 방안
1. UsernamePasswordAuthenticationFilter 비활성화
2.addFilterBefore 사용
1. UsernamePasswordAuthenticationFilter 비활성화
UsernamePasswordAuthenticationFilter를 비활성화해야 합니다.기본 로그인 비활성화를 하면 UsernamePasswordAuthenticationFilter가 작동하지 않는다
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().disable() // 기본 로그인 필터 비활성화
.addFilterAt(customAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CustomAuthFilter customAuthFilter() {
CustomAuthFilter filter = new CustomAuthFilter();
filter.setFilterProcessesUrl("/api/login"); // 로그인 URL 설정
return filter;
}
}
2. addFilterBefore 사용
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(customAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CustomAuthFilter customAuthFilter() {
CustomAuthFilter filter = new CustomAuthFilter();
filter.setFilterProcessesUrl("/api/login"); // 로그인 URL 설정
return filter;
}
}
setFilterProcessesUrl vs LoginProcessingUrl
- 기본 필터 사용:
- Spring Security의 기본 인증 흐름을 따르며 URL만 변경하고 싶다면
loginProcessingUrl을 사용합니다.- 커스텀 필터 사용:
- 인증 과정을 사용자 정의하고 싶거나, 별도의 필터를 작성했다면
setFilterProcessesUrl을 사용합니다.
로그인 실패 시 커스텀 에러 메시지를 출력하는 Spring Security 설정을 작성하세요.
요구 사항:
/login 페이지에서 사용자가 로그인 실패 시 에러 메시지를 보여줍니다.
에러 메시지는 로그인 페이지에서 error라는 쿼리 파라미터로 전달됩니다.
예: /login?error=Invalid credentials
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin(login -> login
.loginPage("/login")
.failureHandler((request, response, exception) -> {
// 실패 시 에러 메시지 추가
response.sendRedirect("/login?error=" + exception.getMessage());
})
);
return http.build();
}
}