Kuma's Curious Paradise
[이룸] 240209 WebSecurityConfig.java 본문
- 클래스명의 이유
- WebSecurityConfig : Spring Security를 사용하여 웹 애플리케이션의 보안을 구성하는 데 사용되는 일반적인 클래스 이름을 사용
- 클래스에 붙은 어노테이션
- @Configuration : 빈을 등록하고 관리함. 여기서는 보안에 관련된 빈은 여기에 모여 있음을 알림.
- @EnableWebSecurity : Spring Security 설정을 활성화. 저희 시큐리티 사용할게요! 라는 의미.
- @EnableMethodSecurity(securedEnabled = true) : 메서드 단위로 시큐리티 설정 가능. securedEnable true로 두면 @Secured 어노테이션을 활용한 설정이 가능.
- 메서드
@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을 가지고 이룸 애플리케이션의 인증 인가를 관리하자!
'이룸 프로젝트' 카테고리의 다른 글
[이룸] 240213 max-age와 expires의 차이 (0) | 2024.03.06 |
---|---|
[이룸] 240211 Builder 사용 이유 (0) | 2024.03.06 |
[이룸] 240205 왜 우리는 생성자 주입을 쓰는가? (1) | 2024.03.06 |
[이룸] 240203 클라이언트가 쿠키를 받지 못하는 이유 : SameSite 설정 (0) | 2024.03.06 |
[이룸] 240202 CORS 에러 이해하기 (1) | 2024.03.06 |