From dcd55a7bf3b9fb1d2573fd576179cc8efeae357d Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:39:11 +0000 Subject: [PATCH 01/52] Feat: SecurityUser implements UserDetails --- .../global/auth/SecurityUser.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java new file mode 100644 index 0000000..4b6291c --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java @@ -0,0 +1,57 @@ +package com.ssafy.springbootapi.global.auth; + +import com.ssafy.springbootapi.domain.user.domain.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +public class SecurityUser implements UserDetails { + private final User user; + + public SecurityUser(User user) { + this.user = user; + } + + // 권한 반환 + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("user")); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getEmail(); + } + + // 계정 만료여부 반환 + @Override + public boolean isAccountNonExpired() { + return true; + } + + // 계정 잠금 여부 반환 + @Override + public boolean isAccountNonLocked() { + return true; + } + + // 패스워드 만료 여부 반환 + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + // 계정 사용 가능 여부 반환 + @Override + public boolean isEnabled() { + return true; + } +} From 5c21b5cdd0f8af0dd9011c756595fd2d396201e9 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:39:25 +0000 Subject: [PATCH 02/52] Feat: UserDetailService --- .../global/auth/UserDetailService.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java new file mode 100644 index 0000000..5e95df0 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java @@ -0,0 +1,24 @@ +package com.ssafy.springbootapi.global.auth; + +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; +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 UserDetailService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) { + User user = userRepository.findByEmail(email) + .orElseThrow(()->new UsernameNotFoundException(email+" 사용자 없음")); + SecurityUser securityUser = new SecurityUser(user); + return securityUser; + } +} From 9bbadb0162762cbca0ca57611c5fabb0e9ca76b3 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:40:48 +0000 Subject: [PATCH 03/52] =?UTF-8?q?Feat:=20jwt=20token=20entity,=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=EC=9E=90,=20security=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/jwt/JwtProperties.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtProperties.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtProperties.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtProperties.java new file mode 100644 index 0000000..0f2daa9 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtProperties.java @@ -0,0 +1,15 @@ +package com.ssafy.springbootapi.global.auth.jwt; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Setter +@Getter +@Component +@ConfigurationProperties("jwt") +public class JwtProperties { + private String issuer; + private String secretKey; +} From 6fe82e447855ef56740308ea630bd91fe55966a8 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:41:13 +0000 Subject: [PATCH 04/52] Feat: jwt token provider --- .../global/auth/jwt/TokenProvider.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java new file mode 100644 index 0000000..609e0ed --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java @@ -0,0 +1,79 @@ +package com.ssafy.springbootapi.global.auth.jwt; + +import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.global.auth.SecurityUser; +import com.ssafy.springbootapi.global.auth.UserDetailService; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Date; + +@RequiredArgsConstructor +@Service +public class TokenProvider { + private final JwtProperties jwtProperties; + private final UserDetailService userDetailService; + + public String generateToken(User user, Duration expiredAt) { + Date now = new Date(); + return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),user); + } + + // jwt token 생성 + private String makeToken(Date expiry, User user){ + Date now = new Date(); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setIssuer(jwtProperties.getIssuer()) + .setIssuedAt(now) // iat: 발급시간 + .setExpiration(expiry) // exp: 만료시간 + .setSubject(user.getEmail()) // sub: 유저 이메일 + .claim("id", user.getId()) // 클레임 id + .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()) + .compact(); + } + + // JWT 토큰 유효성 검증 메서드 + public boolean validToken(String token){ + try { + Jwts.parser() + .setSigningKey(jwtProperties.getSecretKey()) + .parseClaimsJws(token); + return true; + } catch (Exception e){ + return false; + } + } + + // 토큰 기반으로 인증 정보를 가져오는 메서드 (토큰 유효성 검사 이후 실행) + public Authentication getAuthentication(String token){ + Claims claims = getClaims(token); + String userEmail = claims.getSubject(); + UserDetails user = userDetailService.loadUserByUsername(userEmail); + + return new UsernamePasswordAuthenticationToken(user.getUsername(), token, user.getAuthorities()); + } + + // 토큰 기반으로 유저 ID를 가져오는 메서드 + public Long getUserId(String token) { + Claims claims = getClaims(token); + return claims.get("id",Long.class); + } + + // 클래임 조회 + private Claims getClaims(String token){ + return Jwts.parser() + .setSigningKey(jwtProperties.getSecretKey()) + .parseClaimsJws(token) + .getBody(); + } +} From aa2c8017c164522437f0f8ed7bc0739ef2fe9e61 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:42:55 +0000 Subject: [PATCH 05/52] Feat: UserAuthenticationProvider for login --- .../auth/UserAuthenticationProvider.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java new file mode 100644 index 0000000..975b67f --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java @@ -0,0 +1,40 @@ +package com.ssafy.springbootapi.global.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +class UserAuthenticationProvider implements AuthenticationProvider { + + private final UserDetailService userDetailService; + private final PasswordEncoder passwordEncoder; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String password = authentication.getCredentials().toString(); + + UserDetails u = userDetailService.loadUserByUsername(username); + if (passwordEncoder.matches(password, u.getPassword())) { + // 암호일치하면 필요한 세부정보를 넣은 Authentication 객체를 반환 + return new UsernamePasswordAuthenticationToken( + username, password, u.getAuthorities() + ); + } else { + throw new BadCredentialsException("Something went wrong!"); + } + } + + @Override + public boolean supports(Class authentication) { + return false; + } +} From 86a690295ab8cf66ec1f6eed8101dac78945cd35 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:43:19 +0000 Subject: [PATCH 06/52] Feat: refresh token --- .../refreshToken/RefreshToeknRepository.java | 12 +++++++ .../auth/jwt/refreshToken/RefreshToken.java | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java new file mode 100644 index 0000000..e0d51d6 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java @@ -0,0 +1,12 @@ +package com.ssafy.springbootapi.global.auth.jwt.refreshToken; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RefreshToeknRepository extends JpaRepository { + Optional findByUserId(Long userId); + Optional findByRefreshToken(String refreshToken); +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java new file mode 100644 index 0000000..d22a2cf --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java @@ -0,0 +1,35 @@ +package com.ssafy.springbootapi.global.auth.jwt.refreshToken; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +@Entity +public class RefreshToken { + @Id + @GeneratedValue + @Column(name = "id", updatable = false) + private Long id; + + @Column(name = "user_id", nullable = false, unique = true) + private Long userId; + + @Column(name = "refresh_token", nullable = false) + private String refreshToken; + + public RefreshToken(Long userId, String refreshToken){ + this.userId = userId; + this.refreshToken = refreshToken; + } + + public RefreshToken update(String newRefreshToken) { + this.refreshToken = newRefreshToken; + return this; + } + +} From c09eff32590a9254f6a95ee15f06311351176dcf Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:43:33 +0000 Subject: [PATCH 07/52] Feat: jwt authentication filter --- .../auth/jwt/JwtAuthenticationFilter.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..3131ada --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,52 @@ +package com.ssafy.springbootapi.global.auth.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final TokenProvider tokenProvider; + private final static String HEADER_AUTHORIZATION = "Authorization"; + private final static String TOKEN_PREFIX = "Bearer"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // 요청 헤더의 Authorization 키의 값 조회 + String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); + // 가져온 값에서 접두사 제거 + String token = getAccessToken(authorizationHeader); + // 가져온 토큰이 유효한지 확인하고, 유효하면 인증정보 설정 + if(tokenProvider.validToken(token)) { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request,response); + } + + private String getAccessToken(String authorizationHeader){ + if(authorizationHeader!=null && authorizationHeader.startsWith(TOKEN_PREFIX)) { + return authorizationHeader.substring(TOKEN_PREFIX.length()); + } + return null; + } + + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + return request.getServletPath() + .equals("/api/v1/login"); + } +} From c2661f7fcfdd06cbb5ff37899f6ecb395b333f0e Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:43:46 +0000 Subject: [PATCH 08/52] Feat: security config --- .../global/config/WebSecurityConfig.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java new file mode 100644 index 0000000..dbdbefb --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -0,0 +1,34 @@ +package com.ssafy.springbootapi.global.config; + +import com.ssafy.springbootapi.global.auth.UserDetailService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +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.configuration.WebSecurityCustomizer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class WebSecurityConfig { + private final UserDetailService userDetailService; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.build(); + } + + @Bean + AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + +} From 2d5e164c6d5b4b5e7b1bcd9b000171c7141049f2 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:45:32 +0000 Subject: [PATCH 09/52] =?UTF-8?q?=E2=9E=95Chore:=20jwt=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-api/build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spring-boot-api/build.gradle b/spring-boot-api/build.gradle index a0bd52a..b6f131e 100755 --- a/spring-boot-api/build.gradle +++ b/spring-boot-api/build.gradle @@ -17,8 +17,11 @@ repositories { 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' + // security + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt:0.9.1' + // for swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' runtimeOnly 'com.mysql:mysql-connector-j' From 35cbcbb033a7faec5fb2e8c3fc224cddc91977c0 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 21 Apr 2024 10:46:30 +0000 Subject: [PATCH 10/52] =?UTF-8?q?=E2=9C=A8Feat:=20jwt=20application=20prop?= =?UTF-8?q?erties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-api/src/main/resources/application.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-boot-api/src/main/resources/application.yml b/spring-boot-api/src/main/resources/application.yml index 48f4a90..b02946c 100644 --- a/spring-boot-api/src/main/resources/application.yml +++ b/spring-boot-api/src/main/resources/application.yml @@ -10,7 +10,7 @@ spring: database: mysql database-platform: org.hibernate.dialect.MySQL8Dialect hibernate: - ddl-auto: none + ddl-auto: create generate-ddl: false show-sql: true properties: @@ -31,4 +31,8 @@ springdoc: display-query-params-without-oauth2: true doc-expansion: none paths-to-match: - - /api/** \ No newline at end of file + - /api/** + +jwt: + issuer: kkh + secret_key: test_security \ No newline at end of file From e4dfd5af83368c9f8713d718297c18bd8162fd5f Mon Sep 17 00:00:00 2001 From: kkh Date: Tue, 23 Apr 2024 00:58:49 +0000 Subject: [PATCH 11/52] =?UTF-8?q?=E2=9C=A8Feat:=20BCryptPasswordEncoder=20?= =?UTF-8?q?bean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssafy/springbootapi/global/config/WebSecurityConfig.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java index dbdbefb..2373d01 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -10,6 +10,7 @@ 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.WebSecurityCustomizer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -30,5 +31,9 @@ AuthenticationManager authenticationManager( return authenticationConfiguration.getAuthenticationManager(); } + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } From a559ab7360d704d6f0fe0f1a5de4120fc0fff380 Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 24 Apr 2024 00:31:49 +0000 Subject: [PATCH 12/52] =?UTF-8?q?:recycle:Refactor=20:=20User=20addresses?= =?UTF-8?q?=20column=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ssafy/springbootapi/domain/user/domain/User.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index 052a2ad..cc9275d 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -34,7 +34,6 @@ public class User { private String name; @OneToMany(mappedBy = "user") - @Column(name = "addresses" , nullable = true) private List
addresses; @Override From c1e65aa97087672f7e2a9d72fb5ebfe1f47bd9b7 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:43:31 +0000 Subject: [PATCH 13/52] :heavy_plus_sign: Chore: jwt & security dependency --- spring-boot-api/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-boot-api/build.gradle b/spring-boot-api/build.gradle index b6f131e..18f14a4 100755 --- a/spring-boot-api/build.gradle +++ b/spring-boot-api/build.gradle @@ -21,6 +21,9 @@ dependencies { // security implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt:0.9.1' + // https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api + implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.1' + // for swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' From 620d8067f4a3b2c5b2d211fdbfcc54b48732c384 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:45:07 +0000 Subject: [PATCH 14/52] :sparkles: Feat: JwtToken Provider --- .../com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java index 609e0ed..e7ef5f5 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java @@ -60,7 +60,7 @@ public Authentication getAuthentication(String token){ String userEmail = claims.getSubject(); UserDetails user = userDetailService.loadUserByUsername(userEmail); - return new UsernamePasswordAuthenticationToken(user.getUsername(), token, user.getAuthorities()); + return new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); } // 토큰 기반으로 유저 ID를 가져오는 메서드 From 126e60becd4e298d76b017425658117b824acb93 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:45:34 +0000 Subject: [PATCH 15/52] :sparkles: Feat: RefreshToken entity --- .../global/auth/jwt/refreshToken/RefreshToken.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java index d22a2cf..f2f0394 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java @@ -4,10 +4,14 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +@Builder @NoArgsConstructor +@AllArgsConstructor @Getter @Entity public class RefreshToken { From 1f7b8478e248c19a0c098b256de1d33f972b3aeb Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:46:27 +0000 Subject: [PATCH 16/52] :sparkles: Feat: JWT AuthenticationFilter --- .../global/auth/jwt/JwtAuthenticationFilter.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java index 3131ada..d1c656f 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java @@ -23,6 +23,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + System.out.println("여기 들어오나?"); // 요청 헤더의 Authorization 키의 값 조회 String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); // 가져온 값에서 접두사 제거 @@ -46,7 +47,13 @@ private String getAccessToken(String authorizationHeader){ @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return request.getServletPath() - .equals("/api/v1/login"); + String path = request.getRequestURI(); + System.out.println(request.getMethod() + " "+ request.getRequestURI()); + if(request.getMethod().equals("POST")&&path.equals("/api/v1/users")){ + return true; + } + return path.equals("/api/v1/users/login") + || path.startsWith("/swagger-ui") + || path.startsWith("/api-docs"); } } From 269aa51f4b88c1de45e1ccd04395aeee7f227b6e Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:47:07 +0000 Subject: [PATCH 17/52] :sparkles: Feat: User Authentication --- .../auth/CustomBCryptPasswordEncoder.java | 26 +++++++++ ...nUsernamePasswordAuthenticationFilter.java | 56 +++++++++++++++++++ .../auth/MyAuthenticationSuccessHandler.java | 56 +++++++++++++++++++ .../auth/UserAuthenticationProvider.java | 8 +-- 4 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java new file mode 100644 index 0000000..63119aa --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java @@ -0,0 +1,26 @@ +package com.ssafy.springbootapi.global.auth; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +public class CustomBCryptPasswordEncoder implements PasswordEncoder { + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + public CustomBCryptPasswordEncoder() { + this.bCryptPasswordEncoder = new BCryptPasswordEncoder(); + } + + @Override + public String encode(CharSequence rawPassword) { + // 여기에 비밀번호 인코딩 전에 추가 로직을 구현할 수 있습니다. + return bCryptPasswordEncoder.encode(rawPassword); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + // 여기에 매칭 로직 전에 추가 검사를 구현할 수 있습니다. + return bCryptPasswordEncoder.matches(rawPassword, encodedPassword); + } +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java new file mode 100644 index 0000000..2498914 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java @@ -0,0 +1,56 @@ +package com.ssafy.springbootapi.global.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssafy.springbootapi.domain.user.dto.UserLoginRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +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; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; + +@Component +public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + public JsonUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) { + super.setAuthenticationManager(authenticationManager); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + System.out.println("In JsonUsernamePasswordAuthenticationFilter"); + if ("application/json".equals(request.getContentType())) { + try { + // JSON 요청에서 사용자 이름과 비밀번호를 읽어옵니다. + BufferedReader reader = request.getReader(); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + UserLoginRequest loginRequest = objectMapper.readValue(sb.toString(), UserLoginRequest.class); + setPasswordParameter("useremail "+loginRequest.getEmail()); + UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( + loginRequest.getEmail(), loginRequest.getPassword()); + + setDetails(request, authRequest); + return this.getAuthenticationManager().authenticate(authRequest); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + // 기본 form-urlencoded 방식 처리 + return super.attemptAuthentication(request, response); + } + } + +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java new file mode 100644 index 0000000..9009cb5 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java @@ -0,0 +1,56 @@ +package com.ssafy.springbootapi.global.auth; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssafy.springbootapi.domain.user.application.UserService; +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.domain.user.dto.UserLoginResponse; +import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToeknRepository; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; +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.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.Duration; + +@Component +public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private final TokenProvider tokenProvider; + private final RefreshToeknRepository refreshToeknRepository; + private final UserRepository userRepository; + + public MyAuthenticationSuccessHandler(TokenProvider tokenProvider, RefreshToeknRepository refreshToeknRepository, UserRepository userRepository) { + this.tokenProvider = tokenProvider; + this.refreshToeknRepository = refreshToeknRepository; + this.userRepository = userRepository; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { + AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + // 여기에서 UserService의 login을 실행 + User user = userRepository.findByEmail(authentication.getName()).orElseThrow(); + String accessToken = tokenProvider.generateToken(user, Duration.ofMinutes(1L)); + String refreshToken = tokenProvider.generateToken(user, Duration.ofMinutes(30L)); + System.out.println("accessToken "+accessToken); + refreshToeknRepository.save(RefreshToken.builder() + .userId(user.getId()) + .refreshToken(refreshToken) + .build()); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + new ObjectMapper().writeValue(response.getWriter(),new UserLoginResponse(accessToken,refreshToken)); + } +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java index 975b67f..aeecaff 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java @@ -12,8 +12,7 @@ @RequiredArgsConstructor @Component -class UserAuthenticationProvider implements AuthenticationProvider { - +public class UserAuthenticationProvider implements AuthenticationProvider { private final UserDetailService userDetailService; private final PasswordEncoder passwordEncoder; @@ -21,12 +20,13 @@ class UserAuthenticationProvider implements AuthenticationProvider { public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); + System.out.println("username in auth provider:"+username); UserDetails u = userDetailService.loadUserByUsername(username); if (passwordEncoder.matches(password, u.getPassword())) { // 암호일치하면 필요한 세부정보를 넣은 Authentication 객체를 반환 return new UsernamePasswordAuthenticationToken( - username, password, u.getAuthorities() + username, null, u.getAuthorities() ); } else { throw new BadCredentialsException("Something went wrong!"); @@ -35,6 +35,6 @@ public Authentication authenticate(Authentication authentication) throws Authent @Override public boolean supports(Class authentication) { - return false; + return true; } } From 3a9cb020cc39a1b4676328259eaac65481856f19 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:47:31 +0000 Subject: [PATCH 18/52] :sparkles: Feat: User Details Service --- .../com/ssafy/springbootapi/global/auth/UserDetailService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java index 5e95df0..bd4230a 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java @@ -18,7 +18,6 @@ public class UserDetailService implements UserDetailsService { public UserDetails loadUserByUsername(String email) { User user = userRepository.findByEmail(email) .orElseThrow(()->new UsernameNotFoundException(email+" 사용자 없음")); - SecurityUser securityUser = new SecurityUser(user); - return securityUser; + return new SecurityUser(user); } } From e5fea96601ea7784bb35ce01d79111783b59711d Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:48:05 +0000 Subject: [PATCH 19/52] :sparkles: Feat: Security Configuration --- .../global/config/WebSecurityConfig.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java index 2373d01..7982521 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -1,27 +1,69 @@ package com.ssafy.springbootapi.global.config; +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.global.auth.JsonUsernamePasswordAuthenticationFilter; +import com.ssafy.springbootapi.global.auth.MyAuthenticationSuccessHandler; +import com.ssafy.springbootapi.global.auth.UserAuthenticationProvider; import com.ssafy.springbootapi.global.auth.UserDetailService; +import com.ssafy.springbootapi.global.auth.jwt.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; +import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.configuration.WebSecurityCustomizer; +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.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class WebSecurityConfig { - private final UserDetailService userDetailService; + private final UserAuthenticationProvider userAuthenticationProvider; + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final AuthenticationConfiguration authenticationConfiguration; + private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // CSRF 보호 비활성화 + http.csrf(AbstractHttpConfigurer::disable); + + // http basic 인증 비활성화 + http.httpBasic(AbstractHttpConfigurer::disable); + http.formLogin(AbstractHttpConfigurer::disable); + + // 세션 관리 정책 설정 + http.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + // HTTP 요청에 대한 접근 제어 + http.authorizeHttpRequests(auth -> auth + .requestMatchers("**").permitAll() + .requestMatchers("/api/v1/login").permitAll() // 로그인 API는 모든 요청을 허용 + .anyRequest().authenticated() // 그 외의 모든 요청은 인증 필요 + ); + + // jwt 필터 추가 + http.addFilterBefore(jwtAuthenticationFilter + ,UsernamePasswordAuthenticationFilter.class); + + http.authenticationProvider(userAuthenticationProvider); + + // UsernamePasswordAuthenticationFilter 추가 + JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration)); + filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler); + filter.setFilterProcessesUrl("/api/v1/users/login"); + http.addFilterAt(filter, + UsernamePasswordAuthenticationFilter.class); + return http.build(); } @@ -31,9 +73,11 @@ AuthenticationManager authenticationManager( return authenticationConfiguration.getAuthenticationManager(); } - @Bean - public BCryptPasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } +// public JsonUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) { +// JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationManager); +// filter.setAuthenticationManager(authenticationManager); +// filter.setFilterProcessesUrl("/api/v1/users/login"); // 필터가 실행될 URL 설정 +// return filter; +// } } From c8240fc1d3ddc903c78d71a982bf6a289d60456d Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 26 Apr 2024 07:48:34 +0000 Subject: [PATCH 20/52] :poop: --- .../domain/user/api/UserController.java | 7 +++ .../domain/user/application/UserService.java | 44 ++++++++++++++++++- .../domain/user/dto/UserLoginRequest.java | 28 ++++++++++++ .../domain/user/dto/UserLoginResponse.java | 11 +++++ .../domain/user/dto/UserSignUpRequest.java | 12 ++--- 5 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginRequest.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java index aea198b..a96b8a2 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java @@ -19,6 +19,13 @@ public class UserController { private final UserService userService; + @PostMapping("/login") + @ToException + @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") + public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ + return ResponseEntity.status(HttpStatus.OK).body(userService.login()); + } + @PostMapping @ToException @Operation(summary = "회원가입", description = "회원가입 할 때 사용하는 API") diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index 240d341..ab57d40 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -6,10 +6,20 @@ import com.ssafy.springbootapi.domain.user.dto.*; import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; +import com.ssafy.springbootapi.global.auth.SecurityUser; +import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToeknRepository; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.time.Duration; + /* * TODO:: 사용자 정의 exception * - user not found exception @@ -19,12 +29,44 @@ public class UserService { private final UserRepository userRepository; private final UserMapper userMapper; + private final PasswordEncoder passwordEncoder; + private final TokenProvider tokenProvider; + private final RefreshToeknRepository refreshToeknRepository; + + public UserLoginResponse login(){ + System.out.println("In UserService Login"); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if(authentication.isAuthenticated()){ + String email = (String) authentication.getPrincipal(); + + User user = userRepository.findByEmail(email) + .orElseThrow(()->new UserNotFoundException(email+" 사용자 없음")); + + String accessToken = tokenProvider.generateToken(user, Duration.ofMinutes(1L)); + String refreshToken = tokenProvider.generateToken(user, Duration.ofMinutes(30L)); + System.out.println("accessToken "+accessToken); + refreshToeknRepository.save(RefreshToken.builder() + .userId(user.getId()) + .refreshToken(refreshToken) + .build()); + + return new UserLoginResponse(accessToken, refreshToken); + } + return null; + } public UserSignUpResponse signUp(UserSignUpRequest requestDTO){ if(userRepository.findByEmail(requestDTO.getEmail()).isPresent()){ throw new UserDuplicatedException(requestDTO.getEmail()+"이미 존재하는 사용자"); } - User user = userRepository.save(requestDTO.toEntity()); + User user = userRepository.save( + User.builder() + .email(requestDTO.getEmail()) + .password(passwordEncoder.encode(requestDTO.getPassword())) // password 암호화 + .name(requestDTO.getName()) + .addresses(null) + .build()); + return UserSignUpResponse.builder() .email(user.getEmail()) .name(user.getName()) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginRequest.java new file mode 100644 index 0000000..16f0782 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginRequest.java @@ -0,0 +1,28 @@ +package com.ssafy.springbootapi.domain.user.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class UserLoginRequest { + + @NotBlank(message = "email is blank!") + public String email; + + @NotBlank(message = "password is blank!") + public String password; + + @Override + public String toString() { + return "UserSignUpRequestDTO{" + + "email='" + email + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java new file mode 100644 index 0000000..08a14ab --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java @@ -0,0 +1,11 @@ +package com.ssafy.springbootapi.domain.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class UserLoginResponse { + String accessToken; + String refreshToken; +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserSignUpRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserSignUpRequest.java index 2289cc7..d62e68c 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserSignUpRequest.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserSignUpRequest.java @@ -4,10 +4,13 @@ import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; @AllArgsConstructor @Getter public class UserSignUpRequest { + @NotBlank(message = "email is blank!") public String email; @@ -17,15 +20,6 @@ public class UserSignUpRequest { @NotBlank(message = "name is blank!") public String name; - public User toEntity(){ - return User.builder() - .email(email) - .password(password) - .name(name) - .addresses(null) - .build(); - } - @Override public String toString() { return "UserSignUpRequestDTO{" + From 89340d03d2d823d48c2821e5a56d5d998d19c898 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:11:43 +0000 Subject: [PATCH 21/52] =?UTF-8?q?=E2=9C=A8Feat:=20Authentication=20Control?= =?UTF-8?q?ler=20-=20login=20-=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/AuthController.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java new file mode 100644 index 0000000..04f7226 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java @@ -0,0 +1,34 @@ +package com.ssafy.springbootapi.global.auth; + +import com.ssafy.springbootapi.domain.user.dto.UserLoginRequest; +import com.ssafy.springbootapi.domain.user.dto.UserLoginResponse; +import com.ssafy.springbootapi.global.aop.annotation.ToException; +import com.ssafy.springbootapi.global.auth.dto.TokenRequest; +import com.ssafy.springbootapi.global.auth.dto.TokenResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("/api/v1/auth") +@ControllerAdvice +@RequiredArgsConstructor +@RestController +public class AuthController { + private final AuthService authService; + + @PostMapping("/login") + @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") + public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @PostMapping("/token") + @Operation(summary = "토큰 재발급", description = "토큰 재발급 API") + public ResponseEntity token(@RequestBody TokenRequest refreshToken) { + String accessToken = authService.provideNewAccessToken(refreshToken.getRefreshToken()); + return ResponseEntity.ok(TokenResponse.builder().accessToken(accessToken).build()); + } + +} From 348b2eae411d5c59d5e641423b1c71592d9bd7a6 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:12:04 +0000 Subject: [PATCH 22/52] =?UTF-8?q?=E2=9C=A8Feat:=20Authentication=20Service?= =?UTF-8?q?=20-=20provide=20access=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/AuthService.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java new file mode 100644 index 0000000..9079f8b --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java @@ -0,0 +1,42 @@ +package com.ssafy.springbootapi.global.auth; + +import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; +import com.ssafy.springbootapi.global.error.InvalidTokenException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@RequiredArgsConstructor +@Service +public class AuthService { + private final RefreshTokenRepository refreshTokenRepository; + private final TokenProvider tokenProvider; + + public String provideNewAccessToken(String refreshToken){ + // refresh 토큰이 유효하다면 새 access 토큰 발급 + // refresh 토큰이 유효하지 않다면 exception +// SecurityContext context = SecurityContextHolder.getContext(); + + String accessToken = ""; + if (tokenProvider.validToken(refreshToken)){ + System.out.println("valid refresh token"); + refreshTokenRepository.findByRefreshToken(refreshToken).orElseThrow(()-> + new InvalidTokenException("invalid refresh token!") + ); + System.out.println("existing refresh token"); + Authentication authentication = tokenProvider.getAuthentication(refreshToken); + accessToken = tokenProvider.generateToken(authentication.getName(), Duration.ofMinutes(1L)); + System.out.println("new access token "+accessToken); + }else { + System.out.println("InValid refresh token"); + throw new InvalidTokenException("invalid refresh token!"); + } + return accessToken; + } + +} From 9be067247435109052708839e46cbf7ae6ce832a Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:14:48 +0000 Subject: [PATCH 23/52] :art: Rename: Authentication package structure --- .../auth/{ => SecurityUser}/SecurityUser.java | 6 +++- .../{ => SecurityUser}/UserDetailService.java | 3 +- .../CustomBCryptPasswordEncoder.java | 2 +- .../JsonUserAuthenticationProvider.java} | 5 +-- ...JsonUserAuthenticationSuccessHandler.java} | 31 ++++++++----------- ...nUsernamePasswordAuthenticationFilter.java | 4 +-- ...itory.java => RefreshTokenRepository.java} | 4 +-- 7 files changed, 26 insertions(+), 29 deletions(-) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{ => SecurityUser}/SecurityUser.java (91%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{ => SecurityUser}/UserDetailService.java (87%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{ => jsonAuthentication}/CustomBCryptPasswordEncoder.java (93%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{UserAuthenticationProvider.java => jsonAuthentication/JsonUserAuthenticationProvider.java} (88%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{MyAuthenticationSuccessHandler.java => jsonAuthentication/JsonUserAuthenticationSuccessHandler.java} (59%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/{ => jsonAuthentication}/JsonUsernamePasswordAuthenticationFilter.java (94%) rename spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/{RefreshToeknRepository.java => RefreshTokenRepository.java} (72%) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java similarity index 91% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java index 4b6291c..b813d77 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java @@ -1,4 +1,4 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.SecurityUser; import com.ssafy.springbootapi.domain.user.domain.User; import org.springframework.security.core.GrantedAuthority; @@ -15,6 +15,10 @@ public SecurityUser(User user) { this.user = user; } + public Long getUserId() { + return user.getId(); + } + // 권한 반환 @Override public Collection getAuthorities() { diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java similarity index 87% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java index bd4230a..2a142a2 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserDetailService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java @@ -1,8 +1,7 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.SecurityUser; import com.ssafy.springbootapi.domain.user.dao.UserRepository; import com.ssafy.springbootapi.domain.user.domain.User; -import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java similarity index 93% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java index 63119aa..b908865 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/CustomBCryptPasswordEncoder.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java @@ -1,4 +1,4 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.jsonAuthentication; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java similarity index 88% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java index aeecaff..effd53c 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/UserAuthenticationProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java @@ -1,5 +1,6 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.jsonAuthentication; +import com.ssafy.springbootapi.global.auth.SecurityUser.UserDetailService; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -12,7 +13,7 @@ @RequiredArgsConstructor @Component -public class UserAuthenticationProvider implements AuthenticationProvider { +public class JsonUserAuthenticationProvider implements AuthenticationProvider { private final UserDetailService userDetailService; private final PasswordEncoder passwordEncoder; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java similarity index 59% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java index 9009cb5..2230d53 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/MyAuthenticationSuccessHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java @@ -1,19 +1,14 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.jsonAuthentication; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ssafy.springbootapi.domain.user.application.UserService; -import com.ssafy.springbootapi.domain.user.dao.UserRepository; -import com.ssafy.springbootapi.domain.user.domain.User; import com.ssafy.springbootapi.domain.user.dto.UserLoginResponse; import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; -import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToeknRepository; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; 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.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -21,16 +16,14 @@ import java.time.Duration; @Component -public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { +public class JsonUserAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final TokenProvider tokenProvider; - private final RefreshToeknRepository refreshToeknRepository; - private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; - public MyAuthenticationSuccessHandler(TokenProvider tokenProvider, RefreshToeknRepository refreshToeknRepository, UserRepository userRepository) { + public JsonUserAuthenticationSuccessHandler(TokenProvider tokenProvider, RefreshTokenRepository refreshTokenRepository) { this.tokenProvider = tokenProvider; - this.refreshToeknRepository = refreshToeknRepository; - this.userRepository = userRepository; + this.refreshTokenRepository = refreshTokenRepository; } @Override @@ -41,12 +34,14 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 여기에서 UserService의 login을 실행 - User user = userRepository.findByEmail(authentication.getName()).orElseThrow(); - String accessToken = tokenProvider.generateToken(user, Duration.ofMinutes(1L)); - String refreshToken = tokenProvider.generateToken(user, Duration.ofMinutes(30L)); +// User user = userRepository.findByEmail(authentication.getName()).orElseThrow(); + String email = authentication.getName(); + + String accessToken = tokenProvider.generateToken(email, Duration.ofMinutes(1L)); + String refreshToken = tokenProvider.generateToken(email, Duration.ofMinutes(30L)); System.out.println("accessToken "+accessToken); - refreshToeknRepository.save(RefreshToken.builder() - .userId(user.getId()) + refreshTokenRepository.save(RefreshToken.builder() + .email(email) .refreshToken(refreshToken) .build()); response.setContentType("application/json"); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java similarity index 94% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java index 2498914..70eb027 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/JsonUsernamePasswordAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java @@ -1,11 +1,9 @@ -package com.ssafy.springbootapi.global.auth; +package com.ssafy.springbootapi.global.auth.jsonAuthentication; import com.fasterxml.jackson.databind.ObjectMapper; import com.ssafy.springbootapi.domain.user.dto.UserLoginRequest; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java similarity index 72% rename from spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java rename to spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java index e0d51d6..3da5f2e 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToeknRepository.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java @@ -6,7 +6,7 @@ import java.util.Optional; @Repository -public interface RefreshToeknRepository extends JpaRepository { - Optional findByUserId(Long userId); +public interface RefreshTokenRepository extends JpaRepository { + Optional findByEmail(String email); Optional findByRefreshToken(String refreshToken); } From 7d8c6c07afc8b85a174b7bf9c9844260e46e70ae Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:15:43 +0000 Subject: [PATCH 24/52] :goal_net: Feat: InvalidTokenException --- .../springbootapi/global/error/GlobalExceptionHandler.java | 5 +++++ .../springbootapi/global/error/InvalidTokenException.java | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/InvalidTokenException.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java index a5b5de7..1d35660 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java @@ -13,4 +13,9 @@ public class GlobalExceptionHandler { // Domain별 Exception 핸들러 // public ResponseEntity handleNotFoundProductException(NotFoundProductException ex) { // return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); // } + + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleInvalidException(InvalidTokenException ex) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/InvalidTokenException.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/InvalidTokenException.java new file mode 100644 index 0000000..aeaa9cc --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/InvalidTokenException.java @@ -0,0 +1,7 @@ +package com.ssafy.springbootapi.global.error; + +public class InvalidTokenException extends RuntimeException{ + public InvalidTokenException(String s) { + super(s); + } +} From 603126b8ade0b23187068099ff984f889b112fef Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:18:18 +0000 Subject: [PATCH 25/52] :recycle: Style: remove print, unused dependency --- .../global/auth/jwt/JwtAuthenticationFilter.java | 4 +--- .../global/auth/jwt/refreshToken/RefreshToken.java | 14 ++++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java index d1c656f..5316b66 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java @@ -23,7 +23,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - System.out.println("여기 들어오나?"); // 요청 헤더의 Authorization 키의 값 조회 String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); // 가져온 값에서 접두사 제거 @@ -48,11 +47,10 @@ private String getAccessToken(String authorizationHeader){ @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String path = request.getRequestURI(); - System.out.println(request.getMethod() + " "+ request.getRequestURI()); if(request.getMethod().equals("POST")&&path.equals("/api/v1/users")){ return true; } - return path.equals("/api/v1/users/login") + return path.startsWith("/api/v1/auth") || path.startsWith("/swagger-ui") || path.startsWith("/api-docs"); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java index f2f0394..68a5dc9 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java @@ -1,9 +1,7 @@ package com.ssafy.springbootapi.global.auth.jwt.refreshToken; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import com.ssafy.springbootapi.domain.user.domain.User; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -20,14 +18,14 @@ public class RefreshToken { @Column(name = "id", updatable = false) private Long id; - @Column(name = "user_id", nullable = false, unique = true) - private Long userId; + @Column(name = "email", nullable = false, unique = true) + private String email; @Column(name = "refresh_token", nullable = false) private String refreshToken; - public RefreshToken(Long userId, String refreshToken){ - this.userId = userId; + public RefreshToken(String email, String refreshToken){ + this.email = email; this.refreshToken = refreshToken; } From 7677e0f77e1ac865d23278967e371a1585a449ac Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:20:38 +0000 Subject: [PATCH 26/52] :recycle: Style: remove print, unused dependency --- .../springbootapi/global/auth/AuthService.java | 8 ++++---- .../JsonUsernamePasswordAuthenticationFilter.java | 2 +- .../global/auth/jwt/TokenProvider.java | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java index 9079f8b..775a707 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java @@ -24,16 +24,16 @@ public String provideNewAccessToken(String refreshToken){ String accessToken = ""; if (tokenProvider.validToken(refreshToken)){ - System.out.println("valid refresh token"); +// System.out.println("valid refresh token"); refreshTokenRepository.findByRefreshToken(refreshToken).orElseThrow(()-> new InvalidTokenException("invalid refresh token!") ); - System.out.println("existing refresh token"); +// System.out.println("existing refresh token"); Authentication authentication = tokenProvider.getAuthentication(refreshToken); accessToken = tokenProvider.generateToken(authentication.getName(), Duration.ofMinutes(1L)); - System.out.println("new access token "+accessToken); +// System.out.println("new access token "+accessToken); }else { - System.out.println("InValid refresh token"); +// System.out.println("InValid refresh token"); throw new InvalidTokenException("invalid refresh token!"); } return accessToken; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java index 70eb027..a8eb26d 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java @@ -25,7 +25,7 @@ public JsonUsernamePasswordAuthenticationFilter(AuthenticationManager authentica @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - System.out.println("In JsonUsernamePasswordAuthenticationFilter"); +// System.out.println("In JsonUsernamePasswordAuthenticationFilter"); if ("application/json".equals(request.getContentType())) { try { // JSON 요청에서 사용자 이름과 비밀번호를 읽어옵니다. diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java index e7ef5f5..a268c4b 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java @@ -1,8 +1,7 @@ package com.ssafy.springbootapi.global.auth.jwt; import com.ssafy.springbootapi.domain.user.domain.User; -import com.ssafy.springbootapi.global.auth.SecurityUser; -import com.ssafy.springbootapi.global.auth.UserDetailService; +import com.ssafy.springbootapi.global.auth.SecurityUser.UserDetailService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwts; @@ -22,22 +21,23 @@ public class TokenProvider { private final JwtProperties jwtProperties; private final UserDetailService userDetailService; - public String generateToken(User user, Duration expiredAt) { + public String generateToken(String email, Duration expiredAt) { Date now = new Date(); - return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),user); + return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),email); } // jwt token 생성 - private String makeToken(Date expiry, User user){ + private String makeToken(Date expiry, String email){ Date now = new Date(); + UserDetails user = userDetailService.loadUserByUsername(email); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .setIssuer(jwtProperties.getIssuer()) .setIssuedAt(now) // iat: 발급시간 .setExpiration(expiry) // exp: 만료시간 - .setSubject(user.getEmail()) // sub: 유저 이메일 - .claim("id", user.getId()) // 클레임 id + .setSubject(user.getUsername()) // sub: 유저 이메일 +// .claim("id", user.get()) // 클레임 id .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()) .compact(); } From e795e40b310196eba88e146b4520915da27a0e28 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:21:24 +0000 Subject: [PATCH 27/52] :coffin: Refactor: move login logic User to Auth --- .../domain/user/api/UserController.java | 7 ----- .../domain/user/application/UserService.java | 27 ------------------- 2 files changed, 34 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java index a96b8a2..aea198b 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java @@ -19,13 +19,6 @@ public class UserController { private final UserService userService; - @PostMapping("/login") - @ToException - @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") - public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ - return ResponseEntity.status(HttpStatus.OK).body(userService.login()); - } - @PostMapping @ToException @Operation(summary = "회원가입", description = "회원가입 할 때 사용하는 API") diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index ab57d40..ac361f5 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -6,15 +6,12 @@ import com.ssafy.springbootapi.domain.user.dto.*; import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; -import com.ssafy.springbootapi.global.auth.SecurityUser; import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; -import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToeknRepository; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -30,30 +27,6 @@ public class UserService { private final UserRepository userRepository; private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; - private final TokenProvider tokenProvider; - private final RefreshToeknRepository refreshToeknRepository; - - public UserLoginResponse login(){ - System.out.println("In UserService Login"); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if(authentication.isAuthenticated()){ - String email = (String) authentication.getPrincipal(); - - User user = userRepository.findByEmail(email) - .orElseThrow(()->new UserNotFoundException(email+" 사용자 없음")); - - String accessToken = tokenProvider.generateToken(user, Duration.ofMinutes(1L)); - String refreshToken = tokenProvider.generateToken(user, Duration.ofMinutes(30L)); - System.out.println("accessToken "+accessToken); - refreshToeknRepository.save(RefreshToken.builder() - .userId(user.getId()) - .refreshToken(refreshToken) - .build()); - - return new UserLoginResponse(accessToken, refreshToken); - } - return null; - } public UserSignUpResponse signUp(UserSignUpRequest requestDTO){ if(userRepository.findByEmail(requestDTO.getEmail()).isPresent()){ From 19c93fa0af8ebb22247aa4fe2f25578bb210cf2e Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:21:46 +0000 Subject: [PATCH 28/52] :recycle: Refactor: User table name --- .../java/com/ssafy/springbootapi/domain/user/domain/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index cc9275d..cd8d101 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -13,7 +13,7 @@ * TODO:: id to uuid */ @Entity -@Table(name = "users") +@Table(name = "user") @NoArgsConstructor @AllArgsConstructor @Getter From 919cfd94d7e5271d5a1be8bd3a1ec932ff8eecc1 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:22:12 +0000 Subject: [PATCH 29/52] :sparkles: Feat: Auth token DTO --- .../springbootapi/global/auth/dto/TokenRequest.java | 10 ++++++++++ .../springbootapi/global/auth/dto/TokenResponse.java | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java new file mode 100644 index 0000000..6b68d77 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java @@ -0,0 +1,10 @@ +package com.ssafy.springbootapi.global.auth.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TokenRequest { + private String refreshToken; +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java new file mode 100644 index 0000000..f0b4b00 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java @@ -0,0 +1,12 @@ +package com.ssafy.springbootapi.global.auth.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class TokenResponse { + private String accessToken; +} From 21f0e6732315f238b37f8f824806b15a9fafd3f0 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:22:41 +0000 Subject: [PATCH 30/52] :wrench: Feat: security configuration --- .../global/config/WebSecurityConfig.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java index 7982521..2fde70a 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -1,25 +1,19 @@ package com.ssafy.springbootapi.global.config; -import com.ssafy.springbootapi.domain.user.dao.UserRepository; -import com.ssafy.springbootapi.global.auth.JsonUsernamePasswordAuthenticationFilter; -import com.ssafy.springbootapi.global.auth.MyAuthenticationSuccessHandler; -import com.ssafy.springbootapi.global.auth.UserAuthenticationProvider; -import com.ssafy.springbootapi.global.auth.UserDetailService; +import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUsernamePasswordAuthenticationFilter; +import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationSuccessHandler; +import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationProvider; import com.ssafy.springbootapi.global.auth.jwt.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -27,10 +21,10 @@ @EnableWebSecurity @RequiredArgsConstructor public class WebSecurityConfig { - private final UserAuthenticationProvider userAuthenticationProvider; + private final JsonUserAuthenticationProvider jsonUserAuthenticationProvider; private final JwtAuthenticationFilter jwtAuthenticationFilter; private final AuthenticationConfiguration authenticationConfiguration; - private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; + private final JsonUserAuthenticationSuccessHandler jsonUserAuthenticationSuccessHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -46,8 +40,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // HTTP 요청에 대한 접근 제어 http.authorizeHttpRequests(auth -> auth - .requestMatchers("**").permitAll() - .requestMatchers("/api/v1/login").permitAll() // 로그인 API는 모든 요청을 허용 + .requestMatchers("/swagger-ui/**").permitAll() + .requestMatchers("/api-docs/**").permitAll() + .requestMatchers(HttpMethod.POST,"/api/v1/users").permitAll() + .requestMatchers(HttpMethod.POST,"/api/v1/auth/token").permitAll() +// .requestMatchers("/api/v1/login").permitAll() // 로그인 API는 모든 요청을 허용 .anyRequest().authenticated() // 그 외의 모든 요청은 인증 필요 ); @@ -55,12 +52,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.addFilterBefore(jwtAuthenticationFilter ,UsernamePasswordAuthenticationFilter.class); - http.authenticationProvider(userAuthenticationProvider); + http.authenticationProvider(jsonUserAuthenticationProvider); // UsernamePasswordAuthenticationFilter 추가 JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration)); - filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler); - filter.setFilterProcessesUrl("/api/v1/users/login"); + filter.setAuthenticationSuccessHandler(jsonUserAuthenticationSuccessHandler); + filter.setFilterProcessesUrl("/api/v1/auth/login"); http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class); From accca650002c5d485dc6ab8a7a88989edbf4e343 Mon Sep 17 00:00:00 2001 From: kkh Date: Sun, 28 Apr 2024 06:23:34 +0000 Subject: [PATCH 31/52] remove: .gitkeep --- .../src/main/java/com/ssafy/springbootapi/global/auth/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/.gitkeep diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/.gitkeep b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/.gitkeep deleted file mode 100644 index e69de29..0000000 From a5c3e689734e016766fec3919c3543855c1cb6b3 Mon Sep 17 00:00:00 2001 From: kkh Date: Mon, 29 Apr 2024 12:44:24 +0000 Subject: [PATCH 32/52] =?UTF-8?q?:art:=20Refactor:=20refresh=20token=20?= =?UTF-8?q?=EC=9D=84=20http=20only=20cookie=EC=97=90=20=EB=84=A3=EB=8A=94?= =?UTF-8?q?=EB=8B=A4.=20-=20=EA=B8=B0=EC=A1=B4=20:=20body=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/dto/UserLoginResponse.java | 1 - .../global/auth/AuthController.java | 28 ++++++++++++++++--- .../global/auth/AuthService.java | 5 ---- .../JsonUserAuthenticationSuccessHandler.java | 18 ++++++++---- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java index 08a14ab..2d2ff2e 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserLoginResponse.java @@ -7,5 +7,4 @@ @Getter public class UserLoginResponse { String accessToken; - String refreshToken; } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java index 04f7226..d793866 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java @@ -6,6 +6,8 @@ import com.ssafy.springbootapi.global.auth.dto.TokenRequest; import com.ssafy.springbootapi.global.auth.dto.TokenResponse; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,15 +22,33 @@ public class AuthController { @PostMapping("/login") @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") - public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ + public ResponseEntity login(){ return ResponseEntity.status(HttpStatus.OK).build(); } @PostMapping("/token") @Operation(summary = "토큰 재발급", description = "토큰 재발급 API") - public ResponseEntity token(@RequestBody TokenRequest refreshToken) { - String accessToken = authService.provideNewAccessToken(refreshToken.getRefreshToken()); - return ResponseEntity.ok(TokenResponse.builder().accessToken(accessToken).build()); + public ResponseEntity token(HttpServletRequest request) { + String refreshToken = null; + // 요청에서 쿠키 배열을 가져옵니다. + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + // 쿠키 배열을 순회하면서 refreshToken 쿠키를 찾습니다. + for (Cookie cookie : cookies) { + if ("refreshToken".equals(cookie.getName())) { + refreshToken = cookie.getValue(); + break; + } + } + } + // refreshToken이 있는 경우 새로운 액세스 토큰을 발급받습니다. + if (refreshToken != null) { + String accessToken = authService.provideNewAccessToken(refreshToken); + return ResponseEntity.ok(TokenResponse.builder().accessToken(accessToken).build()); + } else { + // refreshToken이 없는 경우 적절한 예외 처리를 합니다. + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java index 775a707..c23fe8c 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java @@ -20,20 +20,15 @@ public class AuthService { public String provideNewAccessToken(String refreshToken){ // refresh 토큰이 유효하다면 새 access 토큰 발급 // refresh 토큰이 유효하지 않다면 exception -// SecurityContext context = SecurityContextHolder.getContext(); String accessToken = ""; if (tokenProvider.validToken(refreshToken)){ -// System.out.println("valid refresh token"); refreshTokenRepository.findByRefreshToken(refreshToken).orElseThrow(()-> new InvalidTokenException("invalid refresh token!") ); -// System.out.println("existing refresh token"); Authentication authentication = tokenProvider.getAuthentication(refreshToken); accessToken = tokenProvider.generateToken(authentication.getName(), Duration.ofMinutes(1L)); -// System.out.println("new access token "+accessToken); }else { -// System.out.println("InValid refresh token"); throw new InvalidTokenException("invalid refresh token!"); } return accessToken; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java index 2230d53..5321a53 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java @@ -6,6 +6,7 @@ import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; @@ -34,18 +35,25 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 여기에서 UserService의 login을 실행 -// User user = userRepository.findByEmail(authentication.getName()).orElseThrow(); String email = authentication.getName(); - String accessToken = tokenProvider.generateToken(email, Duration.ofMinutes(1L)); - String refreshToken = tokenProvider.generateToken(email, Duration.ofMinutes(30L)); - System.out.println("accessToken "+accessToken); + String accessToken = tokenProvider.generateToken(email, Duration.ofMinutes(15L)); + String refreshToken = tokenProvider.generateToken(email, Duration.ofMinutes(60L)); refreshTokenRepository.save(RefreshToken.builder() .email(email) .refreshToken(refreshToken) .build()); + + // Refresh Token HttpOnly Cookie에 저장 + Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken); + refreshTokenCookie.setHttpOnly(true); +// refreshTokenCookie.setSecure(true); // HTTPS를 사용하는 경우에만 보낼 수 있도록 설정 + refreshTokenCookie.setPath("/api/v1/auth/token"); // 토큰 재발급시에만 쿠키를 사용할 수 있다. + refreshTokenCookie.setMaxAge(60 * 60); // 유효 기간 설정 + + response.addCookie(refreshTokenCookie); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); - new ObjectMapper().writeValue(response.getWriter(),new UserLoginResponse(accessToken,refreshToken)); + new ObjectMapper().writeValue(response.getWriter(),new UserLoginResponse(accessToken)); } } From 17a93ed90005500ba2467a350fdc5e26b2fad3c1 Mon Sep 17 00:00:00 2001 From: kkh Date: Thu, 2 May 2024 10:42:48 +0000 Subject: [PATCH 33/52] :white_check_mark: Test: UserService Unit Test --- .../domain/user/UserServiceTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java index a10832e..5c7508a 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java @@ -7,6 +7,7 @@ import com.ssafy.springbootapi.domain.user.dto.*; import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; +import com.ssafy.springbootapi.global.auth.jsonAuthentication.CustomBCryptPasswordEncoder; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -19,6 +20,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; @@ -34,6 +36,9 @@ public class UserServiceTest { @Spy private final UserMapper userMapper = Mappers.getMapper(UserMapper.class); + @Mock + PasswordEncoder passwordEncoder; + @InjectMocks UserService userService; @@ -45,18 +50,27 @@ public class UserServiceTest { // given. UserSignUpRequest userSignUpRequest = new UserSignUpRequest("kkho9654@naver.com","1234","kkh"); - User userToSave = userSignUpRequest.toEntity(); + User userToSave = User.builder() + .email("kkho9654@naver.com") + .password("encodedPassword") + .name("kkh").build(); given(userRepository.findByEmail(anyString())) .willReturn(Optional.empty()); given(userRepository.save(any(User.class))) .willReturn(userToSave); + + // Mock the password encoding to return a specific encoded value + given(passwordEncoder.encode(userSignUpRequest.getPassword())) + .willReturn("encodedPassword"); + // when UserSignUpResponse userSignUpResponse = userService.signUp(userSignUpRequest); // then Assertions.assertThat(userSignUpResponse.getEmail()) .isEqualTo("kkho9654@naver.com"); + verify(passwordEncoder).encode("1234"); // Ensure password encoding is called } @DisplayName("유저 회원가입 중복사용자 예외테스트") From e2cb3f993c864ae87d87bc84c152a417a2d80310 Mon Sep 17 00:00:00 2001 From: kkh Date: Thu, 2 May 2024 11:27:16 +0000 Subject: [PATCH 34/52] :bug: Fix: Auth login request --- .../com/ssafy/springbootapi/global/auth/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java index d793866..19ac4fd 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java @@ -22,7 +22,7 @@ public class AuthController { @PostMapping("/login") @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") - public ResponseEntity login(){ + public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ return ResponseEntity.status(HttpStatus.OK).build(); } From 3d9c29561d8f3f544e2f4f771cdc2abed2895e08 Mon Sep 17 00:00:00 2001 From: kkh Date: Thu, 2 May 2024 11:27:45 +0000 Subject: [PATCH 35/52] :sparkles: Feat: swagger authentication --- .../global/config/SwaggerConfig.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/SwaggerConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/SwaggerConfig.java index 5279947..70f971a 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/SwaggerConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/SwaggerConfig.java @@ -2,6 +2,10 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import lombok.RequiredArgsConstructor; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; @@ -24,4 +28,18 @@ public GroupedOpenApi ssafyApi() { .pathsToMatch(paths) // 그룹에 속하는 경로 패턴을 지정한다. .build(); } + @Bean + public OpenAPI api() { + SecurityScheme apiKey = new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList("Bearer Token"); + + return new OpenAPI() + .components(new Components().addSecuritySchemes("Bearer Token", apiKey)) + .addSecurityItem(securityRequirement); + } } \ No newline at end of file From a25d7e77824efa1e3fa1edd2a4bcf3a83afc146d Mon Sep 17 00:00:00 2001 From: kkh Date: Thu, 2 May 2024 11:42:56 +0000 Subject: [PATCH 36/52] =?UTF-8?q?:recycle:=20Refactor:=20user,=20address?= =?UTF-8?q?=20entity=20-=20=EC=9C=A0=EC=A0=80,=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=EA=B0=84=201=EB=8C=80N=EA=B4=80=EA=B3=84=EC=97=90=20Lazy=20fet?= =?UTF-8?q?ch=20=EC=B6=94=EA=B0=80=20-=20=EC=9C=A0=EC=A0=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B6=88=EB=9F=AC=EC=99=94=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=EC=9D=98=20=EC=9C=A0=EC=A0=80=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EA=B9=8C=EC=A7=80=20=EB=82=98=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20-=20=EC=9C=A0=EC=A0=80=20createdAt?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/domain/Address.java | 4 +++- .../domain/user/domain/User.java | 24 +++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/Address.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/Address.java index cb6e0a1..244fb89 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/Address.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/Address.java @@ -1,5 +1,6 @@ package com.ssafy.springbootapi.domain.user.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -26,8 +27,9 @@ public class Address { @Enumerated(EnumType.STRING) private IsDefault isDefault; - @ManyToOne // optional true => nullable + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id") + @JsonIgnore private User user; } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index cd8d101..89b2d39 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -1,11 +1,10 @@ package com.ssafy.springbootapi.domain.user.domain; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import java.time.LocalDateTime; import java.util.List; @@ -18,6 +17,7 @@ @AllArgsConstructor @Getter @Builder +@ToString public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -33,17 +33,11 @@ public class User { @Column(name = "name", unique = false, nullable = false) private String name; - @OneToMany(mappedBy = "user") + @Column(nullable = false, updatable = false) + @CreationTimestamp + private LocalDateTime createdAt; + + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List
addresses; - @Override - public String toString() { - return "User{" + - "id=" + id + - ", email='" + email + '\'' + - ", password='" + password + '\'' + - ", name='" + name + '\'' + - ", addresses=" + addresses + - '}'; - } } From 94b438212af195f6fec8929cd5e6c27289bd0ed1 Mon Sep 17 00:00:00 2001 From: kkh Date: Thu, 2 May 2024 11:43:23 +0000 Subject: [PATCH 37/52] :recycle: Feat: user createdAt --- .../springbootapi/domain/user/application/UserService.java | 3 ++- .../ssafy/springbootapi/domain/user/dto/UserInfoResponse.java | 3 +++ .../springbootapi/domain/user/dto/UserUpdateResponse.java | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index ac361f5..fe95a48 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -53,6 +53,7 @@ public UserInfoResponse getUserInfo(Long id) { return UserInfoResponse.builder() .email(user.getEmail()) .name(user.getName()) + .createdAt(user.getCreatedAt()) .addresses(user.getAddresses()) .build(); } @@ -65,7 +66,7 @@ public UserUpdateResponse updateUserInfo(UserUpdateRequest requestDTO) { userMapper.updateUserFromDto(requestDTO,user); user = userRepository.save(user); - return UserUpdateResponse.builder().email(user.getEmail()).name(user.getName()).build(); + return UserUpdateResponse.builder().email(user.getEmail()).name(user.getName()).createdAt(user.getCreatedAt()).build(); } public void removeUser(Long id) { diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserInfoResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserInfoResponse.java index 6731709..aa0a490 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserInfoResponse.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserInfoResponse.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; +import java.time.LocalDateTime; import java.util.List; @AllArgsConstructor @@ -13,5 +14,7 @@ public class UserInfoResponse { private String email; private String name; + private LocalDateTime createdAt; private List
addresses; + } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateResponse.java index 4115f88..ccc3a32 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateResponse.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateResponse.java @@ -4,10 +4,13 @@ import lombok.Builder; import lombok.Getter; +import java.time.LocalDateTime; + @AllArgsConstructor @Getter @Builder public class UserUpdateResponse { private String email; private String name; + private LocalDateTime createdAt; } From 85f54082e61c2374e0a0b546616e3a609d427634 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:16:51 +0000 Subject: [PATCH 38/52] :art: Feat: user authority via user type --- .../springbootapi/domain/user/domain/User.java | 4 ++++ .../springbootapi/domain/user/domain/UserType.java | 14 ++++++++++++++ .../global/auth/SecurityUser/SecurityUser.java | 2 +- .../global/config/WebSecurityConfig.java | 3 +++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/UserType.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index 89b2d39..bc821a2 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -33,6 +33,10 @@ public class User { @Column(name = "name", unique = false, nullable = false) private String name; + @Enumerated(EnumType.STRING) + @Column(name = "type", columnDefinition = "varchar(255) default 'BUYER'") + private UserType type = UserType.BUYER; + @Column(nullable = false, updatable = false) @CreationTimestamp private LocalDateTime createdAt; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/UserType.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/UserType.java new file mode 100644 index 0000000..8ab0a9f --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/UserType.java @@ -0,0 +1,14 @@ +package com.ssafy.springbootapi.domain.user.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum UserType { + ADMIN("ADMIN"), + BUYER("BUYER"), + SELLER("SELLER"); + + private String description; +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java index b813d77..263a721 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java @@ -22,7 +22,7 @@ public Long getUserId() { // 권한 반환 @Override public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("user")); + return List.of(new SimpleGrantedAuthority("ROLE_"+user.getType().toString())); } @Override diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java index 2fde70a..b283879 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -1,5 +1,6 @@ package com.ssafy.springbootapi.global.config; +import com.ssafy.springbootapi.domain.user.domain.UserType; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUsernamePasswordAuthenticationFilter; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationSuccessHandler; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationProvider; @@ -9,6 +10,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authorization.AuthenticatedAuthorizationManager; 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; @@ -45,6 +47,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.POST,"/api/v1/users").permitAll() .requestMatchers(HttpMethod.POST,"/api/v1/auth/token").permitAll() // .requestMatchers("/api/v1/login").permitAll() // 로그인 API는 모든 요청을 허용 + .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() // 그 외의 모든 요청은 인증 필요 ); From 1491c1c55d304118ca433c6cd8775b81e1b4252b Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:17:39 +0000 Subject: [PATCH 39/52] :recycle: Refactor: user update controller, service method include id --- .../springbootapi/domain/user/api/UserController.java | 6 +++--- .../domain/user/application/UserService.java | 4 ++-- .../springbootapi/domain/user/dto/UserUpdateRequest.java | 2 -- .../ssafy/springbootapi/domain/user/UserServiceTest.java | 9 +++++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java index aea198b..68fcecd 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java @@ -33,11 +33,11 @@ public ResponseEntity getInfo(@PathVariable Long id){ return ResponseEntity.status(HttpStatus.ACCEPTED).body(userService.getUserInfo(id)); } - @PatchMapping + @PatchMapping("/{id}") @ToException @Operation(summary = "회원 정보 수정", description = "회원 정보 수정 할 때 사용하는 API") - public ResponseEntity update(@RequestBody UserUpdateRequest userUpdateRequest){ - return ResponseEntity.status(HttpStatus.OK).body(userService.updateUserInfo(userUpdateRequest)); + public ResponseEntity update(@PathVariable(name = "id", required = true) Long id, @RequestBody UserUpdateRequest userUpdateRequest){ + return ResponseEntity.status(HttpStatus.OK).body(userService.updateUserInfo(id, userUpdateRequest)); } @DeleteMapping("/{id}") diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index fe95a48..548b4b2 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -59,8 +59,8 @@ public UserInfoResponse getUserInfo(Long id) { } @Transactional - public UserUpdateResponse updateUserInfo(UserUpdateRequest requestDTO) { - User user = userRepository.findById(requestDTO.getId()) + public UserUpdateResponse updateUserInfo(Long id, UserUpdateRequest requestDTO) { + User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(requestDTO.getEmail()+" 사용자 없음")); userMapper.updateUserFromDto(requestDTO,user); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java index 572c16e..5796282 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java @@ -7,8 +7,6 @@ @AllArgsConstructor @Getter public class UserUpdateRequest { - @NotBlank(message = "id is blank!") - public Long id; public String email; public String password; public String name; diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java index 5c7508a..06a48f6 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java @@ -136,8 +136,9 @@ public class UserServiceTest { @Test void 유저수정성공테스트() { // given + Long id = 1L; UserUpdateRequest requestDTO - = new UserUpdateRequest(1L,"kkho9654@naver2.com","1112","3333"); + = new UserUpdateRequest("kkho9654@naver2.com","1112","3333"); User user = User.builder() .id(1L) .email("kkho9654@naver.com") @@ -153,7 +154,7 @@ public class UserServiceTest { .willReturn(updatedUser); // when - UserUpdateResponse responseDTO = userService.updateUserInfo(requestDTO); + UserUpdateResponse responseDTO = userService.updateUserInfo(id,requestDTO); // then Assertions.assertThat(responseDTO.getEmail()) @@ -167,12 +168,12 @@ public class UserServiceTest { // given UserMapper userMapper = mock(UserMapper.class); Long id = 1L; - UserUpdateRequest requestDTO = new UserUpdateRequest(1L,"test","test","test"); + UserUpdateRequest requestDTO = new UserUpdateRequest("test","test","test"); given(userRepository.findById(id)) .willReturn(Optional.empty()); // when - Assertions.assertThatThrownBy(()->userService.updateUserInfo(requestDTO)) + Assertions.assertThatThrownBy(()->userService.updateUserInfo(id,requestDTO)) .isInstanceOf(UserNotFoundException.class); // then From 85fd6e4e2fb50689f76907b3eaf0bcd756f84b8d Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:18:31 +0000 Subject: [PATCH 40/52] :mute: Delete: println --- .../auth/jsonAuthentication/JsonUserAuthenticationProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java index effd53c..4e3e074 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java @@ -21,7 +21,6 @@ public class JsonUserAuthenticationProvider implements AuthenticationProvider { public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); - System.out.println("username in auth provider:"+username); UserDetails u = userDetailService.loadUserByUsername(username); if (passwordEncoder.matches(password, u.getPassword())) { From 4d9a26153520a0a1c0c1c75c32aaad74869b4899 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:43:59 +0000 Subject: [PATCH 41/52] =?UTF-8?q?:bug:=20Fix:=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EC=9D=84=20=EB=95=8C=20default=20buyer=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/SecurityUser/SecurityUser.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java index 263a721..c874539 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java @@ -1,6 +1,7 @@ package com.ssafy.springbootapi.global.auth.SecurityUser; import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.domain.user.domain.UserType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -22,7 +23,13 @@ public Long getUserId() { // 권한 반환 @Override public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_"+user.getType().toString())); + String type = null; + if(user.getType()==null){ + type = UserType.BUYER.toString(); + }else{ + type = user.getType().toString(); + } + return List.of(new SimpleGrantedAuthority("ROLE_"+type)); } @Override From c723ed19806ac178176bff930a82dc04af25208c Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:44:14 +0000 Subject: [PATCH 42/52] =?UTF-8?q?:bug:=20Fix:=20User=20mapper=20=EB=A5=BC?= =?UTF-8?q?=20=EC=9C=84=ED=95=9C=20setter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ssafy/springbootapi/domain/user/domain/User.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index bc821a2..a56ecfa 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -16,6 +16,7 @@ @NoArgsConstructor @AllArgsConstructor @Getter +@Setter @Builder @ToString public class User { From 16a4681adb4b1bdc8f86ec8d8f3bd6c766445cf7 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:44:43 +0000 Subject: [PATCH 43/52] =?UTF-8?q?:art:=20Fix:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EC=8B=9C=20=EA=B4=80=EB=A0=A8=20refresh?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/application/UserService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index 548b4b2..32008c4 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -8,6 +8,7 @@ import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; @@ -25,6 +26,7 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; @@ -61,7 +63,7 @@ public UserInfoResponse getUserInfo(Long id) { @Transactional public UserUpdateResponse updateUserInfo(Long id, UserUpdateRequest requestDTO) { User user = userRepository.findById(id) - .orElseThrow(()->new UserNotFoundException(requestDTO.getEmail()+" 사용자 없음")); + .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); userMapper.updateUserFromDto(requestDTO,user); user = userRepository.save(user); @@ -69,9 +71,15 @@ public UserUpdateResponse updateUserInfo(Long id, UserUpdateRequest requestDTO) return UserUpdateResponse.builder().email(user.getEmail()).name(user.getName()).createdAt(user.getCreatedAt()).build(); } + @Transactional public void removeUser(Long id) { User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); + + // refresh 토큰 삭제 + refreshTokenRepository.findByEmail(user.getEmail()) + .ifPresent(refreshTokenRepository::delete); + userRepository.delete(user); } From 5e4a39a8b82e051fd544358801e27a1f69964db2 Mon Sep 17 00:00:00 2001 From: kkh Date: Fri, 3 May 2024 05:45:03 +0000 Subject: [PATCH 44/52] =?UTF-8?q?:art:=20Fix:=20=EC=9C=A0=EC=A0=80=20email?= =?UTF-8?q?=20update=20=EB=B6=88=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/dto/UserUpdateRequest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java index 5796282..14a5e64 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java @@ -7,16 +7,6 @@ @AllArgsConstructor @Getter public class UserUpdateRequest { - public String email; public String password; public String name; - - @Override - public String toString() { - return "UserSignUpRequestDTO{" + - "email='" + email + '\'' + - ", password='" + password + '\'' + - ", name='" + name + '\'' + - '}'; - } } From cc737ee723bda4521c80f7c8d31e578878e17535 Mon Sep 17 00:00:00 2001 From: kkh Date: Tue, 7 May 2024 01:37:26 +0000 Subject: [PATCH 45/52] :art: Refactor: user id Long to UUID --- .../domain/user/api/UserController.java | 8 +++++--- .../domain/user/application/UserService.java | 9 +++++---- .../domain/user/dao/UserRepository.java | 3 ++- .../springbootapi/domain/user/domain/User.java | 13 ++++++++++--- .../springbootapi/global/auth/AuthService.java | 3 ++- .../global/auth/SecurityUser/SecurityUser.java | 3 ++- .../auth/SecurityUser/UserDetailService.java | 12 ++++++++++-- .../JsonUserAuthenticationProvider.java | 5 ++++- .../JsonUserAuthenticationSuccessHandler.java | 15 +++++++++++---- .../global/auth/jwt/TokenProvider.java | 16 ++++++++-------- .../auth/jwt/refreshToken/RefreshToken.java | 10 ++++++---- .../jwt/refreshToken/RefreshTokenRepository.java | 3 ++- 12 files changed, 67 insertions(+), 33 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java index 68fcecd..4a089ae 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/api/UserController.java @@ -13,6 +13,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.UUID; + @RestController @RequestMapping("/api/v1/users") @RequiredArgsConstructor @@ -29,21 +31,21 @@ public ResponseEntity signUp(@RequestBody UserSignUpRequest @GetMapping("/{id}") @ToException @Operation(summary = "회원 정보", description = "유저정보 얻을 때 사용하는 API") - public ResponseEntity getInfo(@PathVariable Long id){ + public ResponseEntity getInfo(@PathVariable UUID id){ return ResponseEntity.status(HttpStatus.ACCEPTED).body(userService.getUserInfo(id)); } @PatchMapping("/{id}") @ToException @Operation(summary = "회원 정보 수정", description = "회원 정보 수정 할 때 사용하는 API") - public ResponseEntity update(@PathVariable(name = "id", required = true) Long id, @RequestBody UserUpdateRequest userUpdateRequest){ + public ResponseEntity update(@PathVariable(name = "id", required = true) UUID id, @RequestBody UserUpdateRequest userUpdateRequest){ return ResponseEntity.status(HttpStatus.OK).body(userService.updateUserInfo(id, userUpdateRequest)); } @DeleteMapping("/{id}") @ToException @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 할 때 사용하는 API") - public ResponseEntity remove(@PathVariable Long id){ + public ResponseEntity remove(@PathVariable UUID id){ userService.removeUser(id); return ResponseEntity.status(HttpStatus.OK).build(); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index 32008c4..7811bcd 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -17,6 +17,7 @@ import org.springframework.stereotype.Service; import java.time.Duration; +import java.util.UUID; /* * TODO:: 사용자 정의 exception @@ -49,7 +50,7 @@ public UserSignUpResponse signUp(UserSignUpRequest requestDTO){ } - public UserInfoResponse getUserInfo(Long id) { + public UserInfoResponse getUserInfo(UUID id) { User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); return UserInfoResponse.builder() @@ -61,7 +62,7 @@ public UserInfoResponse getUserInfo(Long id) { } @Transactional - public UserUpdateResponse updateUserInfo(Long id, UserUpdateRequest requestDTO) { + public UserUpdateResponse updateUserInfo(UUID id, UserUpdateRequest requestDTO) { User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); @@ -72,12 +73,12 @@ public UserUpdateResponse updateUserInfo(Long id, UserUpdateRequest requestDTO) } @Transactional - public void removeUser(Long id) { + public void removeUser(UUID id) { User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); // refresh 토큰 삭제 - refreshTokenRepository.findByEmail(user.getEmail()) + refreshTokenRepository.findByUserId(id) .ifPresent(refreshTokenRepository::delete); userRepository.delete(user); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dao/UserRepository.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dao/UserRepository.java index d318fae..b7ad7f1 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dao/UserRepository.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dao/UserRepository.java @@ -5,8 +5,9 @@ import org.springframework.stereotype.Repository; import java.util.Optional; +import java.util.UUID; @Repository -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { Optional findByEmail(String email); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java index a56ecfa..33802c1 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/domain/User.java @@ -3,9 +3,11 @@ import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.GenericGenerator; import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; /* @@ -21,9 +23,14 @@ @ToString public class User { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + @Column(updatable = false, nullable = false) + private UUID id; +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// @Column(name = "id") +// private Long id; @Column(name = "email", unique = true, nullable = false) private String email; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java index c23fe8c..6f3bdda 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import java.time.Duration; +import java.util.UUID; @RequiredArgsConstructor @Service @@ -27,7 +28,7 @@ public String provideNewAccessToken(String refreshToken){ new InvalidTokenException("invalid refresh token!") ); Authentication authentication = tokenProvider.getAuthentication(refreshToken); - accessToken = tokenProvider.generateToken(authentication.getName(), Duration.ofMinutes(1L)); + accessToken = tokenProvider.generateToken(UUID.fromString(authentication.getName()), Duration.ofMinutes(1L)); }else { throw new InvalidTokenException("invalid refresh token!"); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java index c874539..66158fa 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/SecurityUser.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.List; +import java.util.UUID; public class SecurityUser implements UserDetails { private final User user; @@ -16,7 +17,7 @@ public SecurityUser(User user) { this.user = user; } - public Long getUserId() { + public UUID getUserId() { return user.getId(); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java index 2a142a2..52da419 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/SecurityUser/UserDetailService.java @@ -8,15 +8,23 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import java.util.UUID; + @Service @RequiredArgsConstructor public class UserDetailService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String email) { + public UserDetails loadUserByUsername(String uuid) { + User user = userRepository.findById(UUID.fromString(uuid)) + .orElseThrow(()->new UsernameNotFoundException(uuid+" 사용자 없음")); + return new SecurityUser(user); + } + + public UUID loadUserIdByUsername(String email) { User user = userRepository.findByEmail(email) .orElseThrow(()->new UsernameNotFoundException(email+" 사용자 없음")); - return new SecurityUser(user); + return user.getId(); } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java index 4e3e074..18a2710 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java @@ -11,6 +11,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import java.util.UUID; + @RequiredArgsConstructor @Component public class JsonUserAuthenticationProvider implements AuthenticationProvider { @@ -22,7 +24,8 @@ public Authentication authenticate(Authentication authentication) throws Authent String username = authentication.getName(); String password = authentication.getCredentials().toString(); - UserDetails u = userDetailService.loadUserByUsername(username); + UUID id = userDetailService.loadUserIdByUsername(username); + UserDetails u = userDetailService.loadUserByUsername(String.valueOf(id)); if (passwordEncoder.matches(password, u.getPassword())) { // 암호일치하면 필요한 세부정보를 넣은 Authentication 객체를 반환 return new UsernamePasswordAuthenticationToken( diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java index 5321a53..6987f6e 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java @@ -1,5 +1,7 @@ package com.ssafy.springbootapi.global.auth.jsonAuthentication; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.domain.user.domain.User; import com.ssafy.springbootapi.domain.user.dto.UserLoginResponse; import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; @@ -15,16 +17,19 @@ import java.io.IOException; import java.time.Duration; +import java.util.UUID; @Component public class JsonUserAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final TokenProvider tokenProvider; private final RefreshTokenRepository refreshTokenRepository; + private final UserRepository userRepository; - public JsonUserAuthenticationSuccessHandler(TokenProvider tokenProvider, RefreshTokenRepository refreshTokenRepository) { + public JsonUserAuthenticationSuccessHandler(UserRepository userRepository, TokenProvider tokenProvider, RefreshTokenRepository refreshTokenRepository) { this.tokenProvider = tokenProvider; this.refreshTokenRepository = refreshTokenRepository; + this.userRepository = userRepository; } @Override @@ -36,11 +41,13 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 여기에서 UserService의 login을 실행 String email = authentication.getName(); + User user = userRepository.findByEmail(email).orElseThrow(); + UUID id = user.getId(); + String accessToken = tokenProvider.generateToken(id, Duration.ofMinutes(15L)); + String refreshToken = tokenProvider.generateToken(id, Duration.ofMinutes(60L)); - String accessToken = tokenProvider.generateToken(email, Duration.ofMinutes(15L)); - String refreshToken = tokenProvider.generateToken(email, Duration.ofMinutes(60L)); refreshTokenRepository.save(RefreshToken.builder() - .email(email) + .userId(user.getId()) .refreshToken(refreshToken) .build()); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java index a268c4b..0551a8b 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java @@ -1,6 +1,5 @@ package com.ssafy.springbootapi.global.auth.jwt; -import com.ssafy.springbootapi.domain.user.domain.User; import com.ssafy.springbootapi.global.auth.SecurityUser.UserDetailService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; @@ -14,6 +13,7 @@ import java.time.Duration; import java.util.Date; +import java.util.UUID; @RequiredArgsConstructor @Service @@ -21,22 +21,22 @@ public class TokenProvider { private final JwtProperties jwtProperties; private final UserDetailService userDetailService; - public String generateToken(String email, Duration expiredAt) { + public String generateToken(UUID id, Duration expiredAt) { Date now = new Date(); - return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),email); + return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),id); } // jwt token 생성 - private String makeToken(Date expiry, String email){ + private String makeToken(Date expiry, UUID id){ Date now = new Date(); - UserDetails user = userDetailService.loadUserByUsername(email); +// UserDetails user = userDetailService.loadUserByUsername(email); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .setIssuer(jwtProperties.getIssuer()) .setIssuedAt(now) // iat: 발급시간 .setExpiration(expiry) // exp: 만료시간 - .setSubject(user.getUsername()) // sub: 유저 이메일 + .setSubject(String.valueOf(id)) // sub: 유저 UUID // .claim("id", user.get()) // 클레임 id .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()) .compact(); @@ -57,8 +57,8 @@ public boolean validToken(String token){ // 토큰 기반으로 인증 정보를 가져오는 메서드 (토큰 유효성 검사 이후 실행) public Authentication getAuthentication(String token){ Claims claims = getClaims(token); - String userEmail = claims.getSubject(); - UserDetails user = userDetailService.loadUserByUsername(userEmail); + String uuid = claims.getSubject(); + UserDetails user = userDetailService.loadUserByUsername(uuid); return new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java index 68a5dc9..75fc1b7 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java @@ -7,6 +7,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.UUID; + @Builder @NoArgsConstructor @AllArgsConstructor @@ -18,14 +20,14 @@ public class RefreshToken { @Column(name = "id", updatable = false) private Long id; - @Column(name = "email", nullable = false, unique = true) - private String email; + @Column(name = "user_id", nullable = false) + private UUID userId; @Column(name = "refresh_token", nullable = false) private String refreshToken; - public RefreshToken(String email, String refreshToken){ - this.email = email; + public RefreshToken(UUID userId, String refreshToken){ + this.userId = userId; this.refreshToken = refreshToken; } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java index 3da5f2e..a9dec50 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshTokenRepository.java @@ -4,9 +4,10 @@ import org.springframework.stereotype.Repository; import java.util.Optional; +import java.util.UUID; @Repository public interface RefreshTokenRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByUserId(UUID userId); Optional findByRefreshToken(String refreshToken); } From 856e3626c7be3574a20ecce913f294260f85c36c Mon Sep 17 00:00:00 2001 From: kkh Date: Tue, 7 May 2024 01:45:18 +0000 Subject: [PATCH 46/52] :bug: Fix: password encode when updating --- .../springbootapi/domain/user/application/UserService.java | 2 ++ .../ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index 7811bcd..5149a61 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -66,7 +66,9 @@ public UserUpdateResponse updateUserInfo(UUID id, UserUpdateRequest requestDTO) User user = userRepository.findById(id) .orElseThrow(()->new UserNotFoundException(id+" 사용자 없음")); + requestDTO.setPassword(passwordEncoder.encode(requestDTO.getPassword())); userMapper.updateUserFromDto(requestDTO,user); + user = userRepository.save(user); return UserUpdateResponse.builder().email(user.getEmail()).name(user.getName()).createdAt(user.getCreatedAt()).build(); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java index 14a5e64..69eaa6a 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/dto/UserUpdateRequest.java @@ -3,9 +3,11 @@ import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.Setter; @AllArgsConstructor @Getter +@Setter public class UserUpdateRequest { public String password; public String name; From 4074a168aa2956b18d14eb5cb5633b96713dd1b0 Mon Sep 17 00:00:00 2001 From: kkh Date: Tue, 7 May 2024 01:58:52 +0000 Subject: [PATCH 47/52] :white_check_mark: Test: update user service test - id long to UUID --- .../domain/user/UserServiceTest.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java index 06a48f6..6e80b2a 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java @@ -8,6 +8,8 @@ import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; import com.ssafy.springbootapi.global.auth.jsonAuthentication.CustomBCryptPasswordEncoder; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; +import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -23,6 +25,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; +import java.util.UUID; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; @@ -33,6 +36,9 @@ public class UserServiceTest { @Mock UserRepository userRepository; + @Mock + RefreshTokenRepository refreshTokenRepository; + @Spy private final UserMapper userMapper = Mappers.getMapper(UserMapper.class); @@ -99,7 +105,7 @@ public class UserServiceTest { @Test public void 유저정보읽기성공테스트(){ // given - Long id = 1L; + UUID id = UUID.randomUUID(); given(userRepository.findById(id)) .willReturn(Optional.of(User.builder() .email("kkho9654@naver.com") @@ -121,7 +127,7 @@ public class UserServiceTest { @Test void 유저정보읽기존재하지않는사용자예외테스트(){ // given - Long id = 1L; + UUID id = UUID.randomUUID(); given(userRepository.findById(id)) .willReturn(Optional.empty()); @@ -136,16 +142,16 @@ public class UserServiceTest { @Test void 유저수정성공테스트() { // given - Long id = 1L; + UUID id = UUID.randomUUID(); UserUpdateRequest requestDTO - = new UserUpdateRequest("kkho9654@naver2.com","1112","3333"); + = new UserUpdateRequest("1112","3333"); User user = User.builder() - .id(1L) + .id(id) .email("kkho9654@naver.com") .password("1234") .name("kkh").build(); User updatedUser = userMapper.toEntity(requestDTO); - given(userRepository.findById(1L)) + given(userRepository.findById(id)) .willReturn(Optional.of(user)); doNothing().when(userMapper).updateUserFromDto(requestDTO, user); @@ -157,8 +163,6 @@ public class UserServiceTest { UserUpdateResponse responseDTO = userService.updateUserInfo(id,requestDTO); // then - Assertions.assertThat(responseDTO.getEmail()) - .isEqualTo(requestDTO.getEmail()); Assertions.assertThat(responseDTO.getName()) .isEqualTo(requestDTO.getName()); } @@ -167,8 +171,8 @@ public class UserServiceTest { void 유저업데이트존재하지않는사용자예외테스트(){ // given UserMapper userMapper = mock(UserMapper.class); - Long id = 1L; - UserUpdateRequest requestDTO = new UserUpdateRequest("test","test","test"); + UUID id = UUID.randomUUID(); + UserUpdateRequest requestDTO = new UserUpdateRequest("test","test"); given(userRepository.findById(id)) .willReturn(Optional.empty()); @@ -184,25 +188,31 @@ public class UserServiceTest { @Test void 유저삭제성공테스트() { // given - Long id = 1L; + UUID id = UUID.randomUUID(); User user = User.builder() - .id(1L) + .id(id) .email("kkho9654@naver.com") .name("kkh").password("123").build(); + RefreshToken refreshToken = RefreshToken.builder() + .userId(id) + .refreshToken(UUID.randomUUID().toString()).build(); given(userRepository.findById(id)) .willReturn(Optional.of(user)); + given(refreshTokenRepository.findByUserId(id)) + .willReturn(Optional.of(refreshToken)); // when userService.removeUser(id); // then verify(userRepository).delete(user); + verify(refreshTokenRepository).delete(refreshToken); } @DisplayName("유저 삭제 존재하지않는 사용자 예외 테스트") @Test void 유저삭제존재하지않는사용자예외테스트(){ // given - Long id = 1L; + UUID id = UUID.randomUUID(); given(userRepository.findById(id)) .willReturn(Optional.empty()); From 7ba3477a358ad39948792c3d33ff7bccace5bb84 Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 8 May 2024 12:09:34 +0000 Subject: [PATCH 48/52] :fire: Refactor: unused success handler --- .../JsonUserAuthenticationSuccessHandler.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java index 6987f6e..e7b46cc 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java @@ -32,14 +32,8 @@ public JsonUserAuthenticationSuccessHandler(UserRepository userRepository, Token this.userRepository = userRepository; } - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { - AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); - } - @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - // 여기에서 UserService의 login을 실행 String email = authentication.getName(); User user = userRepository.findByEmail(email).orElseThrow(); UUID id = user.getId(); From 4639c4e4fd8a439bb188b62b94c5594ff404557c Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 8 May 2024 12:24:50 +0000 Subject: [PATCH 49/52] =?UTF-8?q?:recycle:=20Refactor:=20UsernamePasswordA?= =?UTF-8?q?uthenticationFilter=20-=20setPasswordParameter=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=82=AC=EC=9A=A9=20-=20JsonProcessingAut?= =?UTF-8?q?henticationException=20custom=20exception=20-=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=ED=8A=B8=20=ED=83=80=EC=9E=85=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?:=20equals->startWith=20-=20try-with-resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/JsonProcessingAuthenticationException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java new file mode 100644 index 0000000..b86f693 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java @@ -0,0 +1,9 @@ +package com.ssafy.springbootapi.global.error; + +import org.springframework.security.core.AuthenticationException; + +public class JsonProcessingAuthenticationException extends AuthenticationException { + public JsonProcessingAuthenticationException(String msg, Throwable t) { + super(msg, t); + } +} From 09f3b80da86952aa0383204368bb8f4fc3809ad7 Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 8 May 2024 12:36:49 +0000 Subject: [PATCH 50/52] =?UTF-8?q?:art:=20Feat:=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/error/JsonProcessingAuthenticationException.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java index b86f693..0a72659 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/JsonProcessingAuthenticationException.java @@ -3,6 +3,10 @@ import org.springframework.security.core.AuthenticationException; public class JsonProcessingAuthenticationException extends AuthenticationException { + public JsonProcessingAuthenticationException(String msg) { + super(msg); + } + public JsonProcessingAuthenticationException(String msg, Throwable t) { super(msg, t); } From 88a149cfd6ec2395e10907447dbc33706fc3e225 Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 8 May 2024 13:03:19 +0000 Subject: [PATCH 51/52] =?UTF-8?q?:memo:=20Docs:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20java=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/application/UserService.java | 5 -- .../global/auth/AuthController.java | 20 ++++- .../global/auth/AuthService.java | 26 ++++--- .../global/auth/dto/TokenRequest.java | 3 + .../global/auth/dto/TokenResponse.java | 3 + .../CustomBCryptPasswordEncoder.java | 15 +++- .../JsonUserAuthenticationProvider.java | 3 + .../JsonUserAuthenticationSuccessHandler.java | 15 +++- ...nUsernamePasswordAuthenticationFilter.java | 45 +++++------ .../auth/jwt/JwtAuthenticationFilter.java | 15 ++-- .../global/auth/jwt/TokenProvider.java | 64 ++++++++++++---- .../auth/jwt/refreshToken/RefreshToken.java | 1 - .../global/config/WebSecurityConfig.java | 75 +++++++++---------- .../domain/user/UserServiceTest.java | 3 - 14 files changed, 184 insertions(+), 109 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java index 5149a61..5f06d5a 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/user/application/UserService.java @@ -6,17 +6,12 @@ import com.ssafy.springbootapi.domain.user.dto.*; import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; -import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; -import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.time.Duration; import java.util.UUID; /* diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java index 19ac4fd..7738e95 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthController.java @@ -13,6 +13,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +/** + * 사용자 인증을 관리하는 컨트롤러 클래스. + * 로그인 및 토큰 재발급 기능을 제공합니다. + */ @RequestMapping("/api/v1/auth") @ControllerAdvice @RequiredArgsConstructor @@ -20,20 +24,30 @@ public class AuthController { private final AuthService authService; + /** + * 사용자 로그인을 처리하고 로그인 응답을 반환합니다. + * + * @param userLoginRequest 로그인 요청 데이터 + * @return 로그인 성공 시 상태 코드 200과 함께 로그인 응답 데이터를 포함한 ResponseEntity 객체 반환 + */ @PostMapping("/login") @Operation(summary = "로그인", description = "로그인 할 때 사용하는 API") public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest){ return ResponseEntity.status(HttpStatus.OK).build(); } + /** + * 저장된 리프레시 토큰을 검증하고 새 액세스 토큰을 발급합니다. + * + * @param request HTTP 요청 정보를 포함하는 HttpServletRequest 객체 + * @return 새로운 액세스 토큰을 포함한 ResponseEntity 객체. 리프레시 토큰이 유효하지 않거나 없을 경우 401 Unauthorized 반환 + */ @PostMapping("/token") @Operation(summary = "토큰 재발급", description = "토큰 재발급 API") public ResponseEntity token(HttpServletRequest request) { String refreshToken = null; - // 요청에서 쿠키 배열을 가져옵니다. Cookie[] cookies = request.getCookies(); if (cookies != null) { - // 쿠키 배열을 순회하면서 refreshToken 쿠키를 찾습니다. for (Cookie cookie : cookies) { if ("refreshToken".equals(cookie.getName())) { refreshToken = cookie.getValue(); @@ -41,12 +55,10 @@ public ResponseEntity token(HttpServletRequest request) { } } } - // refreshToken이 있는 경우 새로운 액세스 토큰을 발급받습니다. if (refreshToken != null) { String accessToken = authService.provideNewAccessToken(refreshToken); return ResponseEntity.ok(TokenResponse.builder().accessToken(accessToken).build()); } else { - // refreshToken이 없는 경우 적절한 예외 처리를 합니다. return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java index 6f3bdda..1ce755c 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/AuthService.java @@ -5,34 +5,40 @@ import com.ssafy.springbootapi.global.error.InvalidTokenException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.UUID; +/** + * 인증 관련 서비스를 제공하는 클래스. + * 주로 토큰 생성 및 검증을 담당합니다. + */ @RequiredArgsConstructor @Service public class AuthService { private final RefreshTokenRepository refreshTokenRepository; private final TokenProvider tokenProvider; - public String provideNewAccessToken(String refreshToken){ - // refresh 토큰이 유효하다면 새 access 토큰 발급 - // refresh 토큰이 유효하지 않다면 exception - + /** + * 제공된 리프레시 토큰의 유효성을 검사하고, 유효한 경우 새로운 액세스 토큰을 발급합니다. + * 리프레시 토큰이 유효하지 않을 경우 InvalidTokenException 예외를 발생시킵니다. + * + * @param refreshToken 검증하고자 하는 리프레시 토큰 + * @return 새로 발급된 액세스 토큰 문자열 + * @throws InvalidTokenException 리프레시 토큰이 유효하지 않을 경우 발생 + */ + public String provideNewAccessToken(String refreshToken) { String accessToken = ""; if (tokenProvider.validToken(refreshToken)){ - refreshTokenRepository.findByRefreshToken(refreshToken).orElseThrow(()-> + refreshTokenRepository.findByRefreshToken(refreshToken).orElseThrow(() -> new InvalidTokenException("invalid refresh token!") ); Authentication authentication = tokenProvider.getAuthentication(refreshToken); - accessToken = tokenProvider.generateToken(UUID.fromString(authentication.getName()), Duration.ofMinutes(1L)); - }else { + accessToken = tokenProvider.generateToken(UUID.fromString(authentication.getName()), Duration.ofMinutes(15L)); + } else { throw new InvalidTokenException("invalid refresh token!"); } return accessToken; } - } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java index 6b68d77..8a4ed25 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenRequest.java @@ -3,6 +3,9 @@ import lombok.Getter; import lombok.Setter; +/** + * access Token 재발급을 위한 요청 DTO + */ @Getter @Setter public class TokenRequest { diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java index f0b4b00..502edce 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/dto/TokenResponse.java @@ -4,6 +4,9 @@ import lombok.Getter; import lombok.Setter; +/** + * access Token 재발급을 위한 응답 DTO + */ @Getter @Setter @Builder diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java index b908865..f7a31ee 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/CustomBCryptPasswordEncoder.java @@ -4,6 +4,9 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +/** + * BCrypt 방식으로 사용자의 비밀번호를 사용하기 위한 PasswordEncoder + */ @Component public class CustomBCryptPasswordEncoder implements PasswordEncoder { private final BCryptPasswordEncoder bCryptPasswordEncoder; @@ -12,15 +15,23 @@ public CustomBCryptPasswordEncoder() { this.bCryptPasswordEncoder = new BCryptPasswordEncoder(); } + /** + * @param rawPassword 비밀번호 원본 + * @return encodedPassword 암호화된 비밀번호 + */ @Override public String encode(CharSequence rawPassword) { - // 여기에 비밀번호 인코딩 전에 추가 로직을 구현할 수 있습니다. return bCryptPasswordEncoder.encode(rawPassword); } + /** + * 로그인시 사용자 입력암호와 db에 저장된 인코딩 암호와 비교 + * @param rawPassword 사용자 입력 암호 + * @param encodedPassword db에 저장된 인코딩 암호 + * @return boolean + */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { - // 여기에 매칭 로직 전에 추가 검사를 구현할 수 있습니다. return bCryptPasswordEncoder.matches(rawPassword, encodedPassword); } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java index 18a2710..698b0a9 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationProvider.java @@ -13,6 +13,9 @@ import java.util.UUID; +/** + * Json 데이터 타입으로 보낸 로그인 요청의 인증처리를 위한 인증 공급자 + */ @RequiredArgsConstructor @Component public class JsonUserAuthenticationProvider implements AuthenticationProvider { diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java index e7b46cc..b2673d6 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUserAuthenticationSuccessHandler.java @@ -6,7 +6,6 @@ import com.ssafy.springbootapi.global.auth.jwt.TokenProvider; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; -import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -19,6 +18,9 @@ import java.time.Duration; import java.util.UUID; +/** + * JSON 타입의 유저 로그인이 성공했을 때의 핸들러 + */ @Component public class JsonUserAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @@ -32,6 +34,17 @@ public JsonUserAuthenticationSuccessHandler(UserRepository userRepository, Token this.userRepository = userRepository; } + /** + * json 아이디 패스워드 인증 성공시 실행되는 메소드 + * RefreshToken : Http-Only cookie - 60분 + * AccessToken : response body (UserLoginResponse DTO) - 15분 + * + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param authentication Authentication, 인증 정보 + * @throws IOException + * @throws ServletException + */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String email = authentication.getName(); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java index a8eb26d..a4cd650 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jsonAuthentication/JsonUsernamePasswordAuthenticationFilter.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ssafy.springbootapi.domain.user.dto.UserLoginRequest; +import com.ssafy.springbootapi.global.error.JsonProcessingAuthenticationException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; @@ -23,32 +24,32 @@ public JsonUsernamePasswordAuthenticationFilter(AuthenticationManager authentica super.setAuthenticationManager(authenticationManager); } + /** + * Json content type 의 요청에 대해 로그인 필터 진행 + * Json 의 ID와 Password 를 추출 + * 인증 매니저에게 ID와 Password 에 대한 인증 객체 (Authentication) 을 넘기며 인증 진행 + */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { -// System.out.println("In JsonUsernamePasswordAuthenticationFilter"); - if ("application/json".equals(request.getContentType())) { - try { - // JSON 요청에서 사용자 이름과 비밀번호를 읽어옵니다. - BufferedReader reader = request.getReader(); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - UserLoginRequest loginRequest = objectMapper.readValue(sb.toString(), UserLoginRequest.class); - setPasswordParameter("useremail "+loginRequest.getEmail()); - UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( - loginRequest.getEmail(), loginRequest.getPassword()); - - setDetails(request, authRequest); - return this.getAuthenticationManager().authenticate(authRequest); - } catch (IOException e) { - throw new RuntimeException(e); + if (!request.getContentType().startsWith("application/json")) { + throw new JsonProcessingAuthenticationException("Authentication method not supported: " + request.getMethod()); + } + + try (BufferedReader reader = request.getReader()) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); } - } else { - // 기본 form-urlencoded 방식 처리 - return super.attemptAuthentication(request, response); + UserLoginRequest loginRequest = objectMapper.readValue(sb.toString(), UserLoginRequest.class); + UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( + loginRequest.getEmail(), loginRequest.getPassword()); + setDetails(request, authRequest); + return this.getAuthenticationManager().authenticate(authRequest); + } catch (IOException e) { + throw new JsonProcessingAuthenticationException("JSON parsing failed", e); } } + } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java index 5316b66..3e8cd06 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java @@ -5,8 +5,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -21,13 +19,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final static String HEADER_AUTHORIZATION = "Authorization"; private final static String TOKEN_PREFIX = "Bearer"; + /** + * 요청 헤더의 Authorization 키의 값 조회 + * getAccessToken - 가져온 값에서 접두사 제거 + * 가져온 토큰이 유효한지 확인하고, 유효하면 인증정보 설정 + */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // 요청 헤더의 Authorization 키의 값 조회 String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); - // 가져온 값에서 접두사 제거 + String token = getAccessToken(authorizationHeader); - // 가져온 토큰이 유효한지 확인하고, 유효하면 인증정보 설정 + if(tokenProvider.validToken(token)) { Authentication authentication = tokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); @@ -44,6 +46,9 @@ private String getAccessToken(String authorizationHeader){ } + /** + * 회원가입과 로그인, 토큰재발급, swagger 문서에 대해서는 filtering 하지 않는다. + */ @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String path = request.getRequestURI(); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java index 0551a8b..3d26182 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/TokenProvider.java @@ -15,62 +15,96 @@ import java.util.Date; import java.util.UUID; +/** + * JWT 토큰을 관리하는 서비스 클래스. + */ @RequiredArgsConstructor @Service public class TokenProvider { private final JwtProperties jwtProperties; private final UserDetailService userDetailService; + /** + * 지정된 사용자 ID와 만료 기간을 기반으로 JWT 토큰을 생성합니다. + * + * @param id 사용자의 고유 UUID + * @param expiredAt 토큰의 만료 시간 + * @return 생성된 JWT 토큰 문자열 + */ public String generateToken(UUID id, Duration expiredAt) { Date now = new Date(); - return makeToken(new Date(now.getTime()+ expiredAt.toMillis()),id); + return makeToken(new Date(now.getTime() + expiredAt.toMillis()), id); } - // jwt token 생성 - private String makeToken(Date expiry, UUID id){ + /** + * 주어진 만료 일자와 사용자 ID로 JWT 토큰을 생성합니다. + * + * @param expiry 토큰의 만료 시간 + * @param id 사용자의 UUID + * @return 생성된 JWT 토큰 + */ + private String makeToken(Date expiry, UUID id) { Date now = new Date(); -// UserDetails user = userDetailService.loadUserByUsername(email); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .setIssuer(jwtProperties.getIssuer()) .setIssuedAt(now) // iat: 발급시간 .setExpiration(expiry) // exp: 만료시간 .setSubject(String.valueOf(id)) // sub: 유저 UUID -// .claim("id", user.get()) // 클레임 id .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()) .compact(); } - // JWT 토큰 유효성 검증 메서드 - public boolean validToken(String token){ + /** + * 제공된 JWT 토큰의 유효성을 검증합니다. + * + * @param token 검증하고자 하는 JWT 토큰 + * @return 토큰의 유효성 여부를 boolean으로 반환 + */ + public boolean validToken(String token) { try { Jwts.parser() .setSigningKey(jwtProperties.getSecretKey()) .parseClaimsJws(token); return true; - } catch (Exception e){ + } catch (Exception e) { return false; } } - // 토큰 기반으로 인증 정보를 가져오는 메서드 (토큰 유효성 검사 이후 실행) - public Authentication getAuthentication(String token){ + /** + * 유효한 JWT 토큰으로부터 사용자의 인증 정보를 가져옵니다. + * + * @param token 사용자의 JWT 토큰 + * @return 인증 정보 객체 + */ + public Authentication getAuthentication(String token) { Claims claims = getClaims(token); String uuid = claims.getSubject(); UserDetails user = userDetailService.loadUserByUsername(uuid); - return new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); + return new UsernamePasswordAuthenticationToken(uuid, null, user.getAuthorities()); } - // 토큰 기반으로 유저 ID를 가져오는 메서드 + /** + * JWT 토큰에서 사용자 ID를 추출합니다. + * + * @param token 사용자의 JWT 토큰 + * @return 추출된 사용자 ID + */ public Long getUserId(String token) { Claims claims = getClaims(token); - return claims.get("id",Long.class); + return claims.get("id", Long.class); } - // 클래임 조회 - private Claims getClaims(String token){ + /** + * 주어진 토큰에서 JWT 클레임 세트를 반환합니다. + * + * @param token JWT 토큰 + * @return 클레임 객체 + */ + private Claims getClaims(String token) { return Jwts.parser() .setSigningKey(jwtProperties.getSecretKey()) .parseClaimsJws(token) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java index 75fc1b7..906bd13 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/refreshToken/RefreshToken.java @@ -1,6 +1,5 @@ package com.ssafy.springbootapi.global.auth.jwt.refreshToken; -import com.ssafy.springbootapi.domain.user.domain.User; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java index b283879..4fdc913 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/WebSecurityConfig.java @@ -1,6 +1,5 @@ package com.ssafy.springbootapi.global.config; -import com.ssafy.springbootapi.domain.user.domain.UserType; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUsernamePasswordAuthenticationFilter; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationSuccessHandler; import com.ssafy.springbootapi.global.auth.jsonAuthentication.JsonUserAuthenticationProvider; @@ -10,7 +9,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authorization.AuthenticatedAuthorizationManager; 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; @@ -19,6 +17,10 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +/** + * Spring Security 설정을 위한 설정 클래스. + * 이 클래스는 HTTP 보안 설정을 정의하며, JWT 기반 인증을 포함하여 사용자 정의 인증 메커니즘을 구성합니다. + */ @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -28,56 +30,47 @@ public class WebSecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; private final JsonUserAuthenticationSuccessHandler jsonUserAuthenticationSuccessHandler; + /** + * Spring Security의 필터 체인을 구성합니다. + * + * @param http HttpSecurity를 통해 보안 구성을 정의 + * @return 구성된 SecurityFilterChain + * @throws Exception 보안 구성 중 예외 발생 시 + */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // CSRF 보호 비활성화 - http.csrf(AbstractHttpConfigurer::disable); + http.csrf(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/swagger-ui/**").permitAll() + .requestMatchers("/api-docs/**").permitAll() + .requestMatchers(HttpMethod.POST,"/api/v1/users").permitAll() + .requestMatchers(HttpMethod.POST,"/api/v1/auth/token").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated()) // 그 외의 모든 요청은 인증 필요) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .authenticationProvider(jsonUserAuthenticationProvider); - // http basic 인증 비활성화 - http.httpBasic(AbstractHttpConfigurer::disable); - http.formLogin(AbstractHttpConfigurer::disable); - - // 세션 관리 정책 설정 - http.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - - // HTTP 요청에 대한 접근 제어 - http.authorizeHttpRequests(auth -> auth - .requestMatchers("/swagger-ui/**").permitAll() - .requestMatchers("/api-docs/**").permitAll() - .requestMatchers(HttpMethod.POST,"/api/v1/users").permitAll() - .requestMatchers(HttpMethod.POST,"/api/v1/auth/token").permitAll() -// .requestMatchers("/api/v1/login").permitAll() // 로그인 API는 모든 요청을 허용 - .requestMatchers("/admin/**").hasRole("ADMIN") - .anyRequest().authenticated() // 그 외의 모든 요청은 인증 필요 - ); - - // jwt 필터 추가 - http.addFilterBefore(jwtAuthenticationFilter - ,UsernamePasswordAuthenticationFilter.class); - - http.authenticationProvider(jsonUserAuthenticationProvider); - - // UsernamePasswordAuthenticationFilter 추가 JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration)); filter.setAuthenticationSuccessHandler(jsonUserAuthenticationSuccessHandler); filter.setFilterProcessesUrl("/api/v1/auth/login"); - http.addFilterAt(filter, - UsernamePasswordAuthenticationFilter.class); + http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class); return http.build(); } + /** + * AuthenticationManager를 제공합니다. + * 이 메서드는 AuthenticationManager를 구성하고, 사용자 정의 인증 메커니즘을 사용할 수 있도록 합니다. + * + * @param authenticationConfiguration 인증 설정 + * @return 구성된 AuthenticationManager + * @throws Exception 인증 매니저 구성 중 예외 발생 시 + */ @Bean - AuthenticationManager authenticationManager( - AuthenticationConfiguration authenticationConfiguration) throws Exception { + AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } - -// public JsonUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) { -// JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(authenticationManager); -// filter.setAuthenticationManager(authenticationManager); -// filter.setFilterProcessesUrl("/api/v1/users/login"); // 필터가 실행될 URL 설정 -// return filter; -// } - } diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java index 6e80b2a..d7c78b8 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/user/UserServiceTest.java @@ -7,7 +7,6 @@ import com.ssafy.springbootapi.domain.user.dto.*; import com.ssafy.springbootapi.domain.user.exception.UserDuplicatedException; import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; -import com.ssafy.springbootapi.global.auth.jsonAuthentication.CustomBCryptPasswordEncoder; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshToken; import com.ssafy.springbootapi.global.auth.jwt.refreshToken.RefreshTokenRepository; import org.assertj.core.api.Assertions; @@ -20,8 +19,6 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; From d40bdcacd102937f80df5c0e236adbd2ed92dc2e Mon Sep 17 00:00:00 2001 From: kkh Date: Wed, 8 May 2024 14:03:16 +0000 Subject: [PATCH 52/52] :bug: Fix: invalid access token return 403 -> 401 --- .../auth/jwt/JwtAuthenticationFilter.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java index 3e8cd06..3b38098 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/auth/jwt/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package com.ssafy.springbootapi.global.auth.jwt; +import com.ssafy.springbootapi.global.error.InvalidTokenException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -26,16 +27,24 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); + try { + String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); + String token = getAccessToken(authorizationHeader); - String token = getAccessToken(authorizationHeader); + if (token != null && tokenProvider.validToken(token)) { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + throw new InvalidTokenException("Invalid AccessToken"); + } - if(tokenProvider.validToken(token)) { - Authentication authentication = tokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } catch (InvalidTokenException e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: " + e.getMessage()); + response.getWriter().flush(); + return; } - - filterChain.doFilter(request,response); } private String getAccessToken(String authorizationHeader){