Skip to content

Commit 8e288bd

Browse files
authored
Merge pull request #33 from selab-hs/dev
[Merge] dev → main 코드 통합
2 parents c6a6322 + cd510cf commit 8e288bd

File tree

83 files changed

+4487
-126
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+4487
-126
lines changed

build.gradle

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,51 @@ repositories {
2424
}
2525

2626
dependencies {
27+
implementation 'org.springframework.boot:spring-boot-starter'
28+
29+
// JPA
2730
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
28-
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
31+
32+
// Thymeleaf
33+
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
34+
35+
// Security
36+
implementation 'org.springframework.boot:spring-boot-starter-security'
37+
implementation 'org.springframework.boot:spring-boot-starter-validation'
38+
39+
// JWT
40+
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
41+
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
42+
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
43+
44+
// Spring Web
2945
implementation 'org.springframework.boot:spring-boot-starter-web'
46+
47+
// Lombok
3048
compileOnly 'org.projectlombok:lombok'
31-
runtimeOnly 'com.mysql:mysql-connector-j'
3249
annotationProcessor 'org.projectlombok:lombok'
50+
51+
// Database
52+
implementation 'com.mysql:mysql-connector-j:8.2.0' // 보안 취약점 패치(>=8.2.0) 적용
53+
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
54+
runtimeOnly 'com.h2database:h2'
55+
56+
// Test tool
3357
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3458
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
59+
testCompileOnly 'org.projectlombok:lombok'
60+
testAnnotationProcessor 'org.projectlombok:lombok'
61+
62+
// Querydsl
63+
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
64+
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
65+
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
66+
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
67+
68+
// Swagger
69+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
3570
}
3671

3772
tasks.named('test') {
3873
useJUnitPlatform()
39-
}
74+
}

src/main/java/com/demo/DemoApplication.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.demo;
22

3+
import com.demo.config.properties.JWTProperties;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
57

68
@SpringBootApplication
9+
@EnableConfigurationProperties(
10+
JWTProperties.class
11+
)
712
public class DemoApplication {
813

914
public static void main(String[] args) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.demo.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.cors.CorsConfiguration;
6+
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
7+
import org.springframework.web.filter.CorsFilter;
8+
9+
/**
10+
* 애플리케이션에서 CORS 설정을 하기 위한 Configuration
11+
*
12+
* @author duskafka
13+
* */
14+
@Configuration
15+
public class CorsConfig {
16+
@Bean
17+
public CorsFilter corsFilter() {
18+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
19+
CorsConfiguration config = new CorsConfiguration();
20+
config.setAllowCredentials(true);
21+
config.addAllowedOriginPattern("*");
22+
config.addAllowedHeader("*");
23+
config.addAllowedMethod("*");
24+
source.registerCorsConfiguration("/api/v1/**", config);
25+
return new CorsFilter(source);
26+
}
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.demo.config;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
6+
7+
/**
8+
* JPA 설정을 설정하기 위한 Configuration
9+
*
10+
* <li>JPAQueryFactory: Querydsl을 사용하기 위해 JPAQueryFactory를 빈으로 등록</li>
11+
*
12+
* @author duskafka
13+
* */
14+
@Slf4j
15+
@EnableJpaAuditing
16+
@Configuration
17+
public class JpaConfig {
18+
19+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.demo.config;
2+
3+
import com.demo.config.properties.JWTProperties;
4+
import com.demo.jwt.JwtFilter;
5+
import com.demo.jwt.TokenProvider;
6+
import com.demo.jwt.TokenValidator;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
10+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
11+
import org.springframework.security.web.DefaultSecurityFilterChain;
12+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
13+
14+
@Configuration
15+
@RequiredArgsConstructor
16+
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
17+
private final TokenProvider tokenProvider;
18+
private final TokenValidator tokenValidator;
19+
private final JWTProperties jwtProperties;
20+
21+
@Override
22+
public void configure(HttpSecurity http) {
23+
http.addFilterBefore(
24+
new JwtFilter(tokenProvider, tokenValidator, jwtProperties),
25+
UsernamePasswordAuthenticationFilter.class
26+
);
27+
}
28+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.demo.config;
2+
3+
import com.demo.config.properties.JWTProperties;
4+
import com.demo.jwt.JwtAccessDeniedHandler;
5+
import com.demo.jwt.JwtAuthenticationEntryPoint;
6+
import com.demo.jwt.TokenProvider;
7+
import com.demo.jwt.TokenValidator;
8+
import com.demo.repository.StudentRepository;
9+
import com.demo.service.CustomLoginService;
10+
import lombok.AccessLevel;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
14+
import org.springframework.context.annotation.Bean;
15+
import org.springframework.context.annotation.Configuration;
16+
import org.springframework.http.HttpMethod;
17+
import org.springframework.security.authentication.AuthenticationManager;
18+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
19+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
20+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
21+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
22+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
23+
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
24+
import org.springframework.security.config.http.SessionCreationPolicy;
25+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
26+
import org.springframework.security.crypto.password.PasswordEncoder;
27+
import org.springframework.security.web.SecurityFilterChain;
28+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
29+
import org.springframework.web.filter.CorsFilter;
30+
import org.springframework.web.cors.CorsUtils;
31+
32+
@Slf4j
33+
@EnableWebSecurity
34+
@EnableMethodSecurity
35+
@Configuration
36+
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
37+
public class SecurityConfig {
38+
private final CorsFilter corsFilter;
39+
private final TokenProvider tokenProvider;
40+
private final TokenValidator tokenValidator;
41+
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
42+
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
43+
private final JWTProperties jwtProperties;
44+
45+
@Bean
46+
public PasswordEncoder passwordEncoder() {
47+
return new BCryptPasswordEncoder();
48+
}
49+
50+
@Bean
51+
public CustomLoginService customLoginService(StudentRepository studentRepository) {
52+
return new CustomLoginService(studentRepository);
53+
}
54+
55+
@Bean
56+
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder, CustomLoginService customLoginService) throws Exception {
57+
return http.getSharedObject(AuthenticationManagerBuilder.class)
58+
.userDetailsService(customLoginService)
59+
.passwordEncoder(passwordEncoder)
60+
.and()
61+
.build();
62+
}
63+
64+
@Bean
65+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
66+
http.csrf(AbstractHttpConfigurer::disable)
67+
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
68+
.exceptionHandling(exception -> {
69+
exception.accessDeniedHandler(jwtAccessDeniedHandler)
70+
.authenticationEntryPoint(jwtAuthenticationEntryPoint);
71+
})
72+
.authorizeHttpRequests(request -> {
73+
request
74+
// UI 라우트: 비로그인 허용
75+
.requestMatchers("/", "/sign-in", "/sign-up").permitAll()
76+
.requestMatchers("/posts", "/posts/*", "/posts/edit/*").permitAll()
77+
.requestMatchers("/js/**", "/css/**", "/images/**", "/favicon.ico", "/error").permitAll()
78+
79+
// API 조회는 비로그인 허용
80+
.requestMatchers(HttpMethod.GET, "/api/v1/posts/**").permitAll()
81+
82+
// API 쓰기 작업은 인증 필요
83+
.requestMatchers(HttpMethod.POST, "/api/v1/posts/**").authenticated()
84+
.requestMatchers(HttpMethod.PUT, "/api/v1/posts/**").authenticated()
85+
.requestMatchers(HttpMethod.DELETE, "/api/v1/posts/**").authenticated()
86+
87+
// 나머지 기존 정책
88+
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/register", "/api/v1/auth/check-id").permitAll()
89+
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
90+
.requestMatchers("/api/v1/students/**").hasAnyRole("ADMIN", "USER")
91+
.requestMatchers(org.springframework.web.cors.CorsUtils::isPreFlightRequest).permitAll()
92+
.anyRequest().authenticated();
93+
})
94+
.sessionManagement(session -> {
95+
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
96+
})
97+
.headers(headers -> {
98+
headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin);
99+
})
100+
.with(new JwtSecurityConfig(tokenProvider, tokenValidator, jwtProperties), customizer -> {
101+
});
102+
return http.build();
103+
}
104+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.demo.config.properties;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
5+
/**
6+
* JWT 토큰 프로퍼티를 가지는 클래스
7+
*
8+
* @author duskafka
9+
* */
10+
@ConfigurationProperties(prefix = "jwt")
11+
public record JWTProperties(
12+
//JWT 토큰이 HTTP 헤더에 담길 때 사용될 헤더 이름 (예: Authorization)
13+
String accessTokenHeader,
14+
15+
//JWT 서명 및 검증에 사용될 비밀 키 (Base64 인코딩된 문자열)
16+
//HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 합니다.
17+
String secret,
18+
19+
//Access Token의 만료 기간 (초 단위)
20+
long accessTokenValidityInHour,
21+
22+
//인증용 키
23+
String authorityKey,
24+
25+
//액세스 토큰 인증 헤더
26+
String bearerHeader
27+
) {
28+
}

src/main/java/com/demo/controller/ApiController.java

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)