Jwt_Security (3) - configuration 구현 -SecurityConfiguration,SwaggerConfiguration

Yu Seong Kim·2024년 1월 5일

SpringBoot

목록 보기
8/31
post-thumbnail

구현 사항

Jwt_Security (2) 포스트에서 만든 jwtProvider와 jwtauthentication 등을 이용하고, 확장하여 회원가입과 로그인 Swagger문서를 활용한 RestApi를 구현하였습니다.

SecurityConfiguration 구현

  • RestApi는 UI를 사용하지 않아서 crsf()를 비활성화 하였습니다.
  • JWT를 이용하였기 때문에 세션관련 메서드 비활성화

SecurityConfigration


@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   private final JwtTokenProvider jwtTokenProvider;

   @Autowired
   public SecurityConfiguration(JwtTokenProvider jwtTokenProvider){
       this.jwtTokenProvider = jwtTokenProvider;
   }

   @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception{
       httpSecurity.httpBasic().disable() // RestAPI는 UI를 사용하지 않기 때문에, 기본설정을 비활성화
               .csrf().disable() //RestAPI는 csrf 보안이 필요 없으므로 비활성화
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               //JwtToken 인증방식이므로 세션을 사용하지 않으므로 비활성화
               .and()
               .authorizeRequests() // 요청에 대한 사용권한 체크
               .antMatchers("/sign-api/sign-in","/sign-api/sign-up","/sign-api/exception")
               .permitAll() //가입과 로그인 주소는 허용
               .antMatchers(HttpMethod.GET,"/product/**").permitAll()
               //product로 시작하는 GET 요청은 허용
               .antMatchers("**exception**").permitAll()
               .anyRequest().hasRole("ADMIN") //나머지 요청은 ADMIN만 접근 가능
               .and()
               .exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
               .and()
               .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
               .and()
               .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);

   }

   @Bean
   public PasswordEncoder passwordEncoder(){
       return PasswordEncoderFactories.createDelegatingPasswordEncoder();
   }

   @Override
   public void configure(WebSecurity webSecurity) {
       webSecurity.ignoring().antMatchers("/v2/api-docs", "/swagger-resources/**",
               "/swagger-ui.html", "/webjars/**", "/swagger/**", "/sign-api/exception");
   }

}
  • SecurityConfiguration 클래스의 주요 메서드는 WebSecurity 파라미터의 configure() 메서드와 HttpSecurity 파라미터의 configure() 메서드가 있습니다.

<HttpSecurity 파라미터의 configure() 메서드>

HttpSecurity의 대표적인 기능

  • 리소스 접근 권한 설정
  • 인증 실패 시 발생하는 예외 처리
  • 인증 로직 커스터마이징
  • csrf,cors 등의 스프링 시큐리티 설정

구분 별 설명

  • httpBasic().disable()
    -> UI를 사용하는 것을 기본값으로 가진 시큐리티 설정을 비활성화
  • csrf().disable()
    -> REST API는 csrf 보안이 필요 없기 때문에 비활성화

CSRF란?
CSRF(Cross-Site Request Forgery)의 줄임말로 '사이트 간 요청 위조'를 의미합니다.
'사이트 간 요청 위조'란?
웹 어플리케이셔의 취약점 중 하나로서 사용자가 자신의 의지와 무관하게 웹 어플리케이션을 대상으로 공격자가 의도한 행동을 함으로써 특정 페이지의 보안을 취약하게 한다거나 수정, 삭제등의 잡업을 하는 공격 방법입니다.
스프링 시큐리티 csrf() 메서드는 기본적으로 CSRF 토큰을 발급해서 클라이언트로부터 요청을 받을 때마다 토큰을 검증하는 방식으로 동작하기 때문에 브라우저 사용 환경이 아니라면 비활성화 해도 크게 문제 되 지않습니다.

  • sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    -> REST API 기반 애플리케이션의 동작 방식을 설정합니다. 현재 JWT 토큰으로 인증을 처리하여 세션을 사용하지 않기 때문에 STATELESS로 설정합니다.
  • authorizeRequest()
    -> 애플리케이션에 들어오는 요청에 대한 사용 권한을 체크한다.
    -> anyMatchers() 메서드는 antPattern을 통해 권한을 설정하는 역할을 합니다.
  • exceptionHandling().acessDeniedHandler()
    ->권한을 확인하는 과정에서 통과하지 못하는 예외가 발생할 경우 예외를 전달합니다.
  • exceptionHandling().authenticationEntryPoint()
    ->인증 과정에서 예외가 발생할 경우 예외를 전달합니다.

<WebSecurity 파라미터의 configure() 메서드>

  • WebSecurity는 HttpSecurity 앞단에 적용됩니다.
  • 전체적으로 스프링 시큐리티의 영향권 밖에 존재합니다.
  • 인증과 인가가 적용되기 전에 동작하는 설정입니다.
  • 즉, 다양한 곳에서 사용되지 않고 인증과 인가가 적용되지 않는 리소스 접근에 대해서만 사용합니다.
  • 현재 메서드에서는 ignoring() 메서드를 사용하여 Swagger와 관련된 경로에 대한 예외 처리를 수행합니다.

CustomAccessDeniedHandler, CustomAuthenticationEntryPoint 구현

시큐리티 설정에서 이 부분들을 보시면, new CustomAccessDeniedHandler(),new CustomAuthenticationEntryPoint()이 있습니다. 이 경우 직접 AccessDeniedHandler와 AuthenticationEntryPoint 를 구현하였습니다.

.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())

< CustomAccessDeniedHandler >

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException e)
            throws IOException, ServletException{
        logger.info("[handle] 접근이 막혔을 경우 리다이렉트");
        response.sendRedirect("/sign-api/exception");
    }


}
  • AccessDeniedHandler는 액세스 권한이 없는 리소스에 접근할 경우 발생하는 예외 입니다.
  • 이 예외를 처리하기 위하여 AccessDeniedHandler 인터페이스가 사용 되고, SecurityConfiguration의 exceptionHandling() 메서드를 통해 추가했습니다.
  • CustomAccessDeniedHandler는 AccessDeniedHandler의 handle()를 오버라이딩 합니다.
  • 이 메서드는 HttpServletRequest, HttpServletResponse, AccessDeniedException를 파라미터로 가져옵니다.

< CustomAuthenticationEntryPoint >

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private Logger logger = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException e)
            throws IOException, ServletException{
        ObjectMapper objectMapper = new ObjectMapper();
        logger.info("[commence] 인증 실패로 response.sendError 발생");

        EntryPointErrorResponse entryPointErrorResponse = new EntryPointErrorResponse();
        entryPointErrorResponse.setMsg("인증이 실패 하였습니다.");

        response.setStatus(401);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(entryPointErrorResponse));

    }
}
  • 위의 AcessDeniedHandler와 크게 다르지 않으며, commence() 메서드를 오버라이딩 합니다..

< EntryPointErrorResponse >

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class EntryPointErrorResponse {
    private String msg;
}
  • 응답값을 설정하기 위해 EntryPointErrorResponse 객체를 사용하여 메시지를 설정합니다.
  • reponse의 상태 코드와 콘텐츠 타입 등을 설정한 후 ObjectMapper를 사용하여
    EntryPointErrorResponse 객체를 바디 값으로 파싱합니다.

SwaggerConfiguration 구현

Swagger문서에 대해서는 나중에 더 자세히 포스팅 하겠습니다!

스웨거문서를 작성하려면 디펜던시를 추가해야 합니다.

    <dependencies>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.springboot.jwt_securityprac"))
                .build();

    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("jwt_security")
                .description("jwt_security")
                .version("1.0.0")
                .build();


    }
}
  • @Configuration: 이 어노테이션은 스프링 빈 구성 클래스임을 나타냅니다. 스프링 애플리케이션 컨텍스트에 이 클래스를 빈으로 등록합니다.

  • @EnableSwagger2: Swagger 2.0 버전을 사용하도록 설정하는 어노테이션입니다. Swagger 2를 활성화합니다.

  • @Bean public Docket api(): Docket 객체를 빈으로 생성하고 반환합니다. Docket은 Swagger 문서를 빌드하고 커스터마이즈하는 데 사용됩니다.

  • apiInfo(): API 문서에 대한 정보를 설정하는 메서드입니다. ApiInfo 객체를 생성하고 반환합니다.

  • select(): Swagger가 문서화할 API를 선택하는데 사용됩니다.

  • apis(RequestHandlerSelectors.basePackage("com.springboot.jwt_securityprac")): com.springboot.jwt_securityprac 패키지에 속한 컨트롤러 클래스에서만 문서화할 API를 선택합니다. 이 패키지 아래에 있는 컨트롤러 클래스들의 API만 문서화됩니다.

  • build(): Docket 객체를 빌드하고 설정을 완료합니다.

  • private ApiInfo apiInfo(): API 문서에 대한 정보를 설정하는 메서드입니다. ApiInfoBuilder를 사용하여 API 문서의 제목, 설명, 버전 등을 설정합니다.

profile
Development Record Page

0개의 댓글