API 인증 필요 여부 개별 관리

이전 글에서 API 인증 필요 여부를 표기하려면 @SecurityRequirement 를 이용했었다.

하지만 이 애노테이션을 클래스에 붙이면 해당  클래스 아래의 모든 엔드포인트에 자물쇠가 표시된다.

만약 그 중 1~2개 정도는 인증이 필요 없어서 자물쇠 표시를 하지 않으려면 어떡 해야할까?

애노테이션을 메서드 단위로 붙여서 인증이 필요 없는 메서드만 안붙이기에는 너무 번거롭다.

이런 상황을 위해 API 인증이 불필요한 메서드에만 애노테이션을 붙여서 자물쇠를 제거하는 방법을 정리해두려 한다.


구현하기

구현 순서는 아래와 같다.

  1. 커스텀 애노테이션 추가
  2. SwaggerConfig 추가 설정
  3. 컨트롤러에 커스텀 애노테이션 추가
  4. 결과 확인

1. 커스텀 애노테이션 추가

SecurityNotRequired 라는 이름의 애노테이션을 추가해준다.

이름은 원하는대로 설정해도 상관 없다.

SecurityNotRequired

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityNotRequired {
}

2. SwaggerConfig 추가 설정 

기존 설정에서 아래처럼 OperationCustomizer 를 빈등록해준다.

여기서 애노테이션을 등록해줄 때는 본인이 만든 커스텀 애노테이션으로 등록해주면 된다.

SwaggerConfig

... 기존 설정

@Bean
public OperationCustomizer customize() {
    return (Operation operation, HandlerMethod handlerMethod) -> {
        SecurityNotRequired annotation = handlerMethod.getMethodAnnotation(SecurityNotRequired.class);
        // SecurityNotRequire 어노테이션있을시 스웨거 시큐리티 설정 삭제
        if (annotation != null) {
            operation.security(Collections.emptyList());
        }
        return operation;
    };
}

3. 컨트롤러에 커스텀 애노테이션 추가

클래스에 @SecurityRequirement 애노테이션을 추가하고

인증이 필요 없는 엔드포인트에만 @SecurityNotRequired 애노테이션을 추가하였다.

4. 결과 확인

스웨거 UI를 보면 아래처럼 애노테이션을 추가해준 부분에는 자물쇠 표시가 사라져있음을 확인할 수 있다.

문제 발생

Nginx, certbot을 이용하여 HTTPS 적용을 마쳤다.

그리고 기분 좋게 스웨거로 잘 되는지 확인해보려 했는데 갑자기 아래처럼 CORS 에러가 발생했다.

문제 해결

분명 HTTPS 적용 전에는 잘 됐는데 무엇이 문제인지 생각해보았다.

바뀐건 HTTPS, Nginx 설정 뿐이어서 이 두가지를 위주로 검색을 해보고 답을 찾았다.

결론은 스웨거 서버 URL 설정 기본 값이 http://localhost:8080 이라서 발생한 문제였다.

HTTPS 적용 전에는 프로토콜이 같으므로 문제없었지만 HTTPS 적용 후 프로토콜이 달라져서 CORS 에러가 발생한듯 하다.

그래서 SwaggerConfg를 아래처럼 수정하여 문제를 해결했다.

SwaggerConfig

AppConfg로 yml 파일로 백엔드 도메인을 환경변수로 분리하였다.

yml 파일은 이 글에서와 동일하다.

이러면 로컬에선 서버 URL이 localhost:8080이고 배포 시에는 배포 도메인으로 바뀌므로 CORS 문제를 해결할 수 있다.

@Configuration
@RequiredArgsConstructor
@SecurityScheme(
    name = "jwt-cookie",
    type = SecuritySchemeType.APIKEY,
    in = SecuritySchemeIn.COOKIE
)
public class SwaggerConfig {

    private final AppConfig appConfig;

    @Bean
    public OpenAPI serverApiConfig() {
        Server server = new Server();
        server.setUrl(appConfig.getBackUrl());
        server.description("백엔드 도메인");
        return new OpenAPI()
            .addServersItem(server)
            .info(new Info().title("PAWLAND API")
                .description("PAWLAND API SWAGGER UI입니다."));
    }
}

HTTPS에서 스웨거

보통 프로젝트를 진행하면서 서비스를 배포하고 도메인을 붙이고 HTTPS를 적용할 것이다.

하지만 스웨거의 기본 서버 URL은 http://localhost:8080 이므로

HTTPS를 적용할 때 스웨거에서 설정을 추가해주지 않으면 이 글처럼 CORS 에러를 만나는 수가 있다.

따라서 환경에 따라 스웨거 서버 설정을 변경해주어야 한다.


1.  SwaggerConfig 설정 추가

SwaggerConfig

아래처럼 서버 설정을 추가하고 환경변수로 분리하여 환경에 맞게 관리한다.

@Configuration
@RequiredArgsConstructor
@SecurityScheme(
    name = "jwt-cookie",
    type = SecuritySchemeType.APIKEY,
    in = SecuritySchemeIn.COOKIE
)
public class SwaggerConfig {

    private final AppConfig appConfig;

    @Bean
    public OpenAPI serverApiConfig() {
        Server server = new Server();
        server.setUrl(appConfig.getBackUrl()); // 여기서 서버 URL 설정 가능 -> 환경 변수로 관리
        server.description("백엔드 도메인");
        return new OpenAPI()
            .addServersItem(server)
            .info(new Info().title("PAWLAND API")
                .description("PAWLAND API SWAGGER UI입니다."));
    }
}

2. 결과 확인

아래처럼 스웨거 서버 URL을 yml으로 관리하여 프로필에 따라 환경 변수를 다르게 할 수 있다.

1. 로컬에서 테스트 시 서버 URL - application-local.yml

2. 배포 시 서버 URL - application-prod.yml

 

인증 필요 여부 표기

엔드 포인트 중에는 인증이 필요한 기능이 있고, 필요 없는 기능이 있다.

API 사용자 입장에서는 직접 사용해서 깨닫기 보다는 사용 전에 이 두가지를 구분해주면 사용하기 더 편리할 것이다.


구현하기

구현 순서는 아래와 같다.

  1. SwaggerConfig 추가 설정
  2. 컨트롤러에 스웨거 UI용 설정 추가
  3. 결과 확인

1.  SwaggerConfig 추가 설정 

아래처럼 SwaggerConfig에 @SecurityScheme 애노테이션을 이용하여 인증 수단을 등록할 수 있다.

본인은 JWT를 쿠키에 넣어서 인증하는 방식을 사용했다.

만약 Authorization 헤더를 사용한다면 알맞게 수정하면 된다.

SwaggerConfig

@Configuration
@RequiredArgsConstructor
@SecurityScheme(
    name = "jwt-cookie",
    type = SecuritySchemeType.APIKEY,
    in = SecuritySchemeIn.COOKIE
)
public class SwaggerConfig {

 


2.  컨트롤러에 스웨거 UI 표기용 설정 추가

UserController

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@SecurityRequirement(name = "jwt-cookie")
@Tag(name = "UserController", description = "유저 정보 관련 컨트롤러 입니다.")
public class UserController {

ㄴ@SecurityRequirement

해당 애노테이션 추가 시 인증이 필요하다는 자물쇠 모양이 생긴다.

name 값에는 SwaggerConfig 에서 @SecurityScheme 을 이용하여 등록한 인증 수단 이름을 넣어준다.

각각 메서드에도 붙일 수 있지만 클래스에 붙이면 모든 엔드포인트가 인증이 필요함을 표시할 수 있다.


3. 결과 확인

아래 사진처럼 인증이 필요한 컨트롤러에는 자물쇠 모양으로 구분할 수 있게 되었다.

스웨거

팀 프로젝트를 하면서 프론트엔드와 백엔드 간에 API 명세서가 필요해졌다.

전엔 스웨거를 안써서 노션에 수동으로 API 명세서를 작성하고 수정했었다.

그래서 최신화가 안되는것도 많았는데 이번엔 API 문서 자동화 툴인 스웨거를 이용해보기로 했다.


사용 기술

  • Spring Boot 3.2.4 / gradle-kotlin
  • Java 17
  • Swagger

1. 의존성 추가

스웨거를 사용하기 위해 의존성을 추가해준다.

스웨거도 버전이 여러가지인듯 하므로 적당히 취향껏 쓰면 될것 같다.

본인이 사용한 스웨거 라이브러리는 이쪽 링크에서 확인할 수 있다. 

// build.gradle.kts
// swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4")

2.  SwaggerConfig 설정

스웨거를 사용하기 위해서는 Config 설정이 필요하다.

아래와 같은 기본 설정을 해주면 사용할 수 있다.

SwaggerConfig

@Configuration
@RequiredArgsConstructor
public class SwaggerConfig {

    @Bean
    public OpenAPI serverApiConfig() {
        return new OpenAPI()
            .info(new Info().title("PAWLAND API")
                .description("PAWLAND API SWAGGER UI입니다."));
    }
}

3.  컨트롤러에 스웨거 UI 표기용 설정

UserController

스웨거는 기본적으로 아래처럼 애노테이션을 붙이는 식으로 UI에 표시한다.

참고로 스웨거를 사용하면 기본적인 요청, 응답 DTO는 알아서 맵핑해준다.

@ModelAttribute 로 쿼리스트링을 여러개 받는 경우는 따로 애노테이션을 붙여야하지만 다음에 다뤄보겠다.

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Tag(name = "UserController", description = "유저 정보 관련 컨트롤러 입니다.")
public class UserController {

    private final UserService userService;

    @PreAuthorize("hasRole('ROLE_USER')")
    @Operation(summary = "내 정보 조회", description = "JWT로 내 정보를 조회합니다.")
    @ApiResponse(responseCode = "200", description = "내 정보 조회 성공")
    @ApiResponse(responseCode = "500", description = "삭제된 회원 또는 잘못된 JWT")
    @GetMapping(value = "/my-info", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<UserInfoResponse> getUserInfo(@AuthenticationPrincipal UserPrincipal userPrincipal) {
        UserInfoResponse userInfo = userService.getUserInfo(userPrincipal.getUsername());
        return ResponseEntity
                .status(OK)
                .body(userInfo);
    }
}

 

ㄴ@Tag

아래 사진에서 보듯 컨트롤러의 최상단의 그룹 이름과 설명을 표시할 수 있다.

ㄴ@Operation

각각의 컨트롤러에 대한 이름과 설명을 표시할 수 있다.

ㄴ@ApiResponse

각각의 응답 코드에 따른 설명을 표시할 수 있다.


4. 결과 확인

포트번호가 8080 기준으로 http://localhost:8080/swagger-ui/index.html 로 들어가면 스웨거를 확인할 수 있다.

+ Recent posts