@ExternalAPI 를 통해 외부용/내부용 API 구분커스텀 어노테이션인 @ExternalAPI를 생성하고,
@ExternalAPI 가 붙여진 API만 외부에 공개되도록 설정했다.
@ExternalAPI 가 API 메서드단에 붙여져있다면, 해당 API만 외부에 공개된다.@ExternalAPI 가 컨트롤러 클래스단에 붙여져있다면, 해당 컨트롤러의 모든 API가 외부에 공개된다.Docket을 사용해 스웨거 문서를 생성할 수 있다.
groupName, apis등 여러 개의 체인 메서드를 통해 문서에 대한 커스텀 설정이 가능하다.
적용한 커스텀 설정은 다음과 같다.
Docket 빈을 여러개 생성하면 그만큼 다양한 스웨거 문서가 생성된다.
이 때 각 Docket에는 groupName 명시해줘야한다. Docket이 하나만 있을 때는 default라는 이름으로 자동 생성되지만, 여러 개를 만들면 각 Docket들을 구분하기 위해 groupName을 필수로 지정해줘야 한다.
// 예시 코드
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(basePackagePredicate)
.paths(PathSelectors.any()).build()
.groupName("external") // groupName 지정 필요
.apiInfo(apiInfo());
생성된 스웨거 문서를 보면 오른쪽 상단에서 groupName 메서드에 인자로 넣은 이름으로 만들어진 각 문서들을 볼 수 있다. 아래 사진의 예시는 internal과 external 이라는 groupName을 가진 두 개의 Docket을 생성한 결과이다.
스웨거 문서 우측 상단의 Select a spec 을 눌러서 여러 개의 스웨거 문서 중 하나를 선택해서 조회할 수 있다.

외부용 스웨거 문서는 메서드나 클래스단에 @ExternalAPI 가 붙여진 API들만 응답해야한다.
Docket의 체인 메서드 apis 에서는 스웨거 문서에서 어떤 api들을 보여줄 것인지를 지정할 수 있다.
RequestHandlerSelectors 를 사용해 @ExternalAPI 가 메서드와 클래스에 붙여진 API만 조회할 수 있게 설정해주었다.
// 예시 코드
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(Predicates.and( // @ExternalAPI가 붙여진 API만 지정
basePackagePredicate,
Predicates.or(
RequestHandlerSelectors.withMethodAnnotation(ExternalAPI.class),
RequestHandlerSelectors.withClassAnnotation(ExternalAPI.class)
)
))
.paths(PathSelectors.any()).build()
.groupName("external")
.apiInfo(apiInfo());
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
내부용 문서 접근시 spring security를 사용한 간단한 Basic인증을 수행하게 설정했다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${swagger.username}")
private String SWAGGER_USERNAME;
@Value("${swagger.password}")
private String SWAGGER_PASSWORD;
// 내부용 문서에 한해서만 인증 적용
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers(new SwaggerGroupNameRequestMatcher("internal"))
.authenticated()
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(SWAGGER_USERNAME).password("{noop}" + SWAGGER_PASSWORD).roles("USER");
}
}
// {noop}은 비밀번호를 별다른 인코딩없이 저장하겠다는 의미로, 없으면 인코더가 없어서 에러가 발생한다.
// 보안이 낮지만 예시용으로 ~~₩..............
public class SwaggerGroupNameRequestMatcher implements RequestMatcher {
private final String groupName;
public SwaggerGroupNameRequestMatcher(String groupName) {
this.groupName = groupName;
}
// 스웨거 API 문서 조회 API이면서, group 쿼리 파라미터가 멤버 변수값과 같을 때만 매칭
@Override
public boolean matches(HttpServletRequest request) {
String urlPath = request.getServletPath();
String urlParameter = request.getParameter("group");
return urlPath.equals("/v2/api-docs") && groupName.equals(urlParameter);
}
}
// maven 의존성 추가
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>