diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/2022-Answer-SolutionChallenge.iml b/.idea/2022-Answer-SolutionChallenge.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/2022-Answer-SolutionChallenge.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
new file mode 100644
index 0000000..4c97bdf
--- /dev/null
+++ b/.idea/dbnavigator.xml
@@ -0,0 +1,461 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5821db8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring/notinote/build.gradle b/spring/notinote/build.gradle
index 558d040..3c25ed3 100644
--- a/spring/notinote/build.gradle
+++ b/spring/notinote/build.gradle
@@ -23,9 +23,17 @@ repositories {
}
dependencies {
+ implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- //implementation 'org.springframework.boot:spring-boot-starter-security'
+ implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
+ implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
+ implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2'
+ runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
+ runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
+ developmentOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
diff --git a/spring/notinote/src/main/java/com/answer/notinote/Config/properties/AppProperties.java b/spring/notinote/src/main/java/com/answer/notinote/Config/properties/AppProperties.java
new file mode 100644
index 0000000..1116527
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/Config/properties/AppProperties.java
@@ -0,0 +1,57 @@
+package com.answer.notinote.Config.properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ConfigurationProperties(prefix = "app")
+public class AppProperties {
+
+ private final Auth auth = new Auth();
+
+ private final OAuth2 oauth2 = new OAuth2();
+
+ public static class Auth {
+
+ private String tokenSecret;
+
+ private long tokenExpirationMsec;
+
+ public String getTokenSecret() {
+ return tokenSecret;
+ }
+
+ public void setTokenSecret(String tokenSecret) {
+ this.tokenSecret = tokenSecret;
+ }
+
+ public long getTokenExpirationMsec() {
+ return tokenExpirationMsec;
+ }
+
+ public void setTokenExpirationMsec(long tokenExpirationMsec) {
+ this.tokenExpirationMsec = tokenExpirationMsec;
+ }
+ }
+
+ public static final class OAuth2 {
+
+ private List authorizedRedirectUris = new ArrayList<>();
+
+ public List getAuthorizedRedirectUris() {
+ return authorizedRedirectUris;
+ }
+ public OAuth2 authorizedRedirectUris(List authorizedRedirectUris) {
+ this.authorizedRedirectUris = authorizedRedirectUris;
+ return this;
+ }
+ }
+
+ public Auth getAuth() {
+ return auth;
+ }
+ public OAuth2 getOauth2() {
+ return oauth2;
+ }
+}
\ No newline at end of file
diff --git a/spring/notinote/src/main/java/com/answer/notinote/Config/properties/CorsProperties.java b/spring/notinote/src/main/java/com/answer/notinote/Config/properties/CorsProperties.java
new file mode 100644
index 0000000..864ac76
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/Config/properties/CorsProperties.java
@@ -0,0 +1,14 @@
+package com.answer.notinote.Config.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter @Setter
+@ConfigurationProperties(prefix = "cors")
+public class CorsProperties {
+ private String allowedOrigins;
+ private String allowedMethods;
+ private String allowedHeaders;
+ private Long maxAge;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/Config/security/JwtConfig.java b/spring/notinote/src/main/java/com/answer/notinote/Config/security/JwtConfig.java
new file mode 100644
index 0000000..5f2e030
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/Config/security/JwtConfig.java
@@ -0,0 +1,13 @@
+package com.answer.notinote.Config.security;
+
+import com.answer.notinote.auth.token.JwtTokenProvider;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JwtConfig {
+
+ @Value("${jwt.secret}")
+ private String secret;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java b/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java
new file mode 100644
index 0000000..5b2e209
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java
@@ -0,0 +1,59 @@
+package com.answer.notinote.Config.security;
+
+import com.answer.notinote.auth.data.RoleType;
+import com.answer.notinote.auth.filter.JwtAuthenticationFilter;
+import com.answer.notinote.auth.filter.OAuth2AccessTokenAuthenticationFilter;
+import com.answer.notinote.auth.handler.OAuth2LoginFailureHandler;
+import com.answer.notinote.auth.handler.OAuth2LoginSuccessHandler;
+import com.answer.notinote.auth.token.JwtTokenProvider;
+import lombok.RequiredArgsConstructor;
+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.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * Spring Security 설정 클래스
+ */
+@Configuration
+@RequiredArgsConstructor
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private final JwtTokenProvider jwtTokenProvider;
+ private final OAuth2AccessTokenAuthenticationFilter oAuth2AccessTokenAuthenticationFilter;
+ private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
+ private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .httpBasic().disable()
+ .csrf().disable()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeRequests()
+ .antMatchers("/", "/login/*", "/join", "/join/*").permitAll()
+ .and()
+ .authorizeRequests()
+ .antMatchers("/test/user")
+ .hasRole("USER")
+ .and()
+ .authorizeRequests()
+ .antMatchers("/test/admin")
+ .hasRole("ADMIN")
+ .and()
+ .authorizeRequests()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .oauth2Login()
+ .successHandler(oAuth2LoginSuccessHandler)
+ .failureHandler(oAuth2LoginFailureHandler)
+ .and()
+ .addFilterBefore(oAuth2AccessTokenAuthenticationFilter,
+ UsernamePasswordAuthenticationFilter.class);
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/NotinoteApplication.java b/spring/notinote/src/main/java/com/answer/notinote/NotinoteApplication.java
index ffbfd66..6724fb0 100644
--- a/spring/notinote/src/main/java/com/answer/notinote/NotinoteApplication.java
+++ b/spring/notinote/src/main/java/com/answer/notinote/NotinoteApplication.java
@@ -1,9 +1,14 @@
package com.answer.notinote;
+import com.answer.notinote.Config.properties.AppProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+@EnableJpaAuditing
@SpringBootApplication
+@EnableConfigurationProperties(AppProperties.class)
public class NotinoteApplication {
public static void main(String[] args) {
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java b/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java
new file mode 100644
index 0000000..9783838
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java
@@ -0,0 +1,78 @@
+package com.answer.notinote.User.controller;
+
+import com.answer.notinote.User.dto.JoinRequestDto;
+import com.answer.notinote.auth.token.JwtTokenProvider;
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.User.dto.UserRequestDto;
+import com.answer.notinote.User.service.UserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("")
+public class UserController {
+
+ private final UserService userService;
+
+ private final JwtTokenProvider jwtTokenProvider;
+
+ @GetMapping("/join/{id}")
+ public ResponseEntity> auth_success(@PathVariable("id") long id) {
+ System.out.println("/join/id 입니다.");
+ User user = userService.findUserById(id);
+ return ResponseEntity.ok(user);
+ }
+
+ // 회원가입
+ @PostMapping("/join")
+ public ResponseEntity> join(@RequestBody JoinRequestDto requestDto) {
+ return ResponseEntity.ok(userService.join(requestDto));
+ }
+
+ // 로그인
+ @GetMapping("/login/{id}")
+ public ResponseEntity> login(@PathVariable("id") long id) {
+ User user = userService.findUserById(id);
+
+ String token = jwtTokenProvider.createToken(user.getUemail(), user.getUroleType());
+ return ResponseEntity.ok(token);
+ }
+
+ // token 재발급
+ @PostMapping("/refresh")
+ public String validateRefreshToken(@RequestHeader("REFRESH-TOKEN") String refreshToken) {
+ return "";
+ }
+
+ // 회원정보 수정
+ @PatchMapping()
+ public User update(@RequestParam Long id, @RequestBody UserRequestDto requestDto) {
+ return userService.update(id, requestDto);
+ }
+
+ // 이메일로 회원 조회
+ @GetMapping("/user/email")
+ public User readByEmail(@RequestParam String email) {
+ return userService.findUserByEmail(email);
+ }
+
+ // 전체 회원 조회
+ @GetMapping("/user/list")
+ public List readAll() {
+ return userService.findAllUsers();
+ }
+
+ // 회원 삭제
+ @DeleteMapping("/user")
+ public Long delete(@RequestParam Long id) {
+ return userService.delete(id);
+ }
+
+ //Todo: Logout
+
+ //Todo: find password
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/Timestamped.java b/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/Timestamped.java
new file mode 100644
index 0000000..558962b
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/Timestamped.java
@@ -0,0 +1,23 @@
+package com.answer.notinote.User.domain.entity;
+
+import lombok.Getter;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+
+@Getter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+abstract class Timestamped {
+
+ @CreatedDate
+ private LocalDateTime created_at;
+
+ @LastModifiedDate
+ private LocalDateTime modified_at;
+
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/User.java b/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/User.java
new file mode 100644
index 0000000..a151083
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/domain/entity/User.java
@@ -0,0 +1,65 @@
+package com.answer.notinote.User.domain.entity;
+
+import com.answer.notinote.auth.data.ProviderType;
+import com.answer.notinote.auth.data.RoleType;
+import com.answer.notinote.User.dto.UserRequestDto;
+import lombok.*;
+
+import javax.persistence.*;
+
+@Entity
+@Getter @Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class User extends Timestamped {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column
+ private Long uid;
+
+ @Column(length = 20)
+ private String ufirstname;
+
+ @Column(length = 20)
+ private String ulastname;
+
+ @Column(nullable = false, length = 50, unique = true)
+ private String uemail;
+
+ @Column(length = 20)
+ private String ulanguage;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 20)
+ private ProviderType uproviderType;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false, length = 20)
+ private RoleType uroleType;
+
+ public User(UserRequestDto requestDto) {
+ this.ufirstname = requestDto.getFirstname();
+ this.ulastname = requestDto.getLastname();
+ this.uemail = requestDto.getEmail();
+ }
+
+ public User(com.answer.notinote.auth.data.dto.UserRequestDto requestDto) {
+ this.uemail = requestDto.getEmail();
+ this.ufirstname = requestDto.getFirstname();
+ this.ulastname = requestDto.getLastname();
+ this.uproviderType = requestDto.getProviderType();
+ this.uroleType = requestDto.getRoleType();
+ }
+
+ public String getFullname() {
+ return this.ufirstname + " " + this.ulastname;
+ }
+
+ public void update(UserRequestDto requestDto) {
+ this.ufirstname = requestDto.getFirstname();
+ this.ulastname = requestDto.getLastname();
+ this.uemail = requestDto.getEmail();
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/domain/repository/UserRepository.java b/spring/notinote/src/main/java/com/answer/notinote/User/domain/repository/UserRepository.java
new file mode 100644
index 0000000..d6e68c4
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/domain/repository/UserRepository.java
@@ -0,0 +1,17 @@
+package com.answer.notinote.User.domain.repository;
+
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.auth.data.ProviderType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import javax.swing.text.html.Option;
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+ Optional findByUemail(String uemail);
+
+ Optional findByUproviderTypeAndUemail(ProviderType uproviderType, String uemail);
+ boolean existsByUemail(String email);
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/dto/JoinRequestDto.java b/spring/notinote/src/main/java/com/answer/notinote/User/dto/JoinRequestDto.java
new file mode 100644
index 0000000..f63cb56
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/dto/JoinRequestDto.java
@@ -0,0 +1,10 @@
+package com.answer.notinote.User.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter @Setter
+public class JoinRequestDto {
+ Long id;
+ String language;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/dto/LoginResponseDto.java b/spring/notinote/src/main/java/com/answer/notinote/User/dto/LoginResponseDto.java
new file mode 100644
index 0000000..9233e7f
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/dto/LoginResponseDto.java
@@ -0,0 +1,19 @@
+package com.answer.notinote.User.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Getter @Setter
+public class LoginResponseDto {
+ private Long id;
+ private String firstname;
+ private String lastname;
+ private String email;
+ private String access_token;
+ private String refresh_token;
+ private List roles;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/dto/TokenResponseDto.java b/spring/notinote/src/main/java/com/answer/notinote/User/dto/TokenResponseDto.java
new file mode 100644
index 0000000..716ee17
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/dto/TokenResponseDto.java
@@ -0,0 +1,12 @@
+package com.answer.notinote.User.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter @Setter
+public class TokenResponseDto {
+ private String access_token;
+ private String refresh_token;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/dto/UserRequestDto.java b/spring/notinote/src/main/java/com/answer/notinote/User/dto/UserRequestDto.java
new file mode 100644
index 0000000..fc1b109
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/dto/UserRequestDto.java
@@ -0,0 +1,11 @@
+package com.answer.notinote.User.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter @Setter
+public class UserRequestDto {
+ private String email;
+ private String firstname;
+ private String lastname;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java b/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java
new file mode 100644
index 0000000..1e58276
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java
@@ -0,0 +1,70 @@
+package com.answer.notinote.User.service;
+
+import com.answer.notinote.User.dto.JoinRequestDto;
+import com.answer.notinote.auth.data.RoleType;
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.User.domain.repository.UserRepository;
+import com.answer.notinote.User.dto.UserRequestDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class UserService {
+
+ private final UserRepository userRepository;
+
+ @Transactional
+ public User join(JoinRequestDto requestDto) {
+ User user = userRepository.findById(requestDto.getId()).orElseThrow(
+ () -> new IllegalArgumentException("id가 존재하지 않습니다.")
+ );
+
+ if (user.getUroleType() == RoleType.GUEST) {
+ user.setUlanguage(requestDto.getLanguage());
+ user.setUroleType(RoleType.USER);
+ userRepository.save(user);
+ return user;
+ }
+ else {
+ throw new IllegalArgumentException("구글 회원가입 전적이 존재하지 않습니다.");
+ }
+ }
+
+ public User update(Long id, UserRequestDto requestDto) {
+ User user = userRepository.findById(id).orElseThrow(
+ () -> new IllegalArgumentException("id가 존재하지 않습니다.")
+ );
+ user.update(requestDto);
+
+ return user;
+ }
+
+ public Long delete(Long id) {
+ User user = userRepository.findById(id).orElseThrow(
+ () -> new IllegalArgumentException("id가 존재하지 않습니다.")
+ );
+ userRepository.delete(user);
+
+ return id;
+ }
+
+ public User findUserById(Long id) {
+ return userRepository.findById(id).orElseThrow(
+ () -> new IllegalArgumentException("ID가 존재하지 않습니다.")
+ );
+ }
+
+ public User findUserByEmail(String email) {
+ return userRepository.findByUemail(email).orElseThrow(
+ () -> new IllegalArgumentException("이메일이 존재하지 않습니다.")
+ );
+ }
+
+ public List findAllUsers() {
+ return userRepository.findAll();
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/util/ErrorMessage.java b/spring/notinote/src/main/java/com/answer/notinote/User/util/ErrorMessage.java
new file mode 100644
index 0000000..67b1338
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/util/ErrorMessage.java
@@ -0,0 +1,12 @@
+package com.answer.notinote.User.util;
+
+import lombok.AllArgsConstructor;
+import java.util.Date;
+
+@AllArgsConstructor
+public class ErrorMessage {
+ private int status;
+ private Date date;
+ private String message;
+ private String request;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/util/advice/TokenControllerAdvice.java b/spring/notinote/src/main/java/com/answer/notinote/User/util/advice/TokenControllerAdvice.java
new file mode 100644
index 0000000..357c9b3
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/util/advice/TokenControllerAdvice.java
@@ -0,0 +1,25 @@
+package com.answer.notinote.User.util.advice;
+
+import com.answer.notinote.User.util.ErrorMessage;
+import com.answer.notinote.User.util.exception.TokenRefreshException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+
+import java.util.Date;
+
+@RestControllerAdvice
+public class TokenControllerAdvice {
+ @ExceptionHandler(value = TokenRefreshException.class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ public ErrorMessage handleTokenRefreshException(TokenRefreshException e, WebRequest request) {
+ return new ErrorMessage(
+ HttpStatus.FORBIDDEN.value(),
+ new Date(),
+ e.getMessage(),
+ request.getDescription(false)
+ );
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/util/exception/TokenRefreshException.java b/spring/notinote/src/main/java/com/answer/notinote/User/util/exception/TokenRefreshException.java
new file mode 100644
index 0000000..f4b0f4f
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/User/util/exception/TokenRefreshException.java
@@ -0,0 +1,13 @@
+package com.answer.notinote.User.util.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.FORBIDDEN)
+public class TokenRefreshException extends RuntimeException{
+ private static final long version = 1L;
+
+ public TokenRefreshException(String token, String message) {
+ super(String.format("Failed for [%s]: %s", token, message));
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/data/ProviderType.java b/spring/notinote/src/main/java/com/answer/notinote/auth/data/ProviderType.java
new file mode 100644
index 0000000..94efe36
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/data/ProviderType.java
@@ -0,0 +1,22 @@
+package com.answer.notinote.auth.data;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpMethod;
+
+/**
+ * 제공하는 SNS 로그인 enum 클래스
+ */
+@Getter
+@AllArgsConstructor
+public enum ProviderType {
+ GOOGLE(
+ "google",
+ "https://www.googleapis.com/oauth2/v3/userinfo",
+ HttpMethod.GET
+ );
+
+ private String socialName;
+ private String userInfoUrl;
+ private HttpMethod method;
+}
\ No newline at end of file
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/data/RoleType.java b/spring/notinote/src/main/java/com/answer/notinote/auth/data/RoleType.java
new file mode 100644
index 0000000..35a874d
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/data/RoleType.java
@@ -0,0 +1,17 @@
+package com.answer.notinote.auth.data;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 유저 권한 enum 클래스
+ */
+@Getter
+@AllArgsConstructor
+public enum RoleType {
+ USER("ROLE_USER"),
+ GUEST("ROLE_GUEST"),
+ ADMIN("ROLE_ADMIN");
+
+ private String grantedAuthority;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserRequestDto.java b/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserRequestDto.java
new file mode 100644
index 0000000..8bff008
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserRequestDto.java
@@ -0,0 +1,18 @@
+package com.answer.notinote.auth.data.dto;
+
+import com.answer.notinote.auth.data.ProviderType;
+import com.answer.notinote.auth.data.RoleType;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class UserRequestDto {
+ private String email;
+ private String firstname;
+ private String lastname;
+ private ProviderType providerType;
+ private RoleType roleType;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserSocialResponseDto.java b/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserSocialResponseDto.java
new file mode 100644
index 0000000..fd5555a
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/data/dto/UserSocialResponseDto.java
@@ -0,0 +1,13 @@
+package com.answer.notinote.auth.data.dto;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter @Setter
+@Builder
+public class UserSocialResponseDto {
+ String email;
+ String firstname;
+ String lastname;
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/filter/JwtAuthenticationFilter.java b/spring/notinote/src/main/java/com/answer/notinote/auth/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..a681d07
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,30 @@
+package com.answer.notinote.auth.filter;
+
+import com.answer.notinote.auth.token.JwtTokenProvider;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+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;
+
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends GenericFilterBean {
+
+ private final JwtTokenProvider jwtTokenProvider;
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
+ if (token != null && jwtTokenProvider.validateToekn(token)) {
+ Authentication authentication = jwtTokenProvider.getAuthentication(token);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/filter/OAuth2AccessTokenAuthenticationFilter.java b/spring/notinote/src/main/java/com/answer/notinote/auth/filter/OAuth2AccessTokenAuthenticationFilter.java
new file mode 100644
index 0000000..de37c4d
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/filter/OAuth2AccessTokenAuthenticationFilter.java
@@ -0,0 +1,70 @@
+package com.answer.notinote.auth.filter;
+
+import com.answer.notinote.auth.token.AccessTokenAuthenticationProvider;
+import com.answer.notinote.auth.data.ProviderType;
+import com.answer.notinote.auth.token.AccessTokenProviderTypeToken;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+
+/**
+ * 로그인 요청 헤더의 Access Token을 식별하는 헤더
+ */
+@Component
+public class OAuth2AccessTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+
+ private static final String DEFAULT_OAUTH2_LOGIN_REQUEST_URL_PREFIX = "/login/oauth2";
+ private static final String HTTP_METHOD = "GET";
+ private static final String ACCESS_TOKEN_HEADER_NAME = "Authorization"; //AccessToken 해더
+
+ private static final AntPathRequestMatcher DEFAULT_OAUTH2_LOGIN_PATH_REQUEST_MATCHER =
+ new AntPathRequestMatcher(DEFAULT_OAUTH2_LOGIN_REQUEST_URL_PREFIX +"*", HTTP_METHOD); //=> /oauth2/login/* 의 GET 매핑
+
+ public OAuth2AccessTokenAuthenticationFilter(AccessTokenAuthenticationProvider accessTokenAuthenticationProvider,
+ AuthenticationSuccessHandler authenticationSuccessHandler,
+ AuthenticationFailureHandler authenticationFailureHandler) {
+ super(DEFAULT_OAUTH2_LOGIN_PATH_REQUEST_MATCHER);
+
+ this.setAuthenticationManager(new ProviderManager(accessTokenAuthenticationProvider));
+ this.setAuthenticationSuccessHandler(authenticationSuccessHandler);
+ this.setAuthenticationFailureHandler(authenticationFailureHandler);
+
+ }
+
+ /**
+ * 로그인 요청이 들어오면 가장 먼저 작동되는 메소드입니다.
+ * AuthenticationManager.authenticate()를 호출해 인증을 진행합니다.
+ * @param request
+ * @param response
+ * @return
+ * @throws AuthenticationException
+ */
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+ // SNS 로그인 분류 추출
+ //ProviderType providerType = extractProviderType(request);
+ String accessToken = request.getHeader(ACCESS_TOKEN_HEADER_NAME);
+
+ //AuthenticationManager에 인증 요청 전송
+ return this.getAuthenticationManager().authenticate(new AccessTokenProviderTypeToken(accessToken, ProviderType.GOOGLE));
+ }
+
+
+ private ProviderType extractProviderType(HttpServletRequest request) {
+ return Arrays.stream(ProviderType.values())
+ .filter(providerType ->
+ providerType.getSocialName()
+ .equals(request.getRequestURI().substring(DEFAULT_OAUTH2_LOGIN_REQUEST_URL_PREFIX.length())))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("잘못된 URL 주소입니다"));
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginFailureHandler.java b/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginFailureHandler.java
new file mode 100644
index 0000000..098296d
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginFailureHandler.java
@@ -0,0 +1,22 @@
+package com.answer.notinote.auth.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+ System.out.println("로그인 실패! : " + exception.getMessage());
+ response.sendRedirect("/");
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginSuccessHandler.java b/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginSuccessHandler.java
new file mode 100644
index 0000000..173a1e2
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/handler/OAuth2LoginSuccessHandler.java
@@ -0,0 +1,43 @@
+package com.answer.notinote.auth.handler;
+
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.User.domain.repository.UserRepository;
+import com.answer.notinote.auth.data.RoleType;
+import com.answer.notinote.auth.userdetails.CustomUserDetails;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+ private final UserRepository userRepository;
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+ System.out.println("로그인 성공 !");
+
+ CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
+ User user = userRepository.findByUemail(userDetails.getEmail()).orElseThrow(
+ () -> new IllegalArgumentException("이메일이 존재하지 않습니다.")
+ );
+
+ if (authentication.getAuthorities().stream().anyMatch(s -> s.getAuthority().equals(RoleType.GUEST.getGrantedAuthority()))) {
+ System.out.println("회원가입으로 이동합니다.");
+ response.sendRedirect("/join/" + user.getUid());
+ return;
+ }
+ else {
+ System.out.println("회원가입한 사용자입니다.");
+ response.sendRedirect("/login/" + user.getUid());
+ }
+
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/repository/RefreshTokenRepository.java b/spring/notinote/src/main/java/com/answer/notinote/auth/repository/RefreshTokenRepository.java
new file mode 100644
index 0000000..abf669e
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/repository/RefreshTokenRepository.java
@@ -0,0 +1,17 @@
+package com.answer.notinote.auth.repository;
+
+import com.answer.notinote.auth.token.RefreshToken;
+import com.answer.notinote.User.domain.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface RefreshTokenRepository extends JpaRepository{
+ @Override
+ Optional findById(Long id);
+ Optional findByToken(String token);
+ Optional findByUserEmailAndToken(String userId, String token);
+ void deleteByUserEmail(String userEmail);
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/service/CustomUserDetailsService.java b/spring/notinote/src/main/java/com/answer/notinote/auth/service/CustomUserDetailsService.java
new file mode 100644
index 0000000..3a2d01b
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/service/CustomUserDetailsService.java
@@ -0,0 +1,24 @@
+package com.answer.notinote.auth.service;
+
+import com.answer.notinote.auth.userdetails.CustomUserDetails;
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.User.domain.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CustomUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
+ User user = userRepository.findByUemail(email)
+ .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
+ return CustomUserDetails.create(user);
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/service/LoadUserService.java b/spring/notinote/src/main/java/com/answer/notinote/auth/service/LoadUserService.java
new file mode 100644
index 0000000..4fd3b0b
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/service/LoadUserService.java
@@ -0,0 +1,52 @@
+package com.answer.notinote.auth.service;
+
+import com.answer.notinote.auth.data.dto.UserSocialResponseDto;
+import com.answer.notinote.auth.strategy.GoogleLoadStrategy;
+import com.answer.notinote.auth.strategy.ProviderLoadStrategy;
+import com.answer.notinote.auth.data.ProviderType;
+import com.answer.notinote.auth.token.AccessTokenProviderTypeToken;
+import com.answer.notinote.auth.userdetails.CustomUserDetails;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+@RequiredArgsConstructor
+public class LoadUserService {
+
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ /**
+ * 해당 ProviderType의 url에 요청을 보내 유저 정보 조회
+ * @param authentication
+ * @return customUserDetails
+ */
+ public CustomUserDetails getOAuth2UserDetails(AccessTokenProviderTypeToken authentication) {
+ ProviderType providerType = authentication.getProviderType();
+ ProviderLoadStrategy providerLoadStrategy = getProviderLoadStrategy(providerType);
+
+ UserSocialResponseDto socialEntity = providerLoadStrategy.getSocialEntity(authentication.getAccessToken());
+
+ if (socialEntity == null) {
+ throw new IllegalArgumentException("액세스 토큰이 만료되었습니다.");
+ }
+
+ return CustomUserDetails.builder()
+ .email(socialEntity.getEmail())
+ .firstname(socialEntity.getFirstname())
+ .lastname(socialEntity.getLastname())
+ .providerType(providerType)
+ .build();
+ }
+
+ /**
+ * @param providerType
+ * @return ProviderLoadStrategy
+ */
+ private ProviderLoadStrategy getProviderLoadStrategy(ProviderType providerType) {
+ switch (providerType) {
+ case GOOGLE : return new GoogleLoadStrategy();
+ default : throw new IllegalArgumentException("지원하지 않는 로그인 형식입니다");
+ }
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/service/RefreshTokenService.java b/spring/notinote/src/main/java/com/answer/notinote/auth/service/RefreshTokenService.java
new file mode 100644
index 0000000..2b69086
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/service/RefreshTokenService.java
@@ -0,0 +1,36 @@
+package com.answer.notinote.auth.service;
+
+import com.answer.notinote.auth.token.RefreshToken;
+import com.answer.notinote.User.domain.entity.User;
+import com.answer.notinote.auth.repository.RefreshTokenRepository;
+import com.answer.notinote.User.domain.repository.UserRepository;
+import com.answer.notinote.User.util.exception.TokenRefreshException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.time.Instant;
+
+@Service
+@RequiredArgsConstructor
+public class RefreshTokenService {
+
+ private final RefreshTokenRepository refreshTokenRepository;
+ private final UserRepository userRepository;
+
+ public RefreshToken findByToken(String token) {
+ return refreshTokenRepository.findByToken(token).orElseThrow(
+ () -> new IllegalArgumentException("token이 존재하지 않습니다.")
+ );
+ }
+
+ @Transactional
+ public Long deleteByUid(Long uid) {
+ User user = userRepository.findById(uid).orElseThrow(
+ () -> new IllegalArgumentException("유저 ID가 존재하지 않습니다.")
+ );
+ refreshTokenRepository.deleteByUserEmail(user.getUemail());
+ return user.getUid();
+ }
+}
diff --git a/spring/notinote/src/main/java/com/answer/notinote/auth/strategy/GoogleLoadStrategy.java b/spring/notinote/src/main/java/com/answer/notinote/auth/strategy/GoogleLoadStrategy.java
new file mode 100644
index 0000000..6371c73
--- /dev/null
+++ b/spring/notinote/src/main/java/com/answer/notinote/auth/strategy/GoogleLoadStrategy.java
@@ -0,0 +1,30 @@
+package com.answer.notinote.auth.strategy;
+
+import com.answer.notinote.auth.data.ProviderType;
+import com.answer.notinote.auth.data.dto.UserSocialResponseDto;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.util.Map;
+
+public class GoogleLoadStrategy extends ProviderLoadStrategy{
+ @Override
+ protected UserSocialResponseDto sendRequestToSocialSite(HttpEntity request) {
+ try {
+ ResponseEntity