Spring Security는 API에 대해 접근 권한을 설정을 통해 지정할 수 있습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 회원가입과 로그인 API request는 permitAll, 그 외 모든 요청은 인증 필요
.authorizeHttpRequests()
.antMatchers("허용하기 싶은 API").permitAll()
// 위의 허용하는 API를 제외한 모든 Request에 Role을 가지고 있어야 함
.anyRequest().hasAnyRole("ROLE_USER","ROLE_ADMIN")
return http.build();
}
저는 회원가입, 로그인, Reissue 등 임시등급인 회원도 사용할 수 있는 API를 제외한 모든 API에 접근 권한을 설정했습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// JWT를 사용하기 때문에 Session을 Stateless하게 관리
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 회원가입과 로그인 API 등 request는 permitAll, 그 외 모든 요청은 인증 필요
.and()
.authorizeHttpRequests()
.antMatchers("/api/v1/auth/**").permitAll()
// 권한이 필요
.anyRequest().hasAnyRole("ROLE_USER","ROLE_ADMIN")
.and()
.apply(new JwtSecurityConfig(tokenProvider));
return http.build();
}
위와 같이 ROLE_USER
나 ROLE_ADMIN
권한이 있는 사용자들에게만 회원가입, 로그인을 제외한 API에 접근 권한을 부여했습니다.
하지만, Postman으로 테스트를 해보니
모든 API에서 403 Forbidden
, 권한이 없는 사용자라는 응답이 날아왔습니다... 😢
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 필요한 권한이 없이 접근 시 403
setResponse(response, ErrorCode.USER_NO_PERMISSION);
}
private void setResponse(HttpServletResponse response, ErrorCode errorCode) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().println("{ \"message\" : \"" + errorCode.getMessage()
+ "\", \"code\" : \"" + errorCode.getCode()
+ "\", \"success\" : " + false
+ "}");
}
}
Custom AccessDeniedHandler를 만들어 권한이 없다면 body에 에러메세지가 나가게끔 의도했지만, 무엇이 잘못된 것인지 아무것도 나오지 않았어요..
지속되던 삽질 중 제가 접근 제한 설정을 할 때 메서드를 잘못 사용했다는 것을 깨달았습니다ㅠㅠㅠ
바로, hasAnyRole
을 썼던 것이죠.
그럼, hasAnyRole
을 사용하면 안되는 걸까요?? 🤔
그건 아니에요.. Spring Security에서 잘못 만들어진 메서드를 제공할리 없죠! (Spring 찬양)
저의 문제는 바로 UserDetailsService
를 상속받은 CustomUserDetailsService
에서
UserDetails를 만들 때 저는 Role이 아닌 Authority를 주입했기 때문이에요.
hasAnyRole
메서드를 사용하여 접근을 제한하다보니 모든 Request에 대한 사용자들은 Role을 가지고 있지 않기 때문에 권한이 없다는 403 에러가 발생했던 것이었죠...
private UserDetails createUserDetails(com.infomansion.server.domain.user.domain.User myUser) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(myUser.getAuthority().toString());
return new User(
String.valueOf(myUser.getId()),
myUser.getPassword(),
Collections.singleton(grantedAuthority)
);
}
문제를 파악하고 다음과 같이 hasAnyRole
에서 hasAnyAuthority
로 변경하여 해결했습니다!!
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// JWT를 사용하기 때문에 Session을 Stateless하게 관리
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 회원가입과 로그인 API request는 permitAll, 그 외 모든 요청은 인증 필요
.and()
.authorizeHttpRequests()
.antMatchers("/api/v1/auth/**").permitAll()
// 권한이 필요 -> Role에서 Authority로 변경!!!
.anyRequest().hasAnyAuthority("ROLE_USER","ROLE_ADMIN")
.and()
.apply(new JwtSecurityConfig(tokenProvider));
return http.build();
}
위와 같이 오류메세지도 잘 나오는 것을 확인했어요!!
간단한 문제였는데 많은 시간을 소비했던 지라 혹시 403에러가 발생하시는 분들은 UserDetails를 어떻게 만드셨는지 체크해보시면 좋을 것 같습니다.