Skip to content

Commit

Permalink
[BE] setting: signaling server base entity, jwt 세팅 (#30)
Browse files Browse the repository at this point in the history
Signed-off-by: EunJiJung <[email protected]>
  • Loading branch information
bianbbc87 committed Feb 5, 2025
1 parent 831968b commit e9911d8
Show file tree
Hide file tree
Showing 25 changed files with 736 additions and 17 deletions.
20 changes: 20 additions & 0 deletions src/backend/signaling-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ ext {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

implementation 'org.springframework.cloud:spring-cloud-starter'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'

Expand All @@ -39,11 +41,29 @@ dependencies {

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-oauth2-client'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// AWS
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.asyncgate.signaling_server.config;

import com.asyncgate.signaling_server.security.info.CustomUserPrincipal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.Optional;

@EnableJpaAuditing
@Configuration
public class BaseEntityConfig {

@Bean("user-auditorProvider")
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication == null || !authentication.isAuthenticated()) {
return Optional.of("AnonymousNULL");
}

Object principal = authentication.getPrincipal();

if (principal instanceof CustomUserPrincipal) {
return Optional.of(((CustomUserPrincipal) principal).getId());
}

return Optional.of("AnonymousNOT_TYPE");
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.asyncgate.signaling_server.config;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.ArrayList;
import java.util.Collections;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CorsConfig {

public static CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

//리소스를 허용
ArrayList<String> allowedOriginPatterns = new ArrayList<>();
allowedOriginPatterns.add("http://localhost:5173"); // vite
allowedOriginPatterns.add("http://127.0.0.1:5173");
configuration.setAllowedOrigins(allowedOriginPatterns);

//허용하는 HTTP METHOD
ArrayList<String> allowedHttpMethods = new ArrayList<>();
allowedHttpMethods.add("GET");
allowedHttpMethods.add("POST");
allowedHttpMethods.add("PUT");
allowedHttpMethods.add("PATCH");
allowedHttpMethods.add("DELETE");
allowedHttpMethods.add("OPTIONS");
configuration.setAllowedMethods(allowedHttpMethods);

configuration.setAllowedHeaders(Collections.singletonList("*"));
// configuration.setAllowedHeaders(List.of(HttpHeaders.AUTHORIZATION, HttpHeaders.CONTENT_TYPE));

//인증, 인가를 위한 credentials 를 TRUE로 설정
configuration.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);

return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.asyncgate.signaling_server.domain;

public interface Identifiable {
String getId();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.asyncgate.signaling_server.entity.common;

import com.asyncgate.user_server.entity.common.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends BaseTimeEntity {

@CreatedBy
@Column(updatable = false)
private String createdBy;

@LastModifiedBy
private String lastModifiedBy;

private boolean deleted;

// 재활성화 - soft delete
public void activate() {
this.deleted = false;
}

// 비활성화 - soft delete
public void deactivate() {
this.deleted = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.asyncgate.signaling_server.entity.common;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;

@LastModifiedDate
private LocalDateTime lastModifiedDate;

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ public enum FailType {

// 알 수 없는 에러
_UNKNOWN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Server_5000", "알 수 없는 에러가 발생하였습니다."),
_CONCURRENT_UPDATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Server_5001", "동시 업데이트 에러가 발생하였습니다."),

// S3
_UPLOAD_FILE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3_5001", "S3 이미지 업로드에 실패하였습니다."),
_DELETE_FILE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3_5002", "S3 이미지 제거에 실패하였습니다."),
_FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "S3_5003", "파일이 S3에 존재하지 않습니다.");
_FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "S3_5003", "파일이 S3에 존재하지 않습니다."),

// Unauthorized Error
INVALID_HEADER_ERROR(HttpStatus.UNAUTHORIZED, "Member_40108", "헤더가 올바르지 않습니다."),

// Access Denied Error
ACCESS_DENIED(HttpStatus.FORBIDDEN, "Access_40300", "접근 권한이 없습니다."),
NOT_LOGIN_USER(HttpStatus.FORBIDDEN, "Access_40301", "로그인하지 않은 사용자입니다.");

private final HttpStatus status;
private final String errorCode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.asyncgate.signaling_server.security.annotation;

import java.lang.annotation.*;

@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MemberID {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.asyncgate.signaling_server.security.config;

import com.asyncgate.signaling_server.config.CorsConfig;
import com.asyncgate.signaling_server.security.constant.Constants;
import com.asyncgate.signaling_server.security.filter.JsonWebTokenAuthenticationFilter;
import com.asyncgate.signaling_server.security.usecase.AuthenticateJsonWebTokenUseCase;
import com.asyncgate.signaling_server.security.utility.JsonWebTokenUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final AuthenticateJsonWebTokenUseCase authenticateJsonWebTokenUseCase;

private final JsonWebTokenUtil jsonWebTokenUtil;

@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.cors(cors -> cors
.configurationSource(CorsConfig.corsConfigurationSource())
)
.csrf(AbstractHttpConfigurer::disable)

.httpBasic(AbstractHttpConfigurer::disable)

.sessionManagement(configurer -> configurer
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)

.authorizeHttpRequests(configurer -> configurer
.requestMatchers(Constants.NO_NEED_AUTH_URLS.toArray(String[]::new)).permitAll()
.anyRequest().authenticated()
)

// 빈 주입
.addFilterBefore(
new JsonWebTokenAuthenticationFilter(
authenticateJsonWebTokenUseCase,
jsonWebTokenUtil
),
LogoutFilter.class
)

.getOrBuild();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.asyncgate.signaling_server.security.config;

import com.asyncgate.signaling_server.security.resolver.HttpMemberIDArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final HttpMemberIDArgumentResolver memberIDArgumentResolver;

public WebConfig(HttpMemberIDArgumentResolver memberIDArgumentResolver) {
this.memberIDArgumentResolver = memberIDArgumentResolver;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(memberIDArgumentResolver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.asyncgate.signaling_server.security.constant;

import java.util.List;

public class Constants {

// JWT
public static String MEMBER_ID_ATTRIBUTE_NAME = "MEMBER_ID";
public static String MEMBER_ID_CLAIM_NAME = "mid";

// HEADER
public static String BEARER_PREFIX = "Bearer ";
public static String AUTHORIZATION_HEADER = "Authorization";


/**
* 인증이 필요 없는 URL
*/
public static List<String> NO_NEED_AUTH_URLS = List.of(
// Authentication/Authorization
"/", // root
"/actuator/info",
"/health",

// Swagger
"/api-docs.html",
"/api-docs/**",
"/swagger-ui/**",
"/v3/**"
);

/**
* Swagger 에서 사용하는 URL
*/
public static List<String> SWAGGER_URLS = List.of(
"/api-docs.html",
"/api-docs",
"/swagger-ui",
"/v3"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.asyncgate.signaling_server.security.exception;

// 각 application에 맞는 failType으로 정의해주세요 !
import com.asyncgate.signaling_server.exception.FailType;
import lombok.Getter;

@Getter
public class CommonException extends RuntimeException {

private final FailType failType;

public CommonException(FailType failType) {
super(failType.getMessage());
this.failType = failType;
}
}
Loading

0 comments on commit e9911d8

Please sign in to comment.