Spring Security : 연습문제

이진욱·2024년 12월 10일

이론

목록 보기
4/24

문제 1: 인증 및 권한 설정

아래 요구 사항에 맞게 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();
    }
}

문제 2: 비밀번호 암호화

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);
    }
}






문제 3: 세션 관리

다음 요구 사항을 만족하는 세션 관리 설정을 작성하세요.

요구 사항:
동일 계정으로 여러 디바이스에서 로그인할 수 없도록 설정하세요.
세션이 만료되면 사용자가 /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();
    }
}








문제 4: 정적 리소스 접근

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();
    }
}






문제 5: REST API와 CORS

다음 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();
    }
}








문제 6: 커스텀 인증 필터

Spring Security에 커스텀 인증 필터를 추가하는 코드를 작성하세요.

요구 사항:
로그인 요청을 /api/login으로 처리하는 커스텀 필터를 추가하세요.
커스텀 필터에서 사용자가 입력한 usernamepassword를 읽어 인증을 처리합니다.
인증 성공 시 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 비활성화

  • CustomAuthFilter만 사용하고 싶다면, 기본 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 사용

  • CustomAuthFilter를 기존 UsernamePasswordAuthenticationFilter 앞에 추가하여 우선 처리되도록 설정합니다.

@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을 사용합니다.

문제 7: 로그인 실패 처리

로그인 실패 시 커스텀 에러 메시지를 출력하는 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();
    }
}
profile
열심히 하는 신입 개발자

0개의 댓글