From 8b5b25f214c6ba2e92bc4ca22ea2ce668d8f3766 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sat, 9 Nov 2024 14:35:10 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[2=EA=B0=95]=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20controller=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 ++++++++-- .../springhw4/controller/AdminController.java | 15 +++++++++++++++ .../springhw4/controller/MainController.java | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/springhw4/controller/AdminController.java create mode 100644 src/main/java/com/example/springhw4/controller/MainController.java diff --git a/build.gradle b/build.gradle index d6dd785..b0ce87b 100644 --- a/build.gradle +++ b/build.gradle @@ -24,10 +24,16 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + //implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' + //runtimeOnly 'com.mysql:mysql-connector-j' + + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/com/example/springhw4/controller/AdminController.java b/src/main/java/com/example/springhw4/controller/AdminController.java new file mode 100644 index 0000000..4f63655 --- /dev/null +++ b/src/main/java/com/example/springhw4/controller/AdminController.java @@ -0,0 +1,15 @@ +package com.example.springhw4.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@ResponseBody +public class AdminController { + + @GetMapping("/admin") + public String adminP(){ + return "admin Controller"; + } +} diff --git a/src/main/java/com/example/springhw4/controller/MainController.java b/src/main/java/com/example/springhw4/controller/MainController.java new file mode 100644 index 0000000..e17ee53 --- /dev/null +++ b/src/main/java/com/example/springhw4/controller/MainController.java @@ -0,0 +1,15 @@ +package com.example.springhw4.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@ResponseBody +public class MainController { + + @GetMapping("/") + public String mainP(){ + return "Main Controller"; + } +} From 9a59b24fa1808015472bc1ab10a0926eaf469934 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sat, 9 Nov 2024 14:56:50 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[3=EA=B0=95]=20Config=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/SecurityConfig.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/com/example/springhw4/Config/SecurityConfig.java diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java new file mode 100644 index 0000000..ecf08e8 --- /dev/null +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -0,0 +1,52 @@ +package com.example.springhw4.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.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 + // 세션 방식 : 세션 고정 -> csrf 공격 방어 + // 토큰 방식 : 세션 stateless -> 방어 굳이 x + http + .csrf((auth)-> auth.disable()); + + // Form 로그인 방식 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") + .anyRequest().authenticated()); + + // 세션 설정 + http + .sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + + return http.build(); + } +} From c42af2c5ef3cbce4a7c4f4d166f3b07a1dbd6e87 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:27:13 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[5=EA=B0=95]=20Entity=20=EB=B0=8F=20Repos?= =?UTF-8?q?itory=20=EC=9E=91=EC=84=B1,=20DB=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++-- .../example/springhw4/entity/UserEntity.java | 23 +++++++++++++++++++ .../springhw4/repository/UserRepository.java | 9 ++++++++ src/main/resources/application.yml | 15 ------------ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/example/springhw4/entity/UserEntity.java create mode 100644 src/main/java/com/example/springhw4/repository/UserRepository.java delete mode 100644 src/main/resources/application.yml diff --git a/build.gradle b/build.gradle index b0ce87b..92ece06 100644 --- a/build.gradle +++ b/build.gradle @@ -24,11 +24,11 @@ repositories { } dependencies { - //implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' compileOnly 'org.projectlombok:lombok' - //runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'com.mysql:mysql-connector-j' implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' diff --git a/src/main/java/com/example/springhw4/entity/UserEntity.java b/src/main/java/com/example/springhw4/entity/UserEntity.java new file mode 100644 index 0000000..aa747d0 --- /dev/null +++ b/src/main/java/com/example/springhw4/entity/UserEntity.java @@ -0,0 +1,23 @@ +package com.example.springhw4.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +public class UserEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + private String username; + private String password; + private String role; + +} diff --git a/src/main/java/com/example/springhw4/repository/UserRepository.java b/src/main/java/com/example/springhw4/repository/UserRepository.java new file mode 100644 index 0000000..8458a7b --- /dev/null +++ b/src/main/java/com/example/springhw4/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.example.springhw4.repository; + +import com.example.springhw4.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 9b78d0c..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,15 +0,0 @@ -spring: - datasource: - url: jdbc:mysql://localhost:3306/your_database_name?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8 - username: your_username - password: your_password - driver-class-name: com.mysql.cj.jdbc.Driver - - jpa: - hibernate: - ddl-auto: create - show-sql: true - properties: - hibernate: - format_sql: true - dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file From 440c8b6c37bb321efe1fc9c690cc74a72e46ad3d Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sun, 10 Nov 2024 08:47:02 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[6=EA=B0=95]=20Service=20&=20Dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/SecurityConfig.java | 1 + .../springhw4/controller/JoinController.java | 24 +++++++++++++ .../com/example/springhw4/dto/JoinDTO.java | 12 +++++++ .../springhw4/repository/UserRepository.java | 2 +- .../springhw4/service/JoinService.java | 35 +++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/springhw4/controller/JoinController.java create mode 100644 src/main/java/com/example/springhw4/dto/JoinDTO.java create mode 100644 src/main/java/com/example/springhw4/service/JoinService.java diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java index ecf08e8..56889e8 100644 --- a/src/main/java/com/example/springhw4/Config/SecurityConfig.java +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; + @Configuration @EnableWebSecurity public class SecurityConfig { diff --git a/src/main/java/com/example/springhw4/controller/JoinController.java b/src/main/java/com/example/springhw4/controller/JoinController.java new file mode 100644 index 0000000..24bd6a8 --- /dev/null +++ b/src/main/java/com/example/springhw4/controller/JoinController.java @@ -0,0 +1,24 @@ +package com.example.springhw4.controller; + +import com.example.springhw4.dto.JoinDTO; +import com.example.springhw4.service.JoinService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@ResponseBody +@RequiredArgsConstructor +public class JoinController { + + private final JoinService joinService; + + @PostMapping("/join") + public String joinProcess(JoinDTO joinDTO){ + + joinService.joinProcess(joinDTO); + + return "ok"; + } +} diff --git a/src/main/java/com/example/springhw4/dto/JoinDTO.java b/src/main/java/com/example/springhw4/dto/JoinDTO.java new file mode 100644 index 0000000..9323266 --- /dev/null +++ b/src/main/java/com/example/springhw4/dto/JoinDTO.java @@ -0,0 +1,12 @@ +package com.example.springhw4.dto; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class JoinDTO { + + private String username; + private String password; +} diff --git a/src/main/java/com/example/springhw4/repository/UserRepository.java b/src/main/java/com/example/springhw4/repository/UserRepository.java index 8458a7b..4405964 100644 --- a/src/main/java/com/example/springhw4/repository/UserRepository.java +++ b/src/main/java/com/example/springhw4/repository/UserRepository.java @@ -5,5 +5,5 @@ public interface UserRepository extends JpaRepository { - + Boolean existsByUsername(String username); } diff --git a/src/main/java/com/example/springhw4/service/JoinService.java b/src/main/java/com/example/springhw4/service/JoinService.java new file mode 100644 index 0000000..3d7f6c9 --- /dev/null +++ b/src/main/java/com/example/springhw4/service/JoinService.java @@ -0,0 +1,35 @@ +package com.example.springhw4.service; + +import com.example.springhw4.dto.JoinDTO; +import com.example.springhw4.entity.UserEntity; +import com.example.springhw4.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class JoinService { + + private final UserRepository userRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + public void joinProcess(JoinDTO joinDTO){ + + String username = joinDTO.getUsername(); + String password = joinDTO.getPassword(); + + Boolean isExist = userRepository.existsByUsername(username); + + if (isExist){ + + return; + } + + UserEntity data = new UserEntity(); + + data.setUsername(username); + data.setPassword(bCryptPasswordEncoder.encode(password)); // 암호화 진행 후 주입 + data.setRole("ROLE_ADMIN"); // 접두사를 무조건 가져야 함 + } +} From 134afdaf93386aa854e32b0c20c82eb75ddd164a Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:25:00 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[7=EA=B0=95]=20LoginFilter=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/SecurityConfig.java | 27 ++++++++++-- .../example/springhw4/jwt/LoginFilter.java | 43 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/springhw4/jwt/LoginFilter.java diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java index 56889e8..44002b2 100644 --- a/src/main/java/com/example/springhw4/Config/SecurityConfig.java +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -1,18 +1,35 @@ package com.example.springhw4.Config; +import com.example.springhw4.jwt.LoginFilter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final AuthenticationConfiguration authenticationConfiguration; + + //AuthenticationManager Bean 등록 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + + return configuration.getAuthenticationManager(); + } + + @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { @@ -26,15 +43,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ // 세션 방식 : 세션 고정 -> csrf 공격 방어 // 토큰 방식 : 세션 stateless -> 방어 굳이 x http - .csrf((auth)-> auth.disable()); + .csrf(AbstractHttpConfigurer::disable); // Form 로그인 방식 disable http - .formLogin((auth) -> auth.disable()); + .formLogin(AbstractHttpConfigurer::disable); // http basic 인증 방식 disable http - .httpBasic((auth) -> auth.disable()); + .httpBasic(AbstractHttpConfigurer::disable); http .authorizeHttpRequests((auth)->auth @@ -42,6 +59,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ .requestMatchers("/admin").hasRole("ADMIN") .anyRequest().authenticated()); + http + // 정확한 자리에 대체 하기 위해 At + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class); + // 세션 설정 http .sessionManagement((session) -> session diff --git a/src/main/java/com/example/springhw4/jwt/LoginFilter.java b/src/main/java/com/example/springhw4/jwt/LoginFilter.java new file mode 100644 index 0000000..12a9ad9 --- /dev/null +++ b/src/main/java/com/example/springhw4/jwt/LoginFilter.java @@ -0,0 +1,43 @@ +package com.example.springhw4.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +public class LoginFilter extends UsernamePasswordAuthenticationFilter { + + // 검증 담당하는 곳 + private final AuthenticationManager authenticationManager; + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + + String username = obtainUsername(request); + String password = obtainPassword(request); + + System.out.println(username); + + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); + + return authenticationManager.authenticate(authToken); + } + + //로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨) + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { + + } + + //로그인 실패시 실행하는 메소드 + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + + } +} From 54c920ce7c35affa28810f4a575671e4e928d7d7 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:04:08 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[8=EA=B0=95]=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/dto/CustomUserDetails.java | 59 +++++++++++++++++++ .../example/springhw4/jwt/LoginFilter.java | 4 +- .../springhw4/repository/UserRepository.java | 1 + .../service/CustomUserDetailsService.java | 28 +++++++++ .../springhw4/service/JoinService.java | 2 + 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/springhw4/dto/CustomUserDetails.java create mode 100644 src/main/java/com/example/springhw4/service/CustomUserDetailsService.java diff --git a/src/main/java/com/example/springhw4/dto/CustomUserDetails.java b/src/main/java/com/example/springhw4/dto/CustomUserDetails.java new file mode 100644 index 0000000..0bd3722 --- /dev/null +++ b/src/main/java/com/example/springhw4/dto/CustomUserDetails.java @@ -0,0 +1,59 @@ +package com.example.springhw4.dto; + +import com.example.springhw4.entity.UserEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +@RequiredArgsConstructor +public class CustomUserDetails implements UserDetails { + + + private final UserEntity userEntity; + + @Override + public Collection getAuthorities() { + Collection collection = new ArrayList<>(); + collection.add(new GrantedAuthority() { + @Override + public String getAuthority() { + return userEntity.getRole(); + } + + }); + + return collection; + } + + @Override + public String getPassword() { + return userEntity.getPassword(); + } + + @Override + public String getUsername() { + return userEntity.getUsername(); + } + + @Override + public boolean isAccountNonExpired(){ + return true; + } + + @Override + public boolean isAccountNonLocked(){ + return true; + } + @Override + public boolean isCredentialsNonExpired(){ + return true; + } + @Override + public boolean isEnabled(){ + return true; + } + +} diff --git a/src/main/java/com/example/springhw4/jwt/LoginFilter.java b/src/main/java/com/example/springhw4/jwt/LoginFilter.java index 12a9ad9..c0f497e 100644 --- a/src/main/java/com/example/springhw4/jwt/LoginFilter.java +++ b/src/main/java/com/example/springhw4/jwt/LoginFilter.java @@ -32,12 +32,12 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ //로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨) @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { - + System.out.println("배고ㅠ"); } //로그인 실패시 실행하는 메소드 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { - + System.out.println("ㅋ"); } } diff --git a/src/main/java/com/example/springhw4/repository/UserRepository.java b/src/main/java/com/example/springhw4/repository/UserRepository.java index 4405964..79c0a24 100644 --- a/src/main/java/com/example/springhw4/repository/UserRepository.java +++ b/src/main/java/com/example/springhw4/repository/UserRepository.java @@ -6,4 +6,5 @@ public interface UserRepository extends JpaRepository { Boolean existsByUsername(String username); + UserEntity findByUsername(String username); } diff --git a/src/main/java/com/example/springhw4/service/CustomUserDetailsService.java b/src/main/java/com/example/springhw4/service/CustomUserDetailsService.java new file mode 100644 index 0000000..085c703 --- /dev/null +++ b/src/main/java/com/example/springhw4/service/CustomUserDetailsService.java @@ -0,0 +1,28 @@ +package com.example.springhw4.service; + +import com.example.springhw4.dto.CustomUserDetails; +import com.example.springhw4.entity.UserEntity; +import com.example.springhw4.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 username) throws UsernameNotFoundException { + + UserEntity userData = userRepository.findByUsername(username); + + if(userData != null){ + return new CustomUserDetails(userData); + } + + return null; + } +} diff --git a/src/main/java/com/example/springhw4/service/JoinService.java b/src/main/java/com/example/springhw4/service/JoinService.java index 3d7f6c9..f6568cb 100644 --- a/src/main/java/com/example/springhw4/service/JoinService.java +++ b/src/main/java/com/example/springhw4/service/JoinService.java @@ -31,5 +31,7 @@ public void joinProcess(JoinDTO joinDTO){ data.setUsername(username); data.setPassword(bCryptPasswordEncoder.encode(password)); // 암호화 진행 후 주입 data.setRole("ROLE_ADMIN"); // 접두사를 무조건 가져야 함 + + userRepository.save(data); } } From 7830bc1fdb6e125fb38eef5cb683ad736acae7bb Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:04:22 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[9=EA=B0=95]=20JWT=20=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/springhw4/jwt/JWTUtil.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/java/com/example/springhw4/jwt/JWTUtil.java diff --git a/src/main/java/com/example/springhw4/jwt/JWTUtil.java b/src/main/java/com/example/springhw4/jwt/JWTUtil.java new file mode 100644 index 0000000..8dadc53 --- /dev/null +++ b/src/main/java/com/example/springhw4/jwt/JWTUtil.java @@ -0,0 +1,47 @@ +package com.example.springhw4.jwt; + +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + + private SecretKey secretKey; + + public JWTUtil(@Value("${spring.jwt.secret}")String secret) { + + secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + public String getUsername(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); + } + + public String getRole(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); + } + + public Boolean isExpired(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } + + public String createJwt(String username, String role, Long expiredMs) { + + return Jwts.builder() + .claim("username", username) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) // expiredMs 토큰의 유효시간 + .signWith(secretKey) // 암호화 + .compact(); + } +} From ef772965c6c238221fd7b84be69db42ca3158ad8 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:16:48 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[10=EA=B0=95]=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20JWT=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/SecurityConfig.java | 4 +++- .../example/springhw4/jwt/LoginFilter.java | 23 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java index 44002b2..6124a42 100644 --- a/src/main/java/com/example/springhw4/Config/SecurityConfig.java +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.example.springhw4.Config; +import com.example.springhw4.jwt.JWTUtil; import com.example.springhw4.jwt.LoginFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -21,6 +22,7 @@ public class SecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; + private final JWTUtil jwtUtil; //AuthenticationManager Bean 등록 @Bean @@ -61,7 +63,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http // 정확한 자리에 대체 하기 위해 At - .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class); + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration),jwtUtil), UsernamePasswordAuthenticationFilter.class); // 세션 설정 http diff --git a/src/main/java/com/example/springhw4/jwt/LoginFilter.java b/src/main/java/com/example/springhw4/jwt/LoginFilter.java index c0f497e..828677f 100644 --- a/src/main/java/com/example/springhw4/jwt/LoginFilter.java +++ b/src/main/java/com/example/springhw4/jwt/LoginFilter.java @@ -1,5 +1,6 @@ package com.example.springhw4.jwt; +import com.example.springhw4.dto.CustomUserDetails; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -8,13 +9,18 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import java.util.Collection; +import java.util.Iterator; + @RequiredArgsConstructor public class LoginFilter extends UsernamePasswordAuthenticationFilter { // 검증 담당하는 곳 private final AuthenticationManager authenticationManager; + private final JWTUtil jwtUtil; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { @@ -32,7 +38,22 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ //로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨) @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { - System.out.println("배고ㅠ"); + + CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal(); + + String username = customUserDetails.getUsername(); + + Collection authorities = authentication.getAuthorities(); + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + + String role = auth.getAuthority(); + + // jwt 생성 + String token = jwtUtil.createJwt(username, role, 60*60*10L); + + // response 에 담아서 전달 접두사 Bearer 붙여야 함 + response.addHeader("Authorization", "Bearer " + token); } //로그인 실패시 실행하는 메소드 From aa19284abd4b7ef6b44c0ba6f2f9c45ec881e688 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Mon, 11 Nov 2024 00:21:41 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[11=EA=B0=95]=20JWTFilter=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/SecurityConfig.java | 3 + .../com/example/springhw4/jwt/JWTFilter.java | 71 +++++++++++++++++++ .../com/example/springhw4/jwt/JWTUtil.java | 4 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/springhw4/jwt/JWTFilter.java diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java index 6124a42..cb9077e 100644 --- a/src/main/java/com/example/springhw4/Config/SecurityConfig.java +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.example.springhw4.Config; +import com.example.springhw4.jwt.JWTFilter; import com.example.springhw4.jwt.JWTUtil; import com.example.springhw4.jwt.LoginFilter; import lombok.RequiredArgsConstructor; @@ -61,6 +62,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ .requestMatchers("/admin").hasRole("ADMIN") .anyRequest().authenticated()); + http + .addFilterAt(new JWTFilter(jwtUtil), LoginFilter.class); http // 정확한 자리에 대체 하기 위해 At .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration),jwtUtil), UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/com/example/springhw4/jwt/JWTFilter.java b/src/main/java/com/example/springhw4/jwt/JWTFilter.java new file mode 100644 index 0000000..c3ec0b4 --- /dev/null +++ b/src/main/java/com/example/springhw4/jwt/JWTFilter.java @@ -0,0 +1,71 @@ +package com.example.springhw4.jwt; + +import com.example.springhw4.dto.CustomUserDetails; +import com.example.springhw4.entity.UserEntity; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JWTFilter extends OncePerRequestFilter { + + private final JWTUtil jwtUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + //request에서 Authorization 헤더를 찾음 + String authorization = request.getHeader("Authorization"); + + if (authorization == null || !authorization.startsWith("Bearer ")) { + + System.out.println("token null"); + filterChain.doFilter(request, response); + + return; + } + + // Bearer 부분 제거 후 순수 토큰만 획득 + String token = authorization.split(" ")[1]; + + // 토큰 소멸 시간 검증 + if (jwtUtil.isExpired(token)) { + + System.out.println("token expired"); + filterChain.doFilter(request, response); + + // 조건이 해당되면 메소드 종료 (필수) + return; + } + + // 토큰에서 username 과 role 획득 + String username = jwtUtil.getUsername(token); + String role = jwtUtil.getRole(token); + + // userEntity를 생성하여 값 set + UserEntity userEntity = new UserEntity(); + userEntity.setUsername(username); + // 임시 비밀번호 + userEntity.setPassword("temppassword"); + userEntity.setRole(role); + + //UserDetails에 회원 정보 객체 담기 + CustomUserDetails customUserDetails = new CustomUserDetails(userEntity); + + //스프링 시큐리티 인증 토큰 생성 + Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + //세션에 사용자 등록 + SecurityContextHolder.getContext().setAuthentication(authToken); + + filterChain.doFilter(request, response); + + } +} diff --git a/src/main/java/com/example/springhw4/jwt/JWTUtil.java b/src/main/java/com/example/springhw4/jwt/JWTUtil.java index 8dadc53..f406f1e 100644 --- a/src/main/java/com/example/springhw4/jwt/JWTUtil.java +++ b/src/main/java/com/example/springhw4/jwt/JWTUtil.java @@ -40,8 +40,8 @@ public String createJwt(String username, String role, Long expiredMs) { .claim("username", username) .claim("role", role) .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + expiredMs)) // expiredMs 토큰의 유효시간 - .signWith(secretKey) // 암호화 + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) .compact(); } } From d7499d76c179515eeea6139130f1999d1892213e Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Mon, 11 Nov 2024 00:25:19 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[12=EA=B0=95]=20username=EA=B3=BC=20role?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/controller/MainController.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/springhw4/controller/MainController.java b/src/main/java/com/example/springhw4/controller/MainController.java index e17ee53..d12ec55 100644 --- a/src/main/java/com/example/springhw4/controller/MainController.java +++ b/src/main/java/com/example/springhw4/controller/MainController.java @@ -1,15 +1,32 @@ package com.example.springhw4.controller; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; +import java.util.Collection; +import java.util.Iterator; + @Controller @ResponseBody public class MainController { @GetMapping("/") public String mainP(){ - return "Main Controller"; + + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + Collection authorities = authentication.getAuthorities(); + Iterator iter = authorities.iterator(); + GrantedAuthority auth = iter.next(); + String role = auth.getAuthority(); + + + return "Main Controller" + username + role; } } From 848cdf222a22d84fe6154a6d711f54c6de1955ae Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Mon, 11 Nov 2024 00:32:11 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[13=EA=B0=95]=20CORS=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springhw4/Config/CorsMvcConfig.java | 16 ++++++++++++ .../springhw4/Config/SecurityConfig.java | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/main/java/com/example/springhw4/Config/CorsMvcConfig.java diff --git a/src/main/java/com/example/springhw4/Config/CorsMvcConfig.java b/src/main/java/com/example/springhw4/Config/CorsMvcConfig.java new file mode 100644 index 0000000..ecccf9b --- /dev/null +++ b/src/main/java/com/example/springhw4/Config/CorsMvcConfig.java @@ -0,0 +1,16 @@ +package com.example.springhw4.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 CorsMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry corsRegistry) { + + corsRegistry.addMapping("/**") + .allowedOrigins("http://localhost:3000"); + } +} diff --git a/src/main/java/com/example/springhw4/Config/SecurityConfig.java b/src/main/java/com/example/springhw4/Config/SecurityConfig.java index cb9077e..0a41063 100644 --- a/src/main/java/com/example/springhw4/Config/SecurityConfig.java +++ b/src/main/java/com/example/springhw4/Config/SecurityConfig.java @@ -3,6 +3,7 @@ import com.example.springhw4.jwt.JWTFilter; import com.example.springhw4.jwt.JWTUtil; import com.example.springhw4.jwt.LoginFilter; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,6 +16,10 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Collections; @Configuration @@ -42,6 +47,26 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ + http + .cors((cors)->cors + .configurationSource(new CorsConfigurationSource() { + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); + configuration.setAllowedMethods(Collections.singletonList("*")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Collections.singletonList("*")); + configuration.setMaxAge(3600L); + + configuration.setExposedHeaders(Collections.singletonList("Authorization")); + + return configuration; + } + })); + // csrf disable // 세션 방식 : 세션 고정 -> csrf 공격 방어 // 토큰 방식 : 세션 stateless -> 방어 굳이 x From b3bb52985f7e7da442087f6faad2590ff74da4c9 Mon Sep 17 00:00:00 2001 From: SANGHEEJEONG <147232817+SANGHEEJEONG@users.noreply.github.com> Date: Mon, 11 Nov 2024 00:40:04 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[14=EA=B0=95]=20JWT=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=AA=A9=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 8dc5c6a..c01c341 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ # hw4 + +## JWT의 목적 + ++ 모바일 앱 : 특성상 JWT 방식으로 인증 + STATELESS(부수적 효과) ++ 모바일 앱 로그아웃 : 모바일 앱에서는 JWT 탈취 우려 X, +로그아웃 -> JWT 제거하면 추가 조치 필요 없음 ++ 장시간 로그인 : 세션으로 -> 서버 측 부하 +