Skip to content

Commit

Permalink
feat: swagger3
Browse files Browse the repository at this point in the history
  • Loading branch information
KimKyoHwee committed Apr 9, 2024
1 parent 6d66867 commit 6eb7fc8
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 119 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ repositories {
}

dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
implementation 'io.swagger.core.v3:swagger-models-jakarta:2.2.7'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/soongsil/CoffeeChat/config/CorsMvcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

@Configuration
public class CorsMvcConfig implements WebMvcConfigurer { //컨트롤러에서 보내는 데이터를 받을수 있게끔
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {

corsRegistry.addMapping("/**") //모든 경로에서 매핑 진행
.exposedHeaders("Set-Cookie") //노출할 헤더값은 쿠키헤더
.allowedOrigins("http://localhost:3000"); //리액트서버주소에서 허용
}
corsRegistry.addMapping("/**") //모든 경로에서 매핑 진행
.exposedHeaders("Set-Cookie") //노출할 헤더값은 쿠키헤더
.allowedOrigins("http://localhost:3000"); //리액트서버주소에서 허용
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
Expand Down Expand Up @@ -101,4 +102,10 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**");
}
}
107 changes: 56 additions & 51 deletions src/main/java/com/soongsil/CoffeeChat/config/jwt/JWTFilter.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.soongsil.CoffeeChat.config.jwt;


import com.soongsil.CoffeeChat.dto.CustomOAuth2User;
import com.soongsil.CoffeeChat.dto.UserDTO;
import com.soongsil.CoffeeChat.entity.User;

import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -18,69 +19,73 @@
import java.io.IOException;
import java.io.PrintWriter;

public class JWTFilter extends OncePerRequestFilter{ //요청당 한번만 실행되면 됨
private final JWTUtil jwtUtil; //JWT검증 위하여 주입
public JWTFilter(JWTUtil jwtUtil){
this.jwtUtil=jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 헤더에서 access키에 담긴 토큰을 꺼냄
String accessToken = request.getHeader("access");
public class JWTFilter extends OncePerRequestFilter { //요청당 한번만 실행되면 됨
private final JWTUtil jwtUtil; //JWT검증 위하여 주입

public JWTFilter(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 헤더에서 access키에 담긴 토큰을 꺼냄
String accessToken = request.getHeader("access");

// 토큰이 없다면 다음 필터로 넘김 (권한이 필요 없는 요청들이 있을 수 있기 때문)
if (accessToken == null) {
// 토큰이 없다면 다음 필터로 넘김 (권한이 필요 없는 요청들이 있을 수 있기 때문)
if (accessToken == null) {

filterChain.doFilter(request, response);
filterChain.doFilter(request, response);

return;
}
return;
}

// 토큰 만료 여부 확인, 만료시 다음 필터로 넘기지 않음
try {
jwtUtil.isExpired(accessToken);
} catch (ExpiredJwtException e) {
// 토큰 만료 여부 확인, 만료시 다음 필터로 넘기지 않음
try {
jwtUtil.isExpired(accessToken);
} catch (ExpiredJwtException e) {

//response body
PrintWriter writer = response.getWriter();
writer.print("access token expired");
//response body
PrintWriter writer = response.getWriter();
writer.print("access token expired");

//response status code(상태반환: 다음 필터로 넘기지 않고 바로 응답 반환)
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //401
return;
}
//response status code(상태반환: 다음 필터로 넘기지 않고 바로 응답 반환)
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //401
return;
}

// 토큰이 access인지 확인 (발급시 페이로드에 명시)
String category = jwtUtil.getCategory(accessToken);
// 토큰이 access인지 확인 (발급시 페이로드에 명시)
String category = jwtUtil.getCategory(accessToken);

if (!category.equals("access")) {
if (!category.equals("access")) {

//response body
PrintWriter writer = response.getWriter();
writer.print("invalid access token");
//response body
PrintWriter writer = response.getWriter();
writer.print("invalid access token");

//response status code(상태반환: 다음 필터로 넘기지 않고 바로 응답 반환)
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//401
return;
}
//response status code(상태반환: 다음 필터로 넘기지 않고 바로 응답 반환)
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//401
return;
}

//토큰에서 username과 role 획득
String username = jwtUtil.getUsername(accessToken);
String role = jwtUtil.getRole(accessToken);
//토큰에서 username과 role 획득
String username = jwtUtil.getUsername(accessToken);
String role = jwtUtil.getRole(accessToken);

//userDTO를 생성하여 값 set
UserDTO userDTO = new UserDTO();
userDTO.setUsername(username);
userDTO.setRole(role);
//userDTO를 생성하여 값 set
UserDTO userDTO = new UserDTO();
userDTO.setUsername(username);
userDTO.setRole(role);

//UserDetails에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
//UserDetails에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);

//스프링 시큐리티 인증 토큰 생성(로그인 진행)
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
//일시적인 세션에 사용자 등록(사용자 요청에 대해서 로그인 상태로 변환됨)
SecurityContextHolder.getContext().setAuthentication(authToken);
//스프링 시큐리티 인증 토큰 생성(로그인 진행)
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null,
customOAuth2User.getAuthorities());
//일시적인 세션에 사용자 등록(사용자 요청에 대해서 로그인 상태로 변환됨)
SecurityContextHolder.getContext().setAuthentication(authToken);

filterChain.doFilter(request, response); //다음 필터로 넘기기
}
filterChain.doFilter(request, response); //다음 필터로 넘기기
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import com.soongsil.CoffeeChat.dto.CustomOAuth2User;
import com.soongsil.CoffeeChat.entity.Refresh;
import com.soongsil.CoffeeChat.repository.RefreshRepository;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -22,65 +24,69 @@
//로그인이 성공했을 때 받은 데이터들을 바탕으로 JWT발급을 위한 핸들러
@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JWTUtil jwtUtil;
private RefreshRepository refreshRepository;
public CustomSuccessHandler(JWTUtil jwtUtil, RefreshRepository refreshRepository){
this.jwtUtil=jwtUtil;
this.refreshRepository=refreshRepository;
}

private void addRefreshEntity(String username, String refresh, Long expiredMs) {
//refresh객체를 만들고 레포에 저장

Date date = new Date(System.currentTimeMillis() + expiredMs);

Refresh refreshEntity = new Refresh();
refreshEntity.setUsername(username);
refreshEntity.setRefresh(refresh);
refreshEntity.setExpiration(date.toString());

refreshRepository.save(refreshEntity);
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

//OAuth2User
CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();

String username = customUserDetails.getUsername();

Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
//토큰 생성
String accessToken = jwtUtil.createJwt("access", username, role, 600000L); //10분
//System.out.println("accessToken = " + accessToken);
String refreshToken = jwtUtil.createJwt("refresh", username, role, 86400000L); //24시간
//Refresh 토큰 저장
addRefreshEntity(username, refreshToken, 86400000L);
//Access토큰은 헤더에, Refresh 토큰은 쿠키에 담아 보내기
response.setHeader("access", accessToken);
response.addCookie(createCookie("refresh", refreshToken));

//login status넣어주기
if(role.equals("ROLE_A")) response.setHeader("loginStatus", "가입필요");
else if(role.equals("ROLE_MENTEE")||role.equals("ROLE_MENTOR")) response.setHeader("loginStatus", "가입완료");
//가입필요 : 추가정보 가입 request넣어줘야함 가입완료 : 발급받은 토큰으로 요청보내면됨

response.setStatus(HttpStatus.OK.value()); //200으로 프론트에 반환쳐주기

response.sendRedirect("http://localhost:3000/"); //프론트의 url에 redirect
}

private Cookie createCookie(String key, String value) {

Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(24*60*60); //24시간
//cookie.setSecure(true); //https에서만 쿠키가 사용되게끔 설정
cookie.setPath("/"); //전역에서 쿠키가 보이게끔 설정
cookie.setHttpOnly(true); //JS가 쿠키를 가져가지 못하게 HTTPOnly설정
return cookie;
}
private final JWTUtil jwtUtil;
private RefreshRepository refreshRepository;

public CustomSuccessHandler(JWTUtil jwtUtil, RefreshRepository refreshRepository) {
this.jwtUtil = jwtUtil;
this.refreshRepository = refreshRepository;
}

private void addRefreshEntity(String username, String refresh, Long expiredMs) {
//refresh객체를 만들고 레포에 저장

Date date = new Date(System.currentTimeMillis() + expiredMs);

Refresh refreshEntity = new Refresh();
refreshEntity.setUsername(username);
refreshEntity.setRefresh(refresh);
refreshEntity.setExpiration(date.toString());

refreshRepository.save(refreshEntity);
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {

//OAuth2User
CustomOAuth2User customUserDetails = (CustomOAuth2User)authentication.getPrincipal();

String username = customUserDetails.getUsername();

Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
//토큰 생성
String accessToken = jwtUtil.createJwt("access", username, role, 600000L); //10분
System.out.println("accessToken = " + accessToken);
String refreshToken = jwtUtil.createJwt("refresh", username, role, 86400000L); //24시간
//Refresh 토큰 저장
addRefreshEntity(username, refreshToken, 86400000L);
//Access토큰은 헤더에, Refresh 토큰은 쿠키에 담아 보내기
response.setHeader("access", accessToken);
response.addCookie(createCookie("refresh", refreshToken));

//login status넣어주기
if (role.equals("ROLE_A"))
response.setHeader("loginStatus", "가입필요");
else if (role.equals("ROLE_MENTEE") || role.equals("ROLE_MENTOR"))
response.setHeader("loginStatus", "가입완료");
//가입필요 : 추가정보 가입 request넣어줘야함 가입완료 : 발급받은 토큰으로 요청보내면됨

response.setStatus(HttpStatus.OK.value()); //200으로 프론트에 반환쳐주기

response.sendRedirect("http://localhost:3000/"); //프론트의 url에 redirect
}

private Cookie createCookie(String key, String value) {

Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(24 * 60 * 60); //24시간
//cookie.setSecure(true); //https에서만 쿠키가 사용되게끔 설정
cookie.setPath("/"); //전역에서 쿠키가 보이게끔 설정
cookie.setHttpOnly(true); //JS가 쿠키를 가져가지 못하게 HTTPOnly설정
return cookie;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.soongsil.CoffeeChat.config.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;

@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {

Info info = new Info()
.title("COGO API 문서")
.description("이상 있으면 말씀 부탁드립니다.");

// Security 스키마 설정
SecurityScheme bearerAuth = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name(HttpHeaders.AUTHORIZATION);

// Security 요청 설정
SecurityRequirement addSecurityItem = new SecurityRequirement();
addSecurityItem.addList("JWT");

return new OpenAPI()
// Security 인증 컴포넌트 설정
.components(new Components().addSecuritySchemes("JWT", bearerAuth))
// API 마다 Security 인증 컴포넌트 설정
.addSecurityItem(addSecurityItem)
.info(info);
}
}
Loading

0 comments on commit 6eb7fc8

Please sign in to comment.