Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Kuma's Curious Paradise

[이룸] 240209 WebSecurityConfig.java 본문

이룸 프로젝트

[이룸] 240209 WebSecurityConfig.java

쿠마냥 2024. 3. 6. 16:41
  1. 클래스명의 이유
    • WebSecurityConfig : Spring Security를 사용하여 웹 애플리케이션의 보안을 구성하는 데 사용되는 일반적인 클래스 이름을 사용
  2. 클래스에 붙은 어노테이션
    • @Configuration : 빈을 등록하고 관리함. 여기서는 보안에 관련된 빈은 여기에 모여 있음을 알림.
    • @EnableWebSecurity : Spring Security 설정을 활성화. 저희 시큐리티 사용할게요! 라는 의미.
    • @EnableMethodSecurity(securedEnabled = true) : 메서드 단위로 시큐리티 설정 가능. securedEnable true로 두면 @Secured 어노테이션을 활용한 설정이 가능.
    이룸 서비스는 user와 admin으로 역할이 나눠져 있고, 그중 admin만 접근할 수 있는 기능들을 구현하고자 하였음. 시간 제약으로 해당 기능을 구현하지 못했지만 추후를 위해 남겨 둠.

  3. 메서드
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
    return configuration.getAuthenticationManager();
}
  • 스프링 시큐리티에서 인증 관련 설정을 맡은 AuthenticationConfiguration 클래스의 객체를 파라미터로 받아서, getAuthenticationManager() 메서드 호출 → authenticationManager(중 기본인 delegator)가 불러와짐 → 이 매니저를 bean으로 등록함

  • AuthenticationManager란?
    사용자가 누구인지 확인하는 문지기! 사용자가 아이디와 비밀번호를 입력 → 올바름 → 해당 사용자를 인증했다는 정보를 담은 Authentication 타입 객체를 생성해 줌
@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}
  • 암호화 담당하는 passwordEncoder 빈을 생성하고 반환하는 메서드. 비밀번호를 암호화하고 싶다?(→ passwordEncoder.encode()) 사용자가 입력한 비밀번호가 db에 저장된 비밀번호와 같은지 확인하고 싶다? (→ passwordEncoder.match()를 불러 줘! 해시값은 똑같이 나오지 않지만, 해시했을 때 match가 되는지는 확인할 수 있음)

  • **BCryptPasswordEncoder**는 안전한 해시 알고리즘인 BCrypt를 사용하여 비밀번호를 해싱.

  • 해싱이란? 데이터를 고정된 크기의 고유한 값으로 변환하는 과정. 이렇게 생성된 값을 해시값, 또는 해시코드라고 부름. 예를 들어, 비밀번호를 해싱해서 저장하면 원본 비밀번호를 저장하지 않아도 해시값이 match되는지 확인하여 로그인 시킬지 말지를 결정할 수 있음.
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
    JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
    filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
    return filter;
}
  • JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);   : JwtAuthenticationFilter 객체를 생성하면서 jwtUtil 클래스를 주입받음. json web token을 사용하여 인증 필터를 구현하겠다는 의미.

  • filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));   : 위에 의존성 주입된 부분을 보면 authenticationConfiguration이 있는데, 이를 인자로 받아서 authenticationManager() 메서드 실행 → AuthenticationManager 타입 객체 생성 → 이를 filter에 설정함

  • 이렇게 설정된 filter는 spring security filter chain에서 jwt를 이용한 로그인 로직을 구현하는 데 사용됨.

@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
    return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
  • JwtAuthorizationFilter 객체를 생성하면서 jwtUtil, userDetailsService를 파라미터로 받음. jwt로는 토큰이 유효한지 확인하고 만료가 되지는 않았는지 살펴보고 userDetailsService로는 사용자의 권한을 확인함. 예를 들어 admin인지 보통 user인지 확인할 수 있음.

  • 이 필터도 spring security filter chain에 들어가서 훌륭히 역할을 수행할 것.

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedHeaders(List.of("*"));
    configuration.setAllowedOrigins(List.of("", "<https://eroom-challenge.com>", "<https://www.eroom-challenge.com>"));
    configuration.addExposedHeader(JwtUtil.AUTHORIZATION_HEADER);
    configuration.addExposedHeader(JwtUtil.REFRESH_TOKEN_HEADER);
    configuration.setAllowCredentials(true);
    configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT", "PATCH", "DELETE","OPTIONS"));
    configuration.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
  • Cors는 cross-origin resource sharing의 약자로, cross-origin 즉 출처가 다른 경우의 자원 공유를 가능(!)하게 하기 위해 있는 설정

  • 이 설정이 존재하며 설정을 제대로 작성했다 → Same-origin policy를 넘어서 다른 출처에서도 리소스를 요청할 수 있다!

  • CorsConfiguration configuration = new CorsConfiguration(); : CorsConfiguration 객체 생성 후, 허용할 헤더, 출처, 노출할 헤더, 허용할 메서드, 허용할 쿠키 사용 등을 설정

  • configuration.setAllowedHeaders(List.of("*")) : *는 와일드카드라고 부르는데, ‘모든’이라는 의미로, 여기서는 클라이언트에서 서버로 전송되는 모든 헤더를 허용하기 위해 사용.

  • configuration.setAllowedOrigins(List.of("", "<https://eroom-challenge.com>", "<https://www.eroom-challenge.com>")) : 허용할 출처. 프론트의 로컬 서버와 프론트에서 배포한 이룸 서버.

  • configuration.addExposedHeader(JwtUtil.AUTHORIZATION_HEADER), configuration.addExposedHeader(JwtUtil.REFRESH_TOKEN_HEADER) :
    노출할 헤더. 서버에서 클라이언트로 전송되는 응답 헤더를 설정하는 것으로, 클라이언트에서 해당 헤더 값을 읽을 수 있도록 허용하는 역할 → 주석 처리 예정. 쿠키를 사용하는 이룸 서비스에는 필요 없음

  • configuration.setAllowCredentials(true)) : 쿠키 사용 허용. 쿠키나 헤더에는 “나 누구야!” 라는 인증 정보를 담고 있는데, 클라이언트가 요청을 보낼 때 이와 같은 인증 정보를 함께 보낼 수 있도록 true로 지정하는 것.

  • configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT", "PATCH", "DELETE","OPTIONS"))) : 허용할 HTTP 메서드. options는 실제 get, post처럼 요청을 보내기 전에 사전 연습으로 보내는 요청. 이 사전 요청이 잘 주고받아져야 실제 요청이 보내짐. → 여기는 왜 와일드카드를 쓸 수 없을까?

  • configuration.setMaxAge(60L) : preflight 요청의 캐시 지속 시간. preflight는 서버와 연결이 잘 되고 있는지 확인하는 요청인데, 이 요청이 얼마만에 한 번 날아갈 것인지 설정. 보통 1분으로 설정한다고 하여 참조함.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(AbstractHttpConfigurer::disable)
            .cors(cors -> cors.configurationSource(corsConfigurationSource()));

    http.sessionManagement((sessionManagement) ->
            sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    );

    http.authorizeHttpRequests((authorizeHttpRequests) ->
            authorizeHttpRequests
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                    .requestMatchers("/**").permitAll()
//                        .requestMatchers("/health","/env").permitAll()
//                        .requestMatchers(HttpMethod.GET, "/api/challenge/{challengeId}/auth").authenticated()
//                        .requestMatchers(HttpMethod.GET,"/", "/main","/api/signup/email","/api/signup/nickname","/auth/callback/kakao", "/api/challenge/**","error").permitAll()
//                        .requestMatchers(HttpMethod.POST,"/api/signup","/api/login").permitAll()
//                        .anyRequest().authenticated()
    );

    http.formLogin(AbstractHttpConfigurer::disable);

    // 필터 관리
    http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
    http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}
  • http.csrf(AbstractHttpConfigurer::disable) : CSRF 보호 기능을 비활성화. csrf 보호 기능은 사용자 보호를 위해 csrf 토큰을 사용하는 것을 말하는데, 우리는 jwt를 사용하므로 해당 기능을 비활성화.

  • http.cors(cors -> cors.configurationSource(corsConfigurationSource())) : CORS 설정을 활성화하고, 위의 corsConfigurationSource() 메서드에서 설정한 대로 가겠다는 뜻.

  • http.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) :
    세션 정책을 ‘Stateless’로 하겠다는 뜻. 클라이언트와 서버가 계속해서 연결되어 있는 게 아니라, 요청 하나하나 마다 연결되어서 서버는 각 요청만을 독립적으로 관리하면 됨. 서버의 부하를 줄이고 한 서버가 망가져도 다른 서버로 요청을 보내면 되기 때문에 확장성을 향상시킴.

  • http.authorizeHttpRequests((authorizeHttpRequests) -> ...) : 특정 url에 대한 접근 권한 설정하는 곳.
    • "/health","/env" 경로에 대한 요청은 모든 사용자에게 허용
    • HttpMethod.GET 방식으로 /api/challenge/{challengeId}/auth 경로에 대한 요청은 인증된 사용자만 허용
    • HttpMethod.GET 방식으로 "/", "/main","/api/signup/email","/api/signup/nickname","/auth/callback/kakao", "/api/challenge/**","error" 경로에 대한 요청은 모든 사용자에게 허용
    • HttpMethod.POST 방식으로 "/api/signup","/api/login" 경로에 대한 요청은 모든 사용자에게 허용
    • 기타 다른 요청들은 인증된 사용자만 허용
    • 기억할 것은 .permitAll() 이냐, .authenticated()냐!
    • permitAll() 이라고 해서 spring security filter를 그냥 통과하는 것은 아니고, authenticationManager 문지기가 인증되든 되지 않든 다 들여다보내 주는 것.
  • http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class), http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) : 필터 적용 순서. jwtAuthorizationFilter → jwtAuthenticationFilter → UsernamePasswordAuthenticationFilter

  • http.formLogin(AbstractHttpConfigurer::disable): 폼 기반 로그인은 이전에 많이 사용되던 방법으로, 사용자가 아이디와 비밀번호를 직접 서버에 전송하다 보니 중간에 탈취될 가능성이 높아서 jwt와 같은 토큰 기반 인증 방식이 생김. 따라서 기존의 폼 기반 로그인 기능은 비활성화함.

  • 이렇게 설정한 security filter chain을 가지고 이룸 애플리케이션의 인증 인가를 관리하자!