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 17 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'
}
}
3 changes: 2 additions & 1 deletion src/main/java/net/teumteum/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

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

@EnableJpaAuditing

@SpringBootApplication
public class Application {

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.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.stereotype.Component;

@Component
@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);

}
10 changes: 5 additions & 5 deletions src/main/java/net/teumteum/core/context/LoginContextImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public class LoginContextImpl implements LoginContext {
private Long userId;

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

@Override
public Long getUserId() {
return userId;
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.

20 changes: 3 additions & 17 deletions src/main/java/net/teumteum/core/entity/TimeBaseEntity.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
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 org.springframework.data.jpa.domain.support.AuditingEntityListener;

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

@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();
}

}
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 {
카카오,네이버;
}
58 changes: 58 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,58 @@
package net.teumteum.core.security;


import lombok.RequiredArgsConstructor;
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.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
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {

// authentication 필요 없는 url 정보
private final String[] allowedUrl = {"/auth/reissue", "/users/signup"};

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

return http.build();
}

/* Cors 관련 설정 */
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*"); // Access-Control-Allow-Origin
configuration.addAllowedMethod("*"); // Access-Control-Allow-Methods
configuration.addAllowedHeader("*"); // Access-Control-Allow-Headers
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,52 @@
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;

/**
* - Security Context Holder 에 주입되는 Authentication 을 구현한 AbstractAuthenticationToken
* - 인증 후 UserAuthentication 을 SecurityContext 에 저장 시, Security Context Holder 로 어디서든 접근 가능 !!
**/

@Getter
public class UserAuthentication extends AbstractAuthenticationToken {

private Long id;
private final String oauthId;

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,69 @@
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.core.security.service.RedisService;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.UserRepository;
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;

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

private final JwtProperty jwtProperty;
private final UserRepository userRepository;

@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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.teumteum.core.security.service;

import lombok.RequiredArgsConstructor;
import net.teumteum.user.domain.User;
import net.teumteum.user.domain.UserConnector;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class AuthService {
private final JwtService jwtService;
private final UserConnector userConnector;
public Optional<User> findUserByToken(String accessToken) {
Long id = Long.parseLong(jwtService.getUserIdFromToken(accessToken));
return userConnector.findUserById(id);
}
}
Loading
Loading