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

Test #5

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
27cfedf
erd 추가 수정된 부분 업데이트, 리스폰 형태 수정, 에러 핸들러 수정
Nov 15, 2022
2a4c2e0
공통 리턴 타입 추가
gwon7210 Nov 17, 2022
dbfdfd4
회원가입시 uuid 생성
gwon7210 Nov 17, 2022
e622a91
회원가입, 유저 정보 조회 수정
gwon7210 Nov 19, 2022
66af5ed
응답형식 단일과 리스트로 분리
gwon7210 Nov 19, 2022
75cfc4b
공통 response 마무리
gwon7210 Nov 20, 2022
596072a
유저 정보 조회시 컬럼 추가
gwon7210 Nov 20, 2022
d540424
rest api 데이터 구조 형식 변경
gwon7210 Nov 20, 2022
003fab8
code 조회 api 추가
Nov 22, 2022
710fe3f
유저 스키마 정보 변경
gwon7210 Nov 24, 2022
89e45f1
이메일 인증
gwon7210 Nov 26, 2022
56790fc
이메일 인증 추가
gwon7210 Nov 27, 2022
a2de24d
Add: Security + JWT + redis
seominah Nov 27, 2022
0ad6803
Merge pull request #2 from project-tingting/feature/common
seominah Nov 27, 2022
fbd186b
불필요 클래스 + 파일 제거
gwon7210 Nov 29, 2022
cb801e4
메일 인증 로직 추가
gwon7210 Nov 29, 2022
dc2f6f8
TEST COMMIT
seominah Nov 29, 2022
4e0e20b
Merge pull request #3 from project-tingting/feature/common
seominah Nov 29, 2022
58fd0bc
test 오류 제거
seominah Nov 29, 2022
b6b25bb
userprofile 생성 (진행중)
gwon7210 Nov 30, 2022
5d5cc74
유저 프로퍼티 생성
gwon7210 Dec 1, 2022
c8c7f21
cors 추가 + 만료 토큰 이름 변경
gwon7210 Dec 3, 2022
d2a4ca7
메일 인증 url 수정
gwon7210 Dec 3, 2022
9248d1e
url 오타 수정 + 재 가입 시도시 기존 정보 삭제 로직 추가
gwon7210 Dec 3, 2022
70e08d5
회원가입 시 트렌젝션과 벨류 벨리데이션 분리
gwon7210 Dec 5, 2022
a7ac5fa
회원가입 시 트렌젝션과 벨류 벨리데이션 분리
gwon7210 Dec 5, 2022
e6dc2db
유저 프로퍼티 생성 조회 추가
gwon7210 Dec 6, 2022
1a23efd
토큰에 userId 대신 uuid 넣기
gwon7210 Dec 10, 2022
6112570
tingting토큰 조회 추가 + jwt에서 UserId->uuid로 변경
gwon7210 Dec 10, 2022
73ed8b8
Add: 초대목록 조회 및 파티원 기능 추가
seominah Dec 10, 2022
ebd6e72
데이터 베이스 로컬로 주소 이전
gwon7210 Dec 11, 2022
b6bb428
유저 프로파일 업데이트 추가
gwon7210 Dec 14, 2022
d626071
데이터베이스 아이피 변경
gwon7210 Dec 15, 2022
ed2e7bf
매칭 기능 구현
gwon7210 Dec 17, 2022
786f0c9
매칭 기능 구현
gwon7210 Dec 17, 2022
79d02f1
Add:이메일 인증 확인 시 토큰 정보 추가
seominah Dec 17, 2022
84c5909
Merge branch 'dev' of github.com:project-tingting/tingting_back into dev
seominah Dec 17, 2022
2b486d5
Merge remote-tracking branch 'origin/dev' into dev
gwon7210 Dec 17, 2022
0a8dce2
매칭 기능 구현시 방이 찾으면 새로운방 생성 로직 수정
gwon7210 Dec 18, 2022
3aa7092
메일 인증 재수정
seominah Dec 18, 2022
c29ac4c
임시적으로 풀링 방식으로 채팅 구현
gwon7210 Dec 18, 2022
95ace05
Add: 채팅창 메세지 생성
seominah Dec 18, 2022
dad0146
임시적으로 풀링 방식으로 채팅 구현
gwon7210 Dec 19, 2022
b420cba
미팅룸 구현 시작
gwon7210 Jan 5, 2023
93cd42c
배너 변경
gwon7210 Jan 5, 2023
ec88ab9
배너 변경(test)
gwon7210 Jan 5, 2023
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
68 changes: 67 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}

plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'org.springframework.experimental.aot' version '0.12.1'
id 'org.hibernate.orm'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

group = 'com.date'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
//def querydslVersion = '4.1.3'

configurations {
compileOnly {
Expand All @@ -21,9 +29,39 @@ repositories {
mavenCentral()
}


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'com.querydsl:querydsl-jpa'

// querydsl 디펜던시 추가
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"

//swagger
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
//Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
// validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// implementation group: 'com.querydsl', name: 'querydsl-jpa', version: querydslVersion
// implementation group: 'com.querydsl', name: 'querydsl-core', version: querydslVersion
// implementation group: 'com.querydsl', name: 'querydsl-apt', version: querydslVersion
//
// annotationProcessor "com.querydsl:querydsl-apt:${querydslVersion}:jpa"
// annotationProcessor("jakarta.persistence:jakarta.persistence-api")
// annotationProcessor("jakarta.annotation:jakarta.annotation-api")

developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
runtimeOnly 'mysql:mysql-connector-java'
Expand All @@ -32,7 +70,6 @@ dependencies {
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

tasks.named('test') {
Expand All @@ -52,3 +89,32 @@ tasks.named('bootBuildImage') {
// enableExtendedEnhancement = false
// }
//}

// querydsl 사용할 경로 지정합니다. 현재 지정한 부분은 .gitignore에 포함되므로 git에 올라가지 않습니다.
def querydslDir = "$buildDir/generated/'querydsl'"

// JPA 사용여부 및 사용 경로 설정
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}

// build시 사용할 sourceSet 추가 설정
sourceSets {
main.java.srcDir querydslDir
}


// querydsl 컴파일 시 사용할 옵션 설정
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}

// querydsl이 compileClassPath를 상속하도록 설정
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}

2 changes: 2 additions & 0 deletions src/main/java/com/date/tingting/TingtingApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

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

@EnableJpaAuditing
@EnableJpaRepositories
@SpringBootApplication
public class TingtingApplication {
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/date/tingting/config/QuerydslConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.date.tingting.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuerydslConfiguration {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}

}
33 changes: 33 additions & 0 deletions src/main/java/com/date/tingting/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.date.tingting.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.apiInfo(this.apiInfo())
;
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("TingTing API 명세")
.description("TingTing's API DOC")
.version("1.0")
.build();
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/date/tingting/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.date.tingting.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST","PUT", "DELETE");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.date.tingting.config.jwt;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TYPE = "Bearer";

private final JwtTokenProvider jwtTokenProvider;
private final RedisTemplate redisTemplate;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 1. Request Header 에서 JWT 토큰 추출
String token = resolveToken((HttpServletRequest) request);

// 2. validateToken 으로 토큰 유효성 검사
if (token != null && jwtTokenProvider.validateToken(token)) {
// (추가) Redis 에 해당 accessToken logout 여부 확인
String isLogout = (String)redisTemplate.opsForValue().get(token);
if (ObjectUtils.isEmpty(isLogout)) {
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext 에 저장
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}

// Request Header 에서 토큰 정보 추출
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_TYPE)) {
return bearerToken.substring(7);
}
return null;
}
}
135 changes: 135 additions & 0 deletions src/main/java/com/date/tingting/config/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.date.tingting.config.jwt;

import com.date.tingting.domain.user.UserRepository;
import com.date.tingting.web.responseDto.UserResponse;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Component
public class JwtTokenProvider {

@Autowired
UserRepository userRepository;

private static final String AUTHORITIES_KEY = "auth";
private static final String BEARER_TYPE = "Bearer";
private static final long ACCESS_TOKEN_EXPIRE_TIME = 30 * 60 * 1000L; // 30분
private static final long REFRESH_TOKEN_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L; // 7일

private final Key key;


public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

// 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메서드
public UserResponse.TokenInfo generateToken(Authentication authentication) {
// 권한 가져오기
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));

long now = (new Date()).getTime();
//프론트 요청으로 Uuid 제공
Optional<com.date.tingting.domain.user.User> userInfo = userRepository.findByUserId(authentication.getName());


// Access Token 생성
Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
String accessToken = Jwts.builder()
.setSubject(userInfo.get().getUuid())
.claim(AUTHORITIES_KEY, authorities)
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();

// Refresh Token 생성
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();

return UserResponse.TokenInfo.builder()
.grantType(BEARER_TYPE)
.accessToken(accessToken)
.refreshToken(refreshToken)
.accessTokenExpirationTime(REFRESH_TOKEN_EXPIRE_TIME)
.uuid(userInfo.get().getUuid())
.build();
}

// JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드
public Authentication getAuthentication(String accessToken) {
// 토큰 복호화
Claims claims = parseClaims(accessToken);

if (claims.get(AUTHORITIES_KEY) == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}

// 클레임에서 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

// UserDetails 객체를 만들어서 Authentication 리턴
UserDetails principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}

// 토큰 정보를 검증하는 메서드
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT Token", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT Token", e);
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT Token", e);
} catch (IllegalArgumentException e) {
log.info("JWT claims string is empty.", e);
}
return false;
}

private Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}

public Long getExpiration(String accessToken) {
// accessToken 남은 유효시간
Date expiration = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody().getExpiration();
// 현재 시간
Long now = new Date().getTime();
return (expiration.getTime() - now);
}
}

Loading