Skip to content

Commit

Permalink
카카오 회원가입/로그인, /api/auth/member API 기능 추가 (#6)
Browse files Browse the repository at this point in the history
* ✨ 카카오 로그인/회원가입 구현 (#3)

* Style: codeStyle 추가

* Feat: Member, Profile Entity, MemberRepository 추가

+ MemberRepositoryTest 추가

Related to: #1

* Feat: 카카오 로그인, 회원가입 기능 추가

- 권한 필요한 API 접속 시 카카오 로그인 페이지 반환
  - 로그인 성공 시 가입이 안되어있으면 가입, 되어있으면 권한 부여
  - 권한은 JWT 발급 후 쿠키에 추가, 매 요청마다 JwtFilter를 거치며 쿠키의 Jwt 확

Related to: #1

* Style: 코드 포맷, 비밀번호 노출 등 수정

Related to: #1

* ✨ /api/auth/member API 기능 추가 (#4)

* Feat: Member DB 수정

- 한글이름, 영어이름, 기수, 과정 추가
- 과정 표현하는 Enumerate 추가
- 수정에 따른 오류 수정
  - getNickname -> getKoreaName of getEnglishName

Related to: #2

* Rename: Member DB Field 이름 변경

Related to: #2

* Feat: /api/auth/member 관련 DTO 작성

Related to: #2

* Feat: /api/auth/member API 관련 DTO 내용 추가

Related to: #2

* Feat: /api/auth/member 관련 MemberService 추가

Related to: #2

* Feat: /api/auth/member 관련 MemberContoller 추가

Related to: #2

* Feat: CORS Error 수정

Related to: #2

* Fix: RefreshToken 매번 생성 오류 수정

Related to: #2

* Refactor: 코드 리뷰 내용 반영

- 불필요 코드 삭제
- 반환값 오류 수정

Related to: #2

* Chore: DB를 MariaDB -> H2로 변경

Ralted to: #5

* Test: Test용 application 추가

Related to: #5
  • Loading branch information
Taejin1221 authored Sep 3, 2024
1 parent 57b7065 commit 34f06b0
Show file tree
Hide file tree
Showing 35 changed files with 1,104 additions and 23 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ fabric.properties

.idea/*

!.idea/codeStyles
!.idea/runConfigurations

### Java ###
Expand Down Expand Up @@ -175,4 +174,7 @@ gradle-app.setting
# Java heap dump
*.hprof

# End of https://www.toptal.com/developers/gitignore/api/java,gradle,macos,intellij+all
# End of https://www.toptal.com/developers/gitignore/api/java,gradle,macos,intellij+allauth.yml
oauth.yml
database.yml
jwt.yml
5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 27 additions & 20 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
}

group = 'kaboo'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// JWT Token Dependency
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
14 changes: 14 additions & 0 deletions src/main/java/kaboo/kaboo_auth/config/PasswordEncoderConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kaboo.kaboo_auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
63 changes: 63 additions & 0 deletions src/main/java/kaboo/kaboo_auth/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package kaboo.kaboo_auth.config;

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.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import kaboo.kaboo_auth.domain.handler.LoginSuccessHandler;
import kaboo.kaboo_auth.domain.jwt.filter.JwtFilter;
import kaboo.kaboo_auth.service.CustomOAuth2Service;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtFilter jwtFilter;
private final CustomOAuth2Service customOAuth2Service;
private final LoginSuccessHandler loginSuccessHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/").permitAll()
.anyRequest().authenticated()) // 그 외 요청은 인증 필요
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));

http
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

http.oauth2Login(auth -> auth
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2Service))
.successHandler(loginSuccessHandler));

return http.build();
}

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*"); // 모든 도메인 허용. 필요에 따라 변경
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
24 changes: 24 additions & 0 deletions src/main/java/kaboo/kaboo_auth/controller/MainController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kaboo.kaboo_auth.controller;

import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {

@GetMapping("/")
public String mainAPI() {
return "누구나 접근가능한 API입니다.";
}

@GetMapping("/test")
public String authAPI() {
return "권한 Test API 입니다.";
}

@GetMapping("/auth/hello")
public String helloAuth(Authentication authentication) {
return "인가 받은 사용자 " + authentication.getName() + " 님 환영합니다.";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kaboo.kaboo_auth.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import kaboo.kaboo_auth.domain.dto.response.ResponseDTO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class MainControllerAdvice {

@ExceptionHandler({IllegalStateException.class, UsernameNotFoundException.class})
public ResponseEntity<ResponseDTO<?>> exceptionHandler(Exception e) {
log.error("[Kaboo-Auth]: 예외가 발생하였습니다. {}", e.getMessage());
return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
true,
e.getMessage(),
null
));
}
}
86 changes: 86 additions & 0 deletions src/main/java/kaboo/kaboo_auth/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package kaboo.kaboo_auth.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kaboo.kaboo_auth.domain.dto.request.MemberInfoUpdateRequest;
import kaboo.kaboo_auth.domain.dto.response.MemberInfoResponse;
import kaboo.kaboo_auth.domain.dto.response.MemberListResponse;
import kaboo.kaboo_auth.domain.dto.response.ResponseDTO;
import kaboo.kaboo_auth.service.MemberService;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth/member")
public class MemberController {

private final MemberService memberService;

@GetMapping("/all")
public ResponseEntity<ResponseDTO<MemberListResponse>> getAllMembers() {
return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.getAllMembers()
));
}

@GetMapping("/class/{class}")
public ResponseEntity<ResponseDTO<MemberListResponse>> getClassMembers(
@PathVariable(name = "class") int classNum) {

return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.getMembersByClassNum(classNum)
));
}

@GetMapping
public ResponseEntity<ResponseDTO<MemberInfoResponse>> getMemberInfo(
@RequestParam(name = "name", defaultValue = "") String koreaName) {

return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.getMemberInfoByKoreaName(koreaName)
));
}

@PostMapping
public ResponseEntity<ResponseDTO<MemberInfoResponse>> updateMemberInfo(
@RequestParam(name = "name", defaultValue = "") String koreaName,
@RequestBody MemberInfoUpdateRequest request) {

return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.updateMemberInfoByKoreaName(koreaName, request)
));
}

@GetMapping("/introduce")
public ResponseEntity<ResponseDTO<String>> getMemberIntroduce(
@RequestParam(name = "name", defaultValue = "") String koreaName) {

return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.getMemberIntroduceByKoreaName(koreaName)
));
}

@PostMapping("/introduce")
public ResponseEntity<ResponseDTO<String>> updateMemberIntrouce(
@RequestParam(name = "name", defaultValue = "") String koreaName,
@RequestBody String request) {

return ResponseEntity.status(HttpStatus.OK)
.body(new ResponseDTO<>(
memberService.updateMemberIntroduceByKoreaName(koreaName, request)
));
}
}
13 changes: 13 additions & 0 deletions src/main/java/kaboo/kaboo_auth/domain/Course.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kaboo.kaboo_auth.domain;

public enum Course {
AI("GenAI"),
FULLSTACK("Fullstack"),
CLOUD("Cloud");

private final String course;

Course(String course) {
this.course = course;
}
}
45 changes: 45 additions & 0 deletions src/main/java/kaboo/kaboo_auth/domain/CustomUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kaboo.kaboo_auth.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;

import kaboo.kaboo_auth.domain.entity.Member;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class CustomUserDetails implements OAuth2User, UserDetails {
private final Member member;

@Override
public String getUsername() {
return member.getUsername();
}

@Override
public String getName() {
return member.getKoreaName();
}

@Override
public String getPassword() {
return member.getPassword();
}

@Override
public Map<String, Object> getAttributes() {
return null;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add((GrantedAuthority)() -> member.getRole().toString());

return collection;
}
}
15 changes: 15 additions & 0 deletions src/main/java/kaboo/kaboo_auth/domain/UserRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kaboo.kaboo_auth.domain;

import lombok.Getter;

@Getter
public enum UserRole {
ROLE_ADMIN("ROLE_ADMIN"),
ROLE_USER("ROLE_USER");

private final String role;

UserRole(String role) {
this.role = role;
}
}
Loading

0 comments on commit 34f06b0

Please sign in to comment.