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

feat: application.yml profile 별 분리 및 인증 로직 기본 구현, JWT 관련 로직 구현 #30

Merged
merged 30 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
14e90f0
feat: spring security 관련 의존성 추가
choidongkuen Jan 3, 2024
968125f
feat: spring oauth 2.0 관련 의존성 추가
choidongkuen Jan 3, 2024
0dc8115
refactor: User 엔티티 관련 리팩토링 및 관련 클래스 리팩토링
choidongkuen Jan 4, 2024
6b0fd57
feat: UserAuthentication (인증 객체) 구현
choidongkuen Jan 4, 2024
2f11420
feat: SecurityUtil 구현
choidongkuen Jan 4, 2024
550e344
feat: redis 의존성 추가
choidongkuen Jan 4, 2024
8db4cac
feat: profile 분리 ( 개발, 운영, 테스트, 인증, 데이터소스, 레디스 )
choidongkuen Jan 4, 2024
11b6879
feat: io-netty-dns-native-macos 관련 의존성 추가
choidongkuen Jan 4, 2024
2f26a0e
feat: JWT 관련 설정값 & Redis 관련 설정값 주입 구현
choidongkuen Jan 4, 2024
35f1f4e
feat: application-redis.yml 구현
choidongkuen Jan 4, 2024
eb2f318
feat: jwt 의존성 추가 (#17)
choidongkuen Jan 4, 2024
4e27f1a
feat: jwtAuthenticationFilter 구현 및 관련 Service 구현 (#17)
choidongkuen Jan 4, 2024
9d19b7e
feat: 응답을 위한 TokenResponse 구현 및 기타 구현 (#17)
choidongkuen Jan 4, 2024
993acbd
fix: IntegrationTest 수정 (#17)
choidongkuen Jan 4, 2024
9f480f4
refactor: JwtAuthenticationFilter 불필요한 주석 제거 (#17)
choidongkuen Jan 6, 2024
dbb7adc
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
d76ac8b
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
524cc04
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
7d3edf5
fix: PropertyTest 제거 (#17)
choidongkuen Jan 6, 2024
54a0b86
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
4311b09
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
4f41c73
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
5da01b9
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
c456395
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
78ef69b
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
e8e27c8
feat: securityConfig JwtAuthenticationFilter 적용 (#17)
choidongkuen Jan 6, 2024
b68ca54
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
6477a2a
Merge branch 'develop' into iss-#7
choidongkuen Jan 6, 2024
e0f419b
fix: ci 에러 수정 (#17)
choidongkuen Jan 6, 2024
cc5c25b
Merge remote-tracking branch 'origin/iss-#7' into iss-#7
choidongkuen Jan 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions gradle/spring.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,24 @@ allprojects {
dependencies {
implementation "org.springframework.boot:spring-boot-starter"
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-configuration-processor'

runtimeOnly 'io.netty:netty-resolver-dns-native-macos:4.1.104.Final:osx-aarch_64'
implementation "org.springframework.boot:spring-boot-starter-webflux"

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

implementation "org.springframework.boot:spring-boot-starter-actuator"

implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation 'io.jsonwebtoken:jjwt:0.9.1'


runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

testImplementation "org.springframework.boot:spring-boot-starter-test"
testImplementation 'org.springframework.security:spring-security-test'
}
}
4 changes: 1 addition & 3 deletions src/main/java/net/teumteum/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/teumteum/core/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.teumteum.core.config;

import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
@ConfigurationPropertiesScan("net.teumteum.core.property")
public class AppConfig {
}
4 changes: 2 additions & 2 deletions src/main/java/net/teumteum/core/context/LoginContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

public interface LoginContext {

void setUserId(Long userId);

Long getUserId();

void setUserId(Long userId);

}
11 changes: 5 additions & 6 deletions src/main/java/net/teumteum/core/context/LoginContextImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ public class LoginContextImpl implements LoginContext {

private Long userId;

@Override
public void setUserId(Long userId) {
this.userId = userId;
}

@Override
public Long getUserId() {
return userId;
}

}
@Override
public void setUserId(Long userId) {
this.userId = userId;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

마지막 띄워쓰기 추가해주세용

Copy link
Member

Choose a reason for hiding this comment

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

38 changes: 16 additions & 22 deletions src/main/java/net/teumteum/core/entity/TimeBaseEntity.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
package net.teumteum.core.entity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import java.time.Instant;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.Instant;

@Getter
@SuperBuilder
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class TimeBaseEntity {
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private Instant createdAt;

@Column(name = "created_at", columnDefinition = "TIMESTAMP(6)", nullable = false, updatable = false)
protected Instant createdAt;

@Column(name = "updated_at", columnDefinition = "TIMESTAMP(6)", nullable = false)
protected Instant updatedAt;

@PrePersist
void prePersist() {
var now = Instant.now();

createdAt = createdAt != null ? createdAt : now;
updatedAt = updatedAt != null ? updatedAt : now;
}

@PreUpdate
void preUpdate() {
updatedAt = updatedAt != null ? updatedAt : Instant.now();
}

@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
}

32 changes: 32 additions & 0 deletions src/main/java/net/teumteum/core/property/JwtProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.teumteum.core.property;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties(prefix = "jwt")
public class JwtProperty {

private String bearer;
private String secret;
private Access access;
private Refresh refresh;


@Getter
@Setter
public static class Access{
private long expiration;
private String header;

}

@Getter
@Setter
public static class Refresh {
private long expiration;
private String header;
}
}
13 changes: 13 additions & 0 deletions src/main/java/net/teumteum/core/property/RedisProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.teumteum.core.property;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties(prefix = "data.redis")
public class RedisProperty {
private String host;
private int port;
}
6 changes: 6 additions & 0 deletions src/main/java/net/teumteum/core/security/Authenticated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.teumteum.core.security;

/* 소셜 OAuth 로그인 타입 */
public enum Authenticated {
카카오,네이버;
}
60 changes: 60 additions & 0 deletions src/main/java/net/teumteum/core/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package net.teumteum.core.security;


import lombok.RequiredArgsConstructor;
import net.teumteum.core.security.filter.JwtAuthenticationFilter;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
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.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(request
-> request.requestMatchers("/users").permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.anyRequest().authenticated())
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(sessionManagement
-> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.cors(cors -> cors.configurationSource(this.corsConfigurationSource()));

return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.teumteum.core.security;

import lombok.Getter;
import net.teumteum.user.domain.User;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.ArrayList;
import java.util.List;

@Getter
public class UserAuthentication extends AbstractAuthenticationToken {

private final String oauthId;
private Long id;

public UserAuthentication(User user) {
super(authorities(user));
this.id = user.getId();
this.oauthId = user.getOauth().getOauthId();
}

private static List<GrantedAuthority> authorities(User User) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(User.getRoleType().name()));
return authorities;
}

@Override
public Object getCredentials() {
return null;
}

@Override
public Object getPrincipal() {
return id;
}

@Override
public boolean isAuthenticated() {
return true;
}

public void setUserId(Long userId) {
id = userId;
}
}
18 changes: 18 additions & 0 deletions src/main/java/net/teumteum/core/security/dto/TokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.teumteum.core.security.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class TokenResponse {
private String accessToken;
private String refreshToken;

@Builder
public TokenResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.teumteum.core.security.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.teumteum.core.property.JwtProperty;
import net.teumteum.core.security.UserAuthentication;
import net.teumteum.core.security.service.AuthService;
import net.teumteum.core.security.service.JwtService;
import net.teumteum.user.domain.User;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final AuthService authService;
private final JwtProperty jwtProperty;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
/* Cors Preflight Request */
if (request.getMethod().equals("OPTIONS")) {
return;
}

try {
String token = this.resolveTokenFromRequest(request);
if (checkTokenExistenceAndValidation(token)) {
User user = this.authService.findUserByToken(token).get();
UserAuthentication authentication = new UserAuthentication(user);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (InsufficientAuthenticationException e) {
log.info("JwtAuthentication UnauthorizedUserException!");
}
filterChain.doFilter(request, response);
}

private boolean checkTokenExistenceAndValidation(String token) {
return StringUtils.hasText(token) && this.jwtService.validateToken(token);
}

private String resolveTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(jwtProperty.getAccess().getHeader());
if (!ObjectUtils.isEmpty(token) && token.toLowerCase().startsWith(jwtProperty.getBearer().toLowerCase())) {
return token.substring(jwtProperty.getBearer().length()).trim();
}
return null;
}
}
Loading
Loading