Skip to content

Commit

Permalink
Merge pull request #7 from SWYP-InNOut/6-카카오-로그인
Browse files Browse the repository at this point in the history
Feat(#6): 카카오로그인
  • Loading branch information
dainshon authored Jul 13, 2024
2 parents 0c63f33 + 3d33c48 commit 17b5b0f
Show file tree
Hide file tree
Showing 14 changed files with 603 additions and 40 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ jobs:
DATASOURCE_PASSWORD: ${{ secrets.DATASOURCE_PASSWORD }}
DATASOURCE_URL_DEV: ${{ secrets.DATASOURCE_URL_DEV }}
DATASOURCE_URL_PROD: ${{ secrets.DATASOURCE_URL_PROD }}
KAKAO_API_KEY: ${{ secrets.KAKAO_API_KEY }}
KAKAO_REDIRECT_URI: ${{ secrets.KAKAO_REDIRECT_URI }}
REDIS_HOST: ${{ secrets.REDIS_HOST }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
REDIS_PORT: ${{ secrets.REDIS_PORT }}

- name: Archive build artifacts
uses: actions/upload-artifact@v2
Expand Down Expand Up @@ -71,12 +76,16 @@ jobs:
--build-arg DATASOURCE_URL_DEV=${{ secrets.DATASOURCE_URL_DEV }} \
--build-arg DATASOURCE_URL_PROD=${{ secrets.DATASOURCE_URL_PROD }} \
--build-arg REGION=${{ secrets.REGION }} \
--build-arg KAKAO_API_KEY=${{ secrets.KAKAO_API_KEY }} \
--build-arg KAKAO_REDIRECT_URI=${{ secrets.KAKAO_REDIRECT_URI }} \
--build-arg REDIS_HOST=${{ secrets.REDIS_HOST }} \
--build-arg REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }} \
--build-arg REDIS_PORT=${{ secrets.REDIS_PORT }} \
-t ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPO_NAME }} .
sudo docker images
sudo docker push ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPO_NAME }}:latest
- name: Deploy to prod
uses: appleboy/ssh-action@master
with:
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ ARG DATASOURCE_URL_DEV
ENV DATASOURCE_URL_DEV=${DATASOURCE_URL_DEV}
ARG DATASOURCE_URL_PROD
ENV DATASOURCE_URL_PROD=${DATASOURCE_URL_PROD}
ARG KAKAO_API_KEY
ENV KAKAO_API_KEY=${KAKAO_API_KEY}
ARG KAKAO_REDIRECT_URI
ENV KAKAO_REDIRECT_URI=${KAKAO_REDIRECT_URI}
ARG REDIS_HOST
ENV REDIS_HOST=${REDIS_HOST}
ARG REDIS_PASSWORD
ENV REDIS_PASSWORD=${REDIS_PASSWORD}
ARG REDIS_PORT
ENV REDIS_PORT=${REDIS_PORT}
ENTRYPOINT ["java","-jar","/home/server.jar"]
14 changes: 13 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'

// Security
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
Expand All @@ -41,6 +41,18 @@ dependencies {

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

//Webflux
implementation 'org.springframework.boot:spring-boot-starter-webflux'

//thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

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

implementation 'com.google.code.gson:gson:2.8.8'
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64'
}

tasks.named('test') {
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/inandout/backend/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package inandout.backend.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories // reids 사용한다고 명시
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String redisHost;

@Value("${spring.data.redis.port}")
private int redisPort;

@Value("${spring.data.redis.password}")
private String redisPassword;

// 연결 생성
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(redisHost);
redisStandaloneConfiguration.setPort(redisPort);
redisStandaloneConfiguration.setPassword(redisPassword);
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
return lettuceConnectionFactory;
}

// 데이터 저장/조회
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}

// @Bean
// public StringRedisTemplate stringRedisTemplate() {
// final StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
// stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
// stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
// stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
// return stringRedisTemplate;
// }

}
80 changes: 43 additions & 37 deletions src/main/java/inandout/backend/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,47 @@

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.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {

return new BCryptPasswordEncoder();
}


@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

// csrf disable
http.csrf((auth) -> auth.disable());

//From 로그인 방식 disable
http.formLogin((auth) -> auth.disable());

//http basic 인증 방식 disable
http.httpBasic((auth) -> auth.disable());

//경로별 인가 작업
http.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll() // 모든 권한 허용
.requestMatchers("/admin").hasRole("ADMIN") // "ADMIN"이라는 권한을 가진 사용자만 접근 가능
.anyRequest().authenticated()); // 로그인 한 사용자만 접근 가능

//세션 설정
http.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 세션을 STATELESS 상태로 설정


return http.build();
}
}

//@Configuration
//@EnableWebSecurity
//public class SecurityConfig {
//
// @Bean
// public BCryptPasswordEncoder bCryptPasswordEncoder() {
//
// return new BCryptPasswordEncoder();
// }
//
//
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//
// // csrf disable
// http.csrf((auth) -> auth.disable());
//
// //From 로그인 방식 disable
// http.formLogin((auth) -> auth.disable());
//
// //http basic 인증 방식 disable
// http.httpBasic((auth) -> auth.disable());
//
// //경로별 인가 작업
// http.authorizeHttpRequests((auth) -> auth
// .requestMatchers("/login", "/", "/join").permitAll() // 모든 권한 허용
// .requestMatchers("/admin").hasRole("ADMIN") // "ADMIN"이라는 권한을 가진 사용자만 접근 가능
// .anyRequest().authenticated()); // 로그인 한 사용자만 접근 가능
//
// //세션 설정
// http.sessionManagement((session) -> session
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 세션을 STATELESS 상태로 설정
//
//
// return http.build();
// }
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package inandout.backend.controller.login;


import inandout.backend.dto.login.KakoLoginResponseDTO;
import inandout.backend.dto.login.LoginDTO;
import inandout.backend.entity.auth.Platform;
import inandout.backend.entity.member.Member;
import inandout.backend.service.login.KakaoLoginService;
import inandout.backend.service.login.RedisService;
import inandout.backend.service.login.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.HashMap;
import java.util.Optional;


@RestController
@RequestMapping("/kakaologin")
public class KakaoLoginController {

@Autowired
public KakaoLoginService kakaoLoginService;
@Autowired
public RedisService redisService;
@Autowired
public UserService userService;


@GetMapping("")
public void KakaoLogin() {
System.out.println("KakaoLoginController/KakaoLogin");
// 버튼 눌렀을때 로그인창 뜨게하는거 백에서 구현할거면 여기 사용
}

@GetMapping("/callback")
public ResponseEntity KakaoLoginCallBack(@RequestParam(value = "code") String code) throws IOException {
System.out.println("KakaoLoginController/KakaoLoginCallBack");
KakoLoginResponseDTO kakoLoginResponseDTO = null;

// 엑세스/리프레쉬 토큰 받기
HashMap<String, String> kakaoToken = kakaoLoginService.getAccessToken(code);

// 유저 정보 받기
HashMap<String, Object> kakaoUserInfo = kakaoLoginService.getUserInfo(kakaoToken.get("accessToken"));
String accessToken = kakaoToken.get("accessToken");
String refreshToken = kakaoToken.get("refreshToken");
String email = (String) kakaoUserInfo.get("email");

// email로 회원 찾기
Optional<Member> member = userService.findUser(email);


if (member.isPresent()) { //회원 -> 로그인처리

//redis에서 refreshToken 칮기
String prevRefreshToken = redisService.getRefreshToken(email);
kakoLoginResponseDTO = new KakoLoginResponseDTO(accessToken, prevRefreshToken,member.get().getName());


}else{ //비회원 ->가입
kakoLoginResponseDTO = new KakoLoginResponseDTO(accessToken, refreshToken, "홍길동");

LoginDTO loginDTO = new LoginDTO();
loginDTO.setName("홍길동"); // 닉네임 랜덤으로 부여
loginDTO.setEmail(email);
loginDTO.setPassword("");
loginDTO.setPlatform(Platform.KAKAO);
loginDTO.setPlatformId("1");

userService.save(loginDTO);

//redis에 refreshToken 저장
redisService.setValues(email, refreshToken);

}

// //accessToken 만료되었는지 검사
// boolean isTokenValid = kakaoLoginService.isValidToken("KMXxzLPp_GjjTaMW1-3Z8t2GmCRxTqV9AAAAAQopyV8AAAGQplhQWxKZRqbpl2cW");
// System.out.println("accessToken 유효한지: "+isTokenValid);

return ResponseEntity.ok().body(kakoLoginResponseDTO);
}
}
21 changes: 21 additions & 0 deletions src/main/java/inandout/backend/dto/login/KakoLoginResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package inandout.backend.dto.login;


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KakoLoginResponseDTO {

private String accessToken;
private String refreshToken;
private String name;



}
30 changes: 30 additions & 0 deletions src/main/java/inandout/backend/dto/login/LoginDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package inandout.backend.dto.login;

import inandout.backend.entity.auth.Platform;
import inandout.backend.entity.member.MemberStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginDTO {

private String name;
private String email;
private String password;
private Platform platform;
private String platformId;
// private LocalDateTime createdAt;
// private LocalDateTime updatedAt;
// private MemberStatus status;
// private boolean isPublic;



}
19 changes: 19 additions & 0 deletions src/main/java/inandout/backend/entity/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,23 @@ public class Member {

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)// 다대다(다대일, 일대다) 단방향 연관 관계 / 연관 관계 주인의 반대편
private List<Post> posts = new ArrayList<>();

public Member(String name, String email, String memberImgUrl, String password, Platform platform, String platformId, LocalDateTime createdAt, LocalDateTime updatedAt, MemberStatus status, boolean isPublic) {

this.name = name;
this.email = email;
this.memberImgUrl = memberImgUrl;
this.password = password;
this.platform = platform;
this.platformId = platformId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.status = status;
this.isPublic = isPublic;
}

public String getEmail() {
return email;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package inandout.backend.repository.login;

import inandout.backend.entity.member.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
Loading

0 comments on commit 17b5b0f

Please sign in to comment.