Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#89] feat: 내가 속한 모임 리스트 조회 API 구현 #93

Merged
merged 9 commits into from
Dec 6, 2023
44 changes: 25 additions & 19 deletions main/build.gradle
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}

group = 'org.sopt.makers.crew'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
sourceCompatibility = '17'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.5'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
// https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui
implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.5.2'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.5'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.2.0'
implementation 'org.springframework.boot:spring-boot-starter-security'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0'

implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
implementation 'com.auth0:java-jwt:4.4.0'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sopt.makers.crew.main.common.advice;

import org.sopt.makers.crew.main.common.exception.BaseException;
import org.sopt.makers.crew.main.common.response.ApiResponse;
import org.sopt.makers.crew.main.common.response.CommonResponseDto;
import org.sopt.makers.crew.main.common.response.ErrorStatus;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -12,22 +12,24 @@
@RestControllerAdvice
public class ControllerExceptionAdvice {

@ExceptionHandler(BaseException.class)
public ResponseEntity<ApiResponse> handleGlobalException(BaseException ex) {
return ResponseEntity.status(ex.getStatusCode())
.body(ApiResponse.fail(ex.getErrorCode()));
}
@ExceptionHandler(BaseException.class)
public ResponseEntity<CommonResponseDto> handleGlobalException(BaseException ex) {
return ResponseEntity.status(ex.getStatusCode())
.body(CommonResponseDto.fail(ex.getErrorCode()));
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ApiResponse> handleMissingParameter(MissingServletRequestParameterException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.fail(ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode()));
}
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<CommonResponseDto> handleMissingParameter(
MissingServletRequestParameterException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(CommonResponseDto.fail(
ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode()));
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.fail(ex.getMessage()));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<CommonResponseDto> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(CommonResponseDto.fail(ex.getMessage()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.makers.crew.main.common.config;

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

public class CamelCaseNamingStrategy implements PhysicalNamingStrategy {

@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

private Identifier apply(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
if (identifier == null) {
return null;
}
return Identifier.toIdentifier(identifier.getText(), true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.sopt.makers.crew.main.common.config;

import lombok.RequiredArgsConstructor;
import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationEntryPoint;
import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationFilter;
import org.sopt.makers.crew.main.common.config.jwt.JwtTokenProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

private static final String[] SWAGGER_URL = {
"/swagger-resources/**",
"/favicon.ico",
"/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-ui/index.html",
"/docs/swagger-ui/index.html",
"/swagger-ui/swagger-ui.css",
};

private static final String[] AUTH_WHITELIST = {
"/health"
};

@Bean
@Profile("dev")
SecurityFilterChain devSecurityFilterChain(HttpSecurity http) throws Exception {
return http.csrf((csrfConfig) ->
csrfConfig.disable()
)
.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(
authorize -> authorize.requestMatchers(AUTH_WHITELIST).permitAll()
.requestMatchers(SWAGGER_URL).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, jwtAuthenticationEntryPoint),
UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(jwtAuthenticationEntryPoint)
)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sopt.makers.crew.main.common.config;

import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@SecurityScheme(
name = "bearer",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
public class SwaggerConfig {

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Crew API 문서")
.version("0.5.3")
.description("Crew API 문서"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.sopt.makers.crew.main.common.config.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.sopt.makers.crew.main.common.response.ErrorStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final ObjectMapper mapper = new ObjectMapper();

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
setResponse(response, ErrorStatus.UNAUTHORIZED_TOKEN);
}


public void setResponse(HttpServletResponse response, ErrorStatus status) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.sopt.makers.crew.main.common.config.jwt;

import static org.sopt.makers.crew.main.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.sopt.makers.crew.main.common.response.ErrorStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String accessToken = jwtTokenProvider.resolveToken(request);

if (accessToken != null) {
// 토큰 검증
if (jwtTokenProvider.validateToken(accessToken)
== VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만
Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken);
UserAuthentication authentication = new UserAuthentication(userId, null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굉장히.. 특이한... 줄바꿈 형식이네요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니 이거 무슨 구글 자바 스타일 가이드 컨벤션으로 맞춰놓은건데 ,,, 다른 걸로 맞출까요 ,,,? 허허

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞춰보시죠!!

null); //사용자 객체 생성
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request)); // request 정보로 사용자 객체 디테일 설정
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
jwtAuthenticationEntryPoint.commence(request, response,
new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getErrorCode()) {
});
return;
}
}
chain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.makers.crew.main.common.config.jwt;

public enum JwtExceptionType {
VALID_JWT_TOKEN, // 유효한 JWT
INVALID_JWT_SIGNATURE, // 유효하지 않은 서명
INVALID_JWT_TOKEN, // 유효하지 않은 토큰
EXPIRED_JWT_TOKEN, // 만료된 토큰
UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰
EMPTY_JWT // 빈 JWT
}
Loading