목차
1. Swagger 정의
2. build.gradle 설정
3. SwaggerConfig 작성
4. Controller 작성 방법 / Dto 작성 방법
5. SecurityConfig 수정
6. Swagger 접속
7. Authorize로 전역적 Bearer 토큰 적용
1. Swagger 정의
Swagger는 개발자가 REST 서비스를 설계, 빌드, 문서화, 소비하는 일을 도와주는 오픈 소스 소프트웨어 프레임워크이다.
서버 개발 시에 Postman으로 테스트하고, 노션 API 명세서에 Request, Response 타입이나 예시를 일일이 정리하지 않아도 되므로 프론트와 백엔드 개발자 간 소통을 용이하게 해준다.
해당 포스팅은 Swagger 3.0 버전을 적용한 예제를 기반으로 작성했다.
2. build.gradle 설정
build.gradle의 dependency에 다음과 같은 의존성을 추가해준다.
// Swagger
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation "io.springfox:springfox-swagger-ui:3.0.0"
3. SwaggerConfig 작성
- api() : Swagger 문서 메타데이터 및 보안 설정을 정의
- apiInfo() : API 문서에 대한 메타데이터를 정의
- securityContext() : JWT SecurityContext를 구성
- defaultAuth() : 기본 인증 설정 정의 [ Authorization 헤더에 Bearer 토큰 사용하는 것을 정의 ]
- apiKey() : API 헤더에 JWT 토큰을 설정하는 방식을 정의
@Configuration
public class SwaggerConfig {
private static final String API_NAME = "Memotion API";
private static final String API_VERSION = "0.0.1";
private static final String API_DESCRIPTION = "Memotion API";
// Swagger 문서 메타데이터 및 보안 설정 정의
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
// JWT 인증 구성
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(apiKey()))
.select()
.apis(RequestHandlerSelectors.
basePackage("com.hanium.memotion"))
// 어떤 API 경로를 문서화할지 지정
.paths(PathSelectors.any()).build().apiInfo(apiInfo());
}
// API 문서에 대한 메타데이터 정의
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(API_NAME)
.version(API_VERSION)
.description(API_DESCRIPTION)
.build();
}
// JWT SecurityContext 구성
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.build();
}
// 기본 인증 설정 정의 : Authorization 헤더에 Bearer 토큰 사용하는 것을 정의
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return List.of(new SecurityReference("Authorization", authorizationScopes));
}
// API 헤더에 JWT 토큰을 설정하는 방식을 정의
private ApiKey apiKey() {
return new ApiKey("Authorization", "Bearer", "header");
}
}
4. Controller 작성 + Dto 작성 방법
Controller 예시
- @Api(tags = {"어쩌공"}) : API 카테고리 정의
- @ApiOperation(value = "API 제목", notes = "API 상세 설명") : API 엔드포인트에 대한 설명 및 정보 제공
- @ApiImplicitParams : @ApiImplicitParam을 포함하는 배열로 여러개의 입력 매개변수에 대한 정보 제공
- @ApiImplicitParam(name = "매개변수 이름", value = "매개변수 설명", paramType = "매개변수 유형") : 매개변수에 대한 정보 정의
@RestController
@RequiredArgsConstructor
@RequestMapping("/route")
@Api(tags = {"Route API"})
public class RouteController {
private final RouteService routeService;
// 내가 작성한 루트 기록 조회 -> 루트 기록 + 로컬 가이드 메인 화면
@ApiOperation(value = "내가 작성한 루트 기록 조회", notes = "내가 작성한 모든 루트 기록을 조회하여 리스트 형태로 반환")
@GetMapping("")
public BaseResponse<List<RouteResDto>> getRouteList(@AuthenticationPrincipal Member member) {
List<RouteResDto> routeResDto = routeService.getRouteList(member);
return BaseResponse.onSuccess(routeResDto);
}
// 루트 추가 -> 루트 기록 + 로컬 가이드 메인 화면
@ApiOperation(value = "루트 저장", notes = "루트 추가 성공 시 RouteId를 반환")
@ApiImplicitParams({
@ApiImplicitParam(name = "routeReqDto", value = "이름, 시작일, 종료일, 지역을 RequestBody 형태로 작성")
})
@PostMapping("")
public BaseResponse<Long> postRoute(@AuthenticationPrincipal Member member, @RequestBody @Valid RouteReqDto routeReqDto) {
Long routeId = routeService.postRoute(member, routeReqDto);
return BaseResponse.onSuccess(routeId);
}
// 로컬 가이드 조회 (최신순) -> 루트기록 + 로컬 가이드 메인 화면 and 로컬 가이드 화면
@ApiOperation(value = "로컬 가이드 조회(최신순)", notes = "최신순으로 루트 기록을 조회하여 리스트 형태로 반환")
@GetMapping("/localGuide")
public BaseResponse<List<LocalGuideResDto>> getLocalGuideList(@AuthenticationPrincipal Member member) {
List<LocalGuideResDto> localGuideResDto = routeService.getLocalGuideList(member);
return BaseResponse.onSuccess(localGuideResDto);
}
// 로컬가이드 조회 (인기 지역) -> 로컬 가이드 화면
@ApiOperation(value = "로컬 가이드 조회(인기 지역)", notes = "선택한 인기 지역에 해당하는 루트 기록을 조회하여 리스트 형태로 반환")
@ApiImplicitParams({
@ApiImplicitParam(name = "latitude", value = "조회하고자 하는 지역의 위도를 RequestParam 형태로 작성"),
@ApiImplicitParam(name = "longitude", value = "조회하고자 하는 지역의 경도를 RequestParam 형태로 작성"),
})
@GetMapping("/localGuide/popular")
public BaseResponse<List<LocalGuideResDto>> getLocalGuideListByPopularRegion(@AuthenticationPrincipal Member member, @RequestParam Double latitude, @RequestParam Double longitude) {
List<LocalGuideResDto> localGuideResDto = routeService.getLocalGuideListByPopularRegion(member, latitude, longitude);
return BaseResponse.onSuccess(localGuideResDto);
}
// 로컬가이드 조회 (검색어) -> 로컬 가이드 화면
@ApiOperation(value = "로컬 가이드 조회(지역)", notes = "선택한 지역에 해당하는 루트 기록을 조회하여 리스트 형태로 반환")
@ApiImplicitParams({
@ApiImplicitParam(name = "latitude", value = "조회하고자 하는 지역의 위도를 RequestParam 형태로 작성"),
@ApiImplicitParam(name = "longitude", value = "조회하고자 하는 지역의 경도를 RequestParam 형태로 작성"),
})
@GetMapping("/localGuide/search")
public BaseResponse<List<LocalGuideResDto>> getLocalGuideListByRegion(@AuthenticationPrincipal Member member, @RequestParam Double latitude, @RequestParam Double longitude) {
List<LocalGuideResDto> localGuideResDto = routeService.getLocalGuideListByRegion(member, latitude, longitude);
return BaseResponse.onSuccess(localGuideResDto);
}
}
Dto 예시
- @ApiModelProperty(example = "매개변수 예제 값 지정") : Model 클래스의 필드에 적용
@Data
public class PostReqDto {
@ApiModelProperty(example = "제목")
private String title;
@ApiModelProperty(example = "첨부한 URL")
private String url;
@ApiModelProperty(example = "배달팁")
private int delivery_tips;
@ApiModelProperty(example = "최소 주문 금액")
private int minimum;
@ApiModelProperty(example = "주문 시간")
private String order_time;
@ApiModelProperty(example = "모집 인원")
private int num_of_recruits;
@ApiModelProperty(example = "모집된 인원")
private int recruited_num;
@ApiModelProperty(example = "공고 상태")
private String status;
@ApiModelProperty(example = "위도")
private Double latitude;
@ApiModelProperty(example = "경도")
private Double longitude;
@ApiModelProperty(example = "카테고리")
private String category;
}
5. SecurityConfig 수정
configure 메소드에 아래의 uri를 등록하여 Spring Security Filter가 토큰 여부와 유효성을 검증하지 않도록 해준다.
Configure 메소드에 등록할 URI |
"/v3/api-docs" |
"/swagger-resources/**" |
"/swagger-ui/**" |
"/webjars/**" |
"/swagger/**" |
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 필터 체인에 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtProvider jwtProvider;
private final JwtExceptionFilter jwtExceptionFilter;
// 스프링시큐리티 앞단 설정
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/member/signup", "/member/login" , "/member/check-password",
"/member/check-email", "/member/logout", "/member/kakao",
"/h2-console/**", "/sample", "/sentiment",
"/gpt-sentiment", "/s3" , "/v3/api-docs", "/swagger-resources/**",
"/swagger-ui/**", "/webjars/**", "/swagger/**");
}
// 스프링시큐리티 설정
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors().configurationSource(corsConfigurationSource()) // 크로스 오리진 정책 안씀 (인증 X) , 시큐리티 필터에 등록 인증 O
.and()
// 세션을 사용하지 않기 때문에 STATELESS 로 설정
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtProvider))
.authorizeRequests()
.antMatchers("/member/signup").permitAll()
.antMatchers("/member/login").permitAll()
.antMatchers("/member/check-password").permitAll()
.antMatchers("/member/check-email").permitAll()
.antMatchers("/member/logout").permitAll()
.antMatchers("/member/kakao").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/sample").permitAll()
.antMatchers("/sentiment").permitAll()
.antMatchers("/gpt-sentiment").permitAll()
.antMatchers("/s3").permitAll()
.antMatchers("/v3/api-docs").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/swagger/**").permitAll()
.anyRequest().authenticated()
.and()
// JwtAuthenticationFilter 보다 jwtExceptionFilter를 먼저 실행
.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class);
}
@Bean
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", getDefaultCorsConfiguration());
return source;
}
private CorsConfiguration getDefaultCorsConfiguration() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(
Arrays.asList("http://localhost:9000"));
configuration.setAllowedHeaders(Arrays.asList("*")); // 모든 header 에 응답을 허용
configuration.setAllowedMethods(Arrays.asList("*")); // 모든 get,post,patch,put,delete 요청 허용
configuration.setAllowedOrigins(Arrays.asList("*")); // 모든 ip 응답을 허용
configuration.setAllowCredentials(true); // 내 서버가 응답할 때 json 을 자바스크립트에서 처리할 수 있게 할지를 설정하는 것
configuration.setMaxAge(7200L);
return configuration;
}
// 비밀번호 암호화
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
6. Swagger 접속
⚠️ Swagger 접속 URL은 Swagger 3.X 버전과 2.X 버전에 약간의 차이가 있다.
- 3.X : http://[IP주소]:[포트번호]/swagger-ui/index.html
- 2.X : http://[IP주소]:[포트번호]/swagger-ui.html
8080 포트를 사용하는 로컬에서 서버를 띄운 경우
http://localhost:8080/swagger-ui/index.html 로 접속하면 결과를 확인할 수 있다.
SwaggerConfig에서 securityContext를 잘 작성해주었기 때문에 Authorize 버튼이 생성된 것 또한 확인할 수 있다.
7. Authorize로 전역적 Bearer 토큰 적용
Authorize 버튼을 누르면 아래와 같은 화면이 뜬다.
SwaggerConfig에서 Authorization 헤더에 Bearer 토큰을 넣어주겠다고 설정해뒀으니
로그인해서 발급받은 AccessToken을 Value 값으로 넣어주면 토큰이 필요한 모든 API에 자동으로 토큰을 넣어 요청을 보내준다.
단, Bearer + 한 칸 띄어쓰기 + AccessToken 형태로 값을 넣어주어야 한다!
Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtZW1vdGlvbiIsImlhdCI6MTY5ODczMjgyNywic3ViIjoiMSIsImV4cCI6MTY5ODczNjQyNywibWVtYmVySWQiOjF9.pc2Kk6B_St9GLDnEhEFNyofGh52UMlWnZug3oEvQeqQ
실제로 토큰이 필요한 API 요청을 테스트해보면 Authorize로 토큰을 전역적으로 처리해주었기 때문에
성공적으로 API 요청을 보내고 응답값을 받아오는 것을 확인할 수 있다.
'Framework > Spring' 카테고리의 다른 글
[AWS] S3 활용한 SpringBoot 파일 업로드 프로젝트 (0) | 2023.12.15 |
---|---|
[SpringBoot] Naver CLOVA Sentiment를 활용한 감정분석 (0) | 2023.08.30 |
[SpringBoot] ChatGPT를 활용한 API 작성 (0) | 2023.08.21 |
[SpringBoot] JPA ConverterNotFoundException 에러 (0) | 2023.05.16 |
[SpringBoot] JPA NoViableAltException: unexpected token: value 에러 (0) | 2023.04.02 |