From e427a26c0adc0a90fed58675711a3ec1041400cf Mon Sep 17 00:00:00 2001 From: binary_ho Date: Fri, 8 Mar 2024 17:59:03 +0900 Subject: [PATCH 01/27] =?UTF-8?q?feat=20:=20GitHubResource=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20Embedded=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B5=AC=ED=98=84=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/core/member/GitHubResource.java | 28 +++++++++++++++++++ .../binaryho/imhere/core/member/Member.java | 18 +++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java b/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java new file mode 100644 index 0000000..b10d2fa --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java @@ -0,0 +1,28 @@ +package gdsc.binaryho.imhere.core.member; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class GitHubResource { + + @Column(unique = true, name = "git_hub_id", nullable = false) + private String id; + + @Column(name = "git_hub_handle", nullable = false) + private String handle; + + @Column(name = "git_hub_profile") + private String profile; + + protected void updateHandle(String gitHubHandle) { + this.handle = gitHubHandle; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java index f2e4809..70a0e06 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import java.time.LocalDateTime; import javax.persistence.Column; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -27,8 +28,13 @@ public class Member { @Column(name = "member_id") private Long id; + // TODO : nullable false 조건 제거 @Column(unique = true, nullable = false) private String univId; + + @Embedded + private GitHubResource gitHubResource; + @Column(nullable = false) private String name; private String password; @@ -41,10 +47,6 @@ public class Member { @CreatedDate private LocalDateTime createdAt; - public String getRoleKey() { - return role.getKey(); - } - public static Member createMember(String univId, String name, String password, Role role) { Member member = new Member(); member.setUnivId(univId); @@ -53,4 +55,12 @@ public static Member createMember(String univId, String name, String password, R member.setRole(role); return member; } + + public void updateGitHubHandle(String gitHubHandle) { + this.gitHubResource.updateHandle(gitHubHandle); + } + + public String getRoleKey() { + return role.getKey(); + } } From 8d58613e6392706ac9047a97cd9b93a590e4a722 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Fri, 8 Mar 2024 23:01:39 +0900 Subject: [PATCH 02/27] =?UTF-8?q?feat=20:=20OAuth2=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 39a075d..0832030 100644 --- a/build.gradle +++ b/build.gradle @@ -36,8 +36,9 @@ dependencies { runtimeOnly 'com.h2database:h2' /* Security */ - testImplementation 'org.springframework.security:spring-security-test' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + testImplementation 'org.springframework.security:spring-security-test' /* Lombok */ compileOnly 'org.projectlombok:lombok' From 23b31a568c15c7758078c925403bc33fbc772734 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Fri, 8 Mar 2024 23:19:44 +0900 Subject: [PATCH 03/27] =?UTF-8?q?feat=20:=20OAuth2User=EB=A5=BC=20Wrapping?= =?UTF-8?q?=20=ED=95=98=EB=8A=94=20GitHubUser=20=EA=B5=AC=ED=98=84=20(#112?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/oauth/GitHubUser.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java new file mode 100644 index 0000000..43f3297 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java @@ -0,0 +1,39 @@ +package gdsc.binaryho.imhere.security.oauth; + +import java.util.Collection; +import java.util.Map; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public class GitHubUser { + + public static final String GITHUB_NAME_ATTRIBUTE_KEY = "id"; + private static final String GIT_HUB_HANDLE_ATTRIBUTE_NAME = "login"; + private static final String GIT_HUB_AVATAR_URL_ATTRIBUTE_NAME = "avatar_url"; + + private final OAuth2User oAuth2User; + + public GitHubUser(OAuth2User oAuth2User) { + this.oAuth2User = oAuth2User; + } + + public String getId() { + return oAuth2User.getName(); + } + + public String getHandle() { + return oAuth2User.getAttribute(GIT_HUB_HANDLE_ATTRIBUTE_NAME); + } + + public String getAvatarUrl() { + return oAuth2User.getAttribute(GIT_HUB_AVATAR_URL_ATTRIBUTE_NAME); + } + + public Collection getAuthorities() { + return oAuth2User.getAuthorities(); + } + + public Map getAttributes() { + return oAuth2User.getAttributes(); + } +} From 8a9a2b0fd9550015d81547d80aa0fcacad13ee5b Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:11:13 +0900 Subject: [PATCH 04/27] =?UTF-8?q?feat=20:=20=EC=9D=B8=EA=B0=80=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=90=98=EB=8A=94=20CustomOAuth2User=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SignUpProcessRedirectionPath.java | 26 +++++++++++++++++++ .../security/oauth/CustomOAuth2User.java | 22 ++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java create mode 100644 src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java diff --git a/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java b/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java new file mode 100644 index 0000000..965f94c --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java @@ -0,0 +1,26 @@ +package gdsc.binaryho.imhere.security; + +import gdsc.binaryho.imhere.core.member.Member; +import lombok.Getter; + +@Getter +public enum SignUpProcessRedirectionPath { + + BEFORE_MEMBER_INFO_INPUT("/signup"), + SIGN_UP_DONE("/main"), + ; + + private final String redirectUrlPath; + + public static SignUpProcessRedirectionPath of(Member member) { + if (member.getName() == null) { + return BEFORE_MEMBER_INFO_INPUT; + } + + return SIGN_UP_DONE; + } + + SignUpProcessRedirectionPath(String redirectUrlPath) { + this.redirectUrlPath = redirectUrlPath; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java new file mode 100644 index 0000000..b79b9d3 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java @@ -0,0 +1,22 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.core.member.Member; +import gdsc.binaryho.imhere.core.member.Role; +import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; +import lombok.Getter; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +@Getter +public class CustomOAuth2User extends DefaultOAuth2User { + + private final Long memberId; + private final Role role; + private final SignUpProcessRedirectionPath signUpProcessRedirectionPath; + + public CustomOAuth2User(GitHubUser gitHubUser, Member member) { + super(gitHubUser.getAuthorities(), gitHubUser.getAttributes(), GitHubUser.GITHUB_NAME_ATTRIBUTE_KEY); + this.memberId = member.getId(); + this.role = member.getRole(); + this.signUpProcessRedirectionPath = SignUpProcessRedirectionPath.of(member); + } +} From bd8241ec60ee299450a89e35b93c414749148f2a Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:15:58 +0900 Subject: [PATCH 05/27] =?UTF-8?q?refactor=20:=20Token=20Service=EC=97=90?= =?UTF-8?q?=20id=EB=A1=9C=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EB=A7=8C?= =?UTF-8?q?=EB=93=9C=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/jwt/TokenService.java | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java index dc6daa2..8e7c844 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java @@ -1,5 +1,6 @@ package gdsc.binaryho.imhere.security.jwt; +import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.util.SeoulDateTimeHolder; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; @@ -20,33 +21,27 @@ public class TokenService { private final SecretHolder secretHolder; private final SeoulDateTimeHolder seoulDateTimeHolder; + private static final String ROLE_KEY = "role"; private static final Duration ACCESS_TOKEN_EXPIRATION_TIME = Duration.ofMinutes(30); + // TODO : 기존 토큰 생성 방식은 삭제할 예정 public Token createToken(String univId, String roleKey) { Claims claims = Jwts.claims().setSubject(univId); - claims.put("role", roleKey); - - long seoulTimeNow = seoulDateTimeHolder.getSeoulMilliseconds(); - - String jwt = Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date(seoulTimeNow)) - .setExpiration(new Date(seoulTimeNow + ACCESS_TOKEN_EXPIRATION_TIME.toMillis())) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) - .compact(); + return createToken(claims, roleKey); + } - return new Token(jwt); + public Token createToken(Long id, Role role) { + Claims claims = Jwts.claims().setSubject(id.toString()); + return createToken(claims, role.getKey()); } public boolean validateTokenExpirationTimeNotExpired(String token) { - if (token == null || token.isEmpty()) { + if (isNullOrEmpty(token)) { return false; } try { - Jwts.parser() - .setSigningKey(secretHolder.getSecret()) - .parseClaimsJws(token); + parseToValidateToken(token); return true; } catch (ExpiredJwtException exception) { return false; @@ -63,4 +58,31 @@ public String getUnivId(String token) { .getBody() .getSubject(); } + + private Token createToken(Claims claims, String roleKey) { + claims.put(ROLE_KEY, roleKey); + + String jwtToken = buildJwtToken(claims); + return new Token(jwtToken); + } + + private Boolean isNullOrEmpty(String token) { + return token == null || token.isEmpty(); + } + + private String buildJwtToken(Claims claims) { + long seoulTimeNow = seoulDateTimeHolder.getSeoulMilliseconds(); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date(seoulTimeNow)) + .setExpiration(new Date(seoulTimeNow + ACCESS_TOKEN_EXPIRATION_TIME.toMillis())) + .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .compact(); + } + + private void parseToValidateToken(String token) { + Jwts.parser() + .setSigningKey(secretHolder.getSecret()) + .parseClaimsJws(token); + } } From 406987b2aaa1e23e14963c700c057315ad3c6b88 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:24:37 +0900 Subject: [PATCH 06/27] =?UTF-8?q?feat=20:=20OAuth2=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=EC=8B=9C=20Response=EC=97=90=20RedirectionUR?= =?UTF-8?q?L=EA=B3=BC=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=84=B8=ED=8C=85?= =?UTF-8?q?=ED=95=98=EB=8A=94=20SuccessHandler=20=EA=B5=AC=ED=98=84=20(#11?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/CustomOAuth2SuccessHandler.java | 46 +++++++++++++++++++ .../binaryho/imhere/util/ClientUrlUtil.java | 29 ++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java create mode 100644 src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java new file mode 100644 index 0000000..d056b1a --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java @@ -0,0 +1,46 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; +import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.util.ClientUrlUtil; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; + private static final String ACCESS_TOKEN_PREFIX = "Token "; + + private final TokenService tokenService; + private final ClientUrlUtil clientUrlUtil; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); + + setRedirectUrl(request, response, customOAuth2User.getSignUpProcessRedirectionPath()); + setAccessToken(response, customOAuth2User); + } + + private void setRedirectUrl(HttpServletRequest request, HttpServletResponse response, + SignUpProcessRedirectionPath signupProcessRedirectionPath) throws IOException { + String redirectUrl = clientUrlUtil.getClientUrl() + signupProcessRedirectionPath.getRedirectUrlPath(); + this.getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } + + private void setAccessToken(HttpServletResponse response, CustomOAuth2User oAuthUser) { + Token jwtToken = tokenService.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); + response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); + response.addHeader(HEADER_STRING, ACCESS_TOKEN_PREFIX + jwtToken.getAccessToken()); + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java b/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java new file mode 100644 index 0000000..a1f8964 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java @@ -0,0 +1,29 @@ +package gdsc.binaryho.imhere.util; + +import static gdsc.binaryho.imhere.constant.UrlConstant.LOCAL_CLIENT_URL; +import static gdsc.binaryho.imhere.constant.UrlConstant.PROD_CLIENT_URL; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ClientUrlUtil { + + private final static String PROD = "prod"; + private final Environment environment; + + public String getClientUrl() { + if (isProd()) { + return PROD_CLIENT_URL; + } + return LOCAL_CLIENT_URL; + } + + private boolean isProd() { + return List.of(environment.getActiveProfiles()) + .contains(PROD); + } +} From 8d0fc137fe82f58e35b3e43470438c4dba2e351e Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:26:54 +0900 Subject: [PATCH 07/27] =?UTF-8?q?refactor=20:=20id=EB=A5=BC=20=EB=A7=A5?= =?UTF-8?q?=EB=9D=BD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20memberId=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gdsc/binaryho/imhere/security/jwt/TokenService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java index 8e7c844..87acb14 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java @@ -30,8 +30,8 @@ public Token createToken(String univId, String roleKey) { return createToken(claims, roleKey); } - public Token createToken(Long id, Role role) { - Claims claims = Jwts.claims().setSubject(id.toString()); + public Token createToken(Long memberId, Role role) { + Claims claims = Jwts.claims().setSubject(memberId.toString()); return createToken(claims, role.getKey()); } From 8ee03d52fef23e77acdd05c3b82e0f42808c10b7 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:28:32 +0900 Subject: [PATCH 08/27] =?UTF-8?q?feat=20:=20UrlConstant=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gdsc/binaryho/imhere/constant/UrlConstant.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java diff --git a/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java b/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java new file mode 100644 index 0000000..ea4510a --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java @@ -0,0 +1,10 @@ +package gdsc.binaryho.imhere.constant; + +public class UrlConstant { + + public static final String PROD_CLIENT_URL = "https://imhere.im"; + + public static final String LOCAL_CLIENT_URL = "http://localhost:3000"; + + private UrlConstant() {} +} From a433d9a73eb55c1dfc71a60cbe8c975a1cea9d85 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:32:02 +0900 Subject: [PATCH 09/27] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EB=A7=88=EC=B9=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EC=97=90=20=EC=82=AC=EC=9A=A9=EB=90=A0=20Role=20Guest?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gdsc/binaryho/imhere/core/member/Member.java | 8 ++++++++ src/main/java/gdsc/binaryho/imhere/core/member/Role.java | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java index 70a0e06..455564e 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java @@ -47,6 +47,14 @@ public class Member { @CreatedDate private LocalDateTime createdAt; + public static Member createGuestMember(String gitHubId, String gitHubHandle, String gitHubProfile) { + Member member = new Member(); + member.gitHubResource = new GitHubResource(gitHubId, gitHubHandle, gitHubProfile); + member.setRole(Role.GUEST); + return member; + } + + // TODO : OAUth2 도입 이후 사용하지 않을 기능 public static Member createMember(String univId, String name, String password, Role role) { Member member = new Member(); member.setUnivId(univId); diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/Role.java b/src/main/java/gdsc/binaryho/imhere/core/member/Role.java index 80fa052..b9521fe 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/Role.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/Role.java @@ -1,7 +1,12 @@ package gdsc.binaryho.imhere.core.member; public enum Role { - ADMIN("ROLE_ADMIN"), LECTURER("ROLE_LECTURER"), STUDENT("ROLE_STUDENT"); + GUEST("ROLE_GUEST"), + ADMIN("ROLE_ADMIN"), + LECTURER("ROLE_LECTURER"), + STUDENT("ROLE_STUDENT"), + + ; private final String key; From 2f7b98a2c4b2902b4990dfb32aa818f67340f69f Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:34:12 +0900 Subject: [PATCH 10/27] =?UTF-8?q?feat=20:=20=EC=9D=B8=EC=A6=9D=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=90=A0=20CustomOAuth2User=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20CustomOAuth2UserService?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/MemberRepository.java | 1 + .../oauth/CustomOAuth2UserService.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java b/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java index 3e80b1a..974e4a6 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java @@ -8,4 +8,5 @@ public interface MemberRepository extends JpaRepository { Optional findById(Long id); Optional findByUnivId(String univId); + Optional findByGitHubResource_Id(String gitHubId); } diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java new file mode 100644 index 0000000..b60a5b2 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java @@ -0,0 +1,44 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.core.member.Member; +import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final MemberRepository memberRepository; + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + GitHubUser gitHubUser = new GitHubUser(super.loadUser(userRequest)); + + Member member = memberRepository.findByGitHubResource_Id(gitHubUser.getId()) + .orElseGet(() -> { + Member guest = createGuestMember(gitHubUser); + return memberRepository.save(guest); + }); + + updateGitHubHandleIfChanged(member, gitHubUser.getHandle()); + return new CustomOAuth2User(gitHubUser, member); + } + + private static Member createGuestMember(GitHubUser gitHubUser) { + return Member.createGuestMember( + gitHubUser.getId(), gitHubUser.getHandle(), gitHubUser.getAvatarUrl()); + } + + private void updateGitHubHandleIfChanged(Member member, String gitHubHandle) { + if (!member.getGitHubResource().getHandle().equals(gitHubHandle)) { + member.updateGitHubHandle(gitHubHandle); + } + } +} From 24d6be432430da3f3ecd2348a46fb7e630e207b3 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sat, 9 Mar 2024 00:36:09 +0900 Subject: [PATCH 11/27] =?UTF-8?q?feat=20:=20SecurityConfig=EC=97=90=20OAut?= =?UTF-8?q?h2=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java index 6b12005..e9ae0eb 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java +++ b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java @@ -2,7 +2,8 @@ import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; -import gdsc.binaryho.imhere.security.filter.JwtAuthenticationFilter; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2SuccessHandler; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2UserService; import gdsc.binaryho.imhere.security.filter.JwtAuthorizationFilter; import gdsc.binaryho.imhere.security.jwt.TokenService; import lombok.RequiredArgsConstructor; @@ -10,6 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -22,7 +24,7 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.channel.ChannelProcessingFilter; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.filter.CorsFilter; @@ -35,6 +37,8 @@ public class SecurityConfig { private final MemberRepository memberRepository; private final CorsFilter corsFilter; + private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; + private final CustomOAuth2UserService customOAuth2UserService; private final TokenService tokenService; @@ -89,8 +93,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin().disable() .httpBasic().disable() - .authorizeRequests() + .oauth2Login(configurer -> { + configurer.userInfoEndpoint(endpoint -> endpoint.userService(customOAuth2UserService)); + configurer.successHandler(customOAuth2SuccessHandler); + configurer.failureHandler(setStatusUnauthorized()); + } + ) + .authorizeRequests() .antMatchers("/login", "/logout", "/member/**", "/swagger*/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**") .permitAll() @@ -103,9 +113,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().authenticated(); - http.addFilterBefore(new JwtAuthenticationFilter( - authenticationManager(authenticationConfiguration), tokenService), UsernamePasswordAuthenticationFilter.class); - http.addFilterBefore(new JwtAuthorizationFilter( authenticationManager(authenticationConfiguration), tokenService, memberRepository), BasicAuthenticationFilter.class); @@ -121,4 +128,9 @@ private UserDetailsService getActuatorUserDetailsService() { return new InMemoryUserDetailsManager(userDetails); } + + private AuthenticationFailureHandler setStatusUnauthorized() { + int unauthorized = HttpStatus.UNAUTHORIZED.value(); + return (request, response, exception) -> response.setStatus(unauthorized); + } } From e0280802d7a99d080093529644d5b9de07860f9d Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 10 Mar 2024 21:41:12 +0900 Subject: [PATCH 12/27] =?UTF-8?q?chore=20:=20=EC=A7=80=EC=9A=B8=20?= =?UTF-8?q?=EC=98=88=EC=A0=95=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=91=9C?= =?UTF-8?q?=EC=84=9C=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/filter/JwtAuthenticationFilter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java index f313046..ebbc5e8 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java @@ -20,6 +20,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +// TODO : 사용하지 않을 예정인 클래스 @RequiredArgsConstructor public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @@ -59,7 +60,12 @@ private SignInRequest getSignInRequest(ServletInputStream inputStream) throws IO public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { - String grantedAuthority = authResult.getAuthorities().stream().findAny().orElseThrow().toString(); + String grantedAuthority = authResult.getAuthorities() + .stream() + .findAny() + .orElseThrow() + .toString(); + Token jwtToken = tokenService.createToken(authResult.getPrincipal().toString(), grantedAuthority); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); From 43a3fcd62f8640b5db736ec8f3d9b2620cb3ed9d Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 10 Mar 2024 21:42:54 +0900 Subject: [PATCH 13/27] =?UTF-8?q?feat=20:=20github=20oauth2=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 8 ++++++++ src/main/resources/application-prod.yml | 8 ++++++++ src/main/resources/application.yml | 8 ++++++++ src/test/resources/application.yml | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f453f60..89e7146 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -27,6 +27,14 @@ spring: hibernate: format_sql: true + security: + oauth2: + client: + registration: + github: + client-id: ENC(cwYhhVQPF7EA6AsPSNX7BkV4o1Q+kWM1SqFh6SfDoxQ=) + client-secret: ENC(sI6HRt2XDATOJWZvrUqzqrdtO++dR9ttm69l3hwsJX8=) + logging.level: org.hibernate.SQL: debug diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index e32ade5..dd3b80a 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -23,6 +23,14 @@ spring: ddl-auto: none dialect: -- org.hibernate.dialect.PostgreSQLDialect + security: + oauth2: + client: + registration: + github: + client-id: ENC(18FNZ6cjSHBH889DgERn3ZscMgAY8R02b6DBg9l47Xs=) + client-secret: ENC(hUvBA0g45hgzXQ2Ia/ovO8dQWDR67BsVt6Um0h1cSh/qrwKaWdVND06XApXIJMSaZy5Kem6nJx4=) + admin: univ-id: END(MnnJJNaW4by9rR1oo90AfA==) name: END(74r3AHMoJBHegbbayNVTRbM3GACH/df5) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b404cda..18bc924 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,14 @@ spring: jpa: open-in-view: false + security: + oauth2: + client: + registration: + github: + client-id: ENC(18FNZ6cjSHBH889DgERn3ZscMgAY8R02b6DBg9l47Xs=) + client-secret: ENC(hUvBA0g45hgzXQ2Ia/ovO8dQWDR67BsVt6Um0h1cSh/qrwKaWdVND06XApXIJMSaZy5Kem6nJx4=) + jwt: secret: ENC(Wi5BD1R9VNidIJj2xI10Ww==) prefix: "Bearer " diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index b834b61..abc80df 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -11,6 +11,14 @@ spring: profiles: active: test + security: + oauth2: + client: + registration: + github: + client-id: ENC(cwYhhVQPF7EA6AsPSNX7BkV4o1Q+kWM1SqFh6SfDoxQ=) + client-secret: ENC(sI6HRt2XDATOJWZvrUqzqrdtO++dR9ttm69l3hwsJX8=) + jpa: open-in-view: false From e17b1f95d3cc0c56acc57adbb5e5b94c0ad994c1 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 01:09:23 +0900 Subject: [PATCH 14/27] refactor : oauth login path permission (#112) --- .../gdsc/binaryho/imhere/security/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java index e9ae0eb..457f6f5 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java +++ b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java @@ -101,7 +101,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .authorizeRequests() - .antMatchers("/login", "/logout", "/member/**", + .antMatchers("/login/**", "/logout", "/member/**", "/swagger*/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**") .permitAll() From 6b7553091c1b0d588160359b0a13f5e7e25070d6 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 01:10:39 +0900 Subject: [PATCH 15/27] =?UTF-8?q?refactor=20:=20JwtAuthorizationFilter=20?= =?UTF-8?q?=EB=8B=A4=EC=96=91=ED=95=9C=20=EA=B0=80=EB=8F=85=EC=84=B1=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/JwtAuthorizationFilter.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java index c04323e..624ef3a 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java @@ -1,10 +1,12 @@ package gdsc.binaryho.imhere.security.filter; +import gdsc.binaryho.imhere.core.auth.exception.MemberNotFoundException; import gdsc.binaryho.imhere.core.member.Member; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.TokenService; import gdsc.binaryho.imhere.security.principal.PrincipalDetails; import java.io.IOException; +import java.util.Objects; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -18,7 +20,7 @@ public class JwtAuthorizationFilter extends BasicAuthenticationFilter { - private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; + private static final String TOKEN_HEADER_STRING = HttpHeaders.AUTHORIZATION; private static final String ACCESS_TOKEN_PREFIX = "Token "; private final TokenService tokenService; @@ -35,34 +37,33 @@ public JwtAuthorizationFilter( @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - if (isNullToken(request)) { + String jwtToken = request.getHeader(TOKEN_HEADER_STRING); + if (isTokenNullOrInvalidate(jwtToken)) { chain.doFilter(request, response); return; } - String jwtToken = request.getHeader(HEADER_STRING) - .replace(ACCESS_TOKEN_PREFIX, ""); - - if (tokenService.validateTokenExpirationTimeNotExpired(jwtToken)) { - - String univId = tokenService.getUnivId(jwtToken); - Member member = memberRepository.findByUnivId(univId).orElseThrow(); - PrincipalDetails principalDetails = new PrincipalDetails(member); - - Authentication authentication = - new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); - - SecurityContextHolder.getContext().setAuthentication(authentication); + String tokenValue = jwtToken.replace(ACCESS_TOKEN_PREFIX, ""); + if (tokenService.validateTokenExpirationTimeNotExpired(tokenValue)) { + setAuthentication(tokenValue); } - chain.doFilter(request, response); } - private boolean isNullToken(HttpServletRequest request) { - String jwtHeader = request.getHeader(HEADER_STRING); - if (jwtHeader == null || !jwtHeader.startsWith(ACCESS_TOKEN_PREFIX)) { - return true; - } - return false; + private boolean isTokenNullOrInvalidate(String token) { + return Objects.isNull(token) + || (!token.startsWith(ACCESS_TOKEN_PREFIX)); + } + + private void setAuthentication(String jwtToken) { + String univId = tokenService.getUnivId(jwtToken); + Member member = memberRepository.findByUnivId(univId) + .orElseThrow(() -> MemberNotFoundException.EXCEPTION); + + PrincipalDetails principalDetails = new PrincipalDetails(member); + Authentication authentication = + new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); + + SecurityContextHolder.getContext().setAuthentication(authentication); } } From ad5bf9508267a3bf22308d46ce692cb9a4d9f1db Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 01:22:52 +0900 Subject: [PATCH 16/27] =?UTF-8?q?refactor=20:=20token=20service=EC=97=90?= =?UTF-8?q?=20id=EB=A5=BC=20parsing=20=ED=95=98=EB=8A=94=20getId=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gdsc/binaryho/imhere/security/jwt/TokenService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java index 87acb14..ea52e77 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java @@ -51,12 +51,15 @@ public boolean validateTokenExpirationTimeNotExpired(String token) { } } - public String getUnivId(String token) { - return Jwts.parser() + public Long getId(String token) { + String subject = Jwts.parser() .setSigningKey(secretHolder.getSecret()) .parseClaimsJws(token) .getBody() .getSubject(); + + // TODO : Exception Check + return Long.parseLong(subject); } private Token createToken(Claims claims, String roleKey) { From 548e2a110fa1a5792a2b6c4d8178b1efa73fcd96 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 01:24:11 +0900 Subject: [PATCH 17/27] =?UTF-8?q?test=20:=20token=20service=20getId=20test?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/jwt/TokenServiceTest.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java index 374b2dd..ffa8dec 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java @@ -27,11 +27,6 @@ public class TokenServiceTest { private static final long ACCESS_TOKEN_EXPIRATION_TIME = 1000L * 60L * 20L; private static final long TIME_NOW = FixedSeoulTimeHolder.FIXED_MILLISECONDS; -// public static long getTimeNowByMillis() { -// ZonedDateTime seoulTimeNow = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); -// return seoulTimeNow.toInstant().toEpochMilli(); -// } - SecretHolder secretHolder = new TestSecretHolder(SECRET); TokenService tokenService = new TokenService(secretHolder, new FixedSeoulTimeHolder()); @@ -46,12 +41,6 @@ public class TokenServiceTest { JsonNode payloadJson = new ObjectMapper().readTree(payload); String subject = payloadJson.get("sub").asText(); String role = payloadJson.get("role").asText(); -// Claims claims = Jwts.parser() -// .setSigningKey(secretHolder.getSecret()) -// .set -// .setAllowedClockSkewSeconds(Long.MAX_VALUE) -// .parseClaimsJws(accessToken) -// .getBody(); assertAll( () -> assertThat(subject).isEqualTo(UNIV_ID), @@ -59,6 +48,25 @@ public class TokenServiceTest { ); } + @Test + void 맴버_아이디와_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { + Long id = MOCK_STUDENT.getId(); + Token token = tokenService.createToken(id, ROLE); + String accessToken = token.getAccessToken(); + + String[] splitToken = accessToken.split("\\."); + String payload = new String(Base64.getDecoder().decode(splitToken[1])); + System.out.println(payload); + JsonNode payloadJson = new ObjectMapper().readTree(payload); + Long subject = payloadJson.get("sub").asLong(); + String role = payloadJson.get("role").asText(); + + assertAll( + () -> assertThat(subject).isEqualTo(id), + () -> assertThat(role).isEqualTo(ROLE.getKey()) + ); + } + @Test void 토큰의_유효_시간의_만료_여부를_확인할_수_있다() { // given From 05e79f08f265d6b2861886d0ba02ed710e2bf2ed Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 01:26:10 +0900 Subject: [PATCH 18/27] =?UTF-8?q?refactor=20:=20JwtAuthenticationFilter?= =?UTF-8?q?=EC=97=90=20=ED=86=A0=ED=81=B0=20parse=20ById=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/filter/JwtAuthorizationFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java index 624ef3a..07c1335 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java @@ -56,8 +56,8 @@ private boolean isTokenNullOrInvalidate(String token) { } private void setAuthentication(String jwtToken) { - String univId = tokenService.getUnivId(jwtToken); - Member member = memberRepository.findByUnivId(univId) + Long id = tokenService.getId(jwtToken); + Member member = memberRepository.findById(id) .orElseThrow(() -> MemberNotFoundException.EXCEPTION); PrincipalDetails principalDetails = new PrincipalDetails(member); From b6ecacb3b9bb6f02d3e267e7343603bbe1778224 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 17:24:00 +0900 Subject: [PATCH 19/27] =?UTF-8?q?test=20:=20SecurityConfig=20Test=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/SecurityConfigTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java new file mode 100644 index 0000000..54e2cdb --- /dev/null +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -0,0 +1,83 @@ +package gdsc.binaryho.imhere.security; + +import static gdsc.binaryho.imhere.mock.fixture.MemberFixture.MOCK_STUDENT; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import gdsc.binaryho.imhere.core.member.Role; +import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; +import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenService; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +@AutoConfigureMockMvc +public class SecurityConfigTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private TokenService tokenService; + + @MockBean + private MemberRepository memberRepository; + + @Mock + private DefaultOAuth2UserService defaultOAuth2UserService; + + private static final String ACCESS_TOKEN_PREFIX = "Token "; + + @Test + public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string("Location", containsString("/oauth2/authorization/github"))); + } + + @Test + public void 토큰을_통해_인가_할_수_있다() throws Exception { + given(memberRepository.findById(any())) + .willReturn(Optional.of(MOCK_STUDENT)); + Token token = tokenService.createToken(1L, Role.STUDENT); + + mockMvc.perform(get("/api/lecture") + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN_PREFIX + token.getAccessToken()) + ) + .andExpect(status().is2xxSuccessful()); + } + + @Test + public void 권한이_없는_토큰_요청은_403_응답을_반환한다() throws Exception { + given(memberRepository.findById(any())) + .willReturn(Optional.of(MOCK_STUDENT)); + Token token = tokenService.createToken(1L, Role.STUDENT); + + mockMvc.perform(post("/api/admin/role/1") + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN_PREFIX + token.getAccessToken()) + ) + .andExpect(status().isForbidden()); + } +} From ef23d2a1e175e5a1a2b5093f5d4b9419f01b35f9 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 20:26:21 +0900 Subject: [PATCH 20/27] =?UTF-8?q?refactor=20:=20Token=20=EB=A7=8C=EB=A3=8C?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=EC=9D=84=20=ED=8F=AC=ED=95=A8=ED=95=9C=20?= =?UTF-8?q?Property=EB=A5=BC=20TokenPropertyHolder=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=97=AD=EC=A0=84=ED=95=98=EC=97=AC=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 13 +++++--- .../filter/JwtAuthenticationFilter.java | 6 ++-- .../filter/JwtAuthorizationFilter.java | 19 +++++++---- .../security/jwt/ImhereSecretHolder.java | 16 --------- .../jwt/ImhereTokenPropertyHolder.java | 33 +++++++++++++++++++ .../imhere/security/jwt/SecretHolder.java | 6 ---- .../security/jwt/TokenPropertyHolder.java | 12 +++++++ .../imhere/security/jwt/TokenService.java | 16 +++++---- .../oauth/CustomOAuth2SuccessHandler.java | 9 +++-- src/main/resources/application.yml | 3 +- .../imhere/mock/FakeTokenPropertyHolder.java | 31 +++++++++++++++++ .../imhere/mock/FixedSeoulTimeHolder.java | 2 -- .../imhere/mock/TestSecretHolder.java | 17 ---------- .../imhere/security/SecurityConfigTest.java | 15 ++++----- .../imhere/security/jwt/TokenServiceTest.java | 12 +++---- src/test/resources/application.yml | 9 ++--- 16 files changed, 138 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java create mode 100644 src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java delete mode 100644 src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java create mode 100644 src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java create mode 100644 src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java delete mode 100644 src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java diff --git a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java index 457f6f5..cb7e34e 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java +++ b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java @@ -2,10 +2,11 @@ import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; -import gdsc.binaryho.imhere.security.oauth.CustomOAuth2SuccessHandler; -import gdsc.binaryho.imhere.security.oauth.CustomOAuth2UserService; import gdsc.binaryho.imhere.security.filter.JwtAuthorizationFilter; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2SuccessHandler; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -41,6 +42,7 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; private final TokenService tokenService; + private final TokenPropertyHolder tokenPropertyHolder; @Value("${actuator.username}") private String ACTUATOR_USERNAME; @@ -94,7 +96,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic().disable() .oauth2Login(configurer -> { - configurer.userInfoEndpoint(endpoint -> endpoint.userService(customOAuth2UserService)); + configurer.userInfoEndpoint( + endpoint -> endpoint.userService(customOAuth2UserService)); configurer.successHandler(customOAuth2SuccessHandler); configurer.failureHandler(setStatusUnauthorized()); } @@ -114,7 +117,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().authenticated(); http.addFilterBefore(new JwtAuthorizationFilter( - authenticationManager(authenticationConfiguration), tokenService, memberRepository), BasicAuthenticationFilter.class); + authenticationManager(authenticationConfiguration), + tokenService, memberRepository, tokenPropertyHolder), + BasicAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java index ebbc5e8..aa966dc 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenService; import java.io.IOException; import javax.servlet.FilterChain; @@ -25,10 +26,10 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; - private static final String ACCESS_TOKEN_PREFIX = "Token "; private final AuthenticationManager authenticationManager; private final TokenService tokenService; + private final TokenPropertyHolder tokenPropertyHolder; @Override public Authentication attemptAuthentication( @@ -68,8 +69,9 @@ public void successfulAuthentication(HttpServletRequest request, Token jwtToken = tokenService.createToken(authResult.getPrincipal().toString(), grantedAuthority); + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); - response.addHeader(HEADER_STRING, ACCESS_TOKEN_PREFIX + jwtToken.getAccessToken()); + response.addHeader(HEADER_STRING, accessTokenPrefix + jwtToken.getAccessToken()); } @Override diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java index 07c1335..bd2e165 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java @@ -3,6 +3,7 @@ import gdsc.binaryho.imhere.core.auth.exception.MemberNotFoundException; import gdsc.binaryho.imhere.core.member.Member; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenService; import gdsc.binaryho.imhere.security.principal.PrincipalDetails; import java.io.IOException; @@ -21,21 +22,24 @@ public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private static final String TOKEN_HEADER_STRING = HttpHeaders.AUTHORIZATION; - private static final String ACCESS_TOKEN_PREFIX = "Token "; private final TokenService tokenService; private final MemberRepository memberRepository; + private final TokenPropertyHolder tokenPropertyHolder; public JwtAuthorizationFilter( AuthenticationManager authenticationManager, - TokenService tokenService, MemberRepository memberRepository) { + TokenService tokenService, MemberRepository memberRepository, + TokenPropertyHolder tokenPropertyHolder) { super(authenticationManager); this.tokenService = tokenService; this.memberRepository = memberRepository; + this.tokenPropertyHolder = tokenPropertyHolder; } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { String jwtToken = request.getHeader(TOKEN_HEADER_STRING); if (isTokenNullOrInvalidate(jwtToken)) { @@ -43,7 +47,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String tokenValue = jwtToken.replace(ACCESS_TOKEN_PREFIX, ""); + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); + String tokenValue = jwtToken.replace(accessTokenPrefix, ""); if (tokenService.validateTokenExpirationTimeNotExpired(tokenValue)) { setAuthentication(tokenValue); } @@ -51,8 +56,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } private boolean isTokenNullOrInvalidate(String token) { + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); return Objects.isNull(token) - || (!token.startsWith(ACCESS_TOKEN_PREFIX)); + || (!token.startsWith(accessTokenPrefix)); } private void setAuthentication(String jwtToken) { @@ -62,7 +68,8 @@ private void setAuthentication(String jwtToken) { PrincipalDetails principalDetails = new PrincipalDetails(member); Authentication authentication = - new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); + new UsernamePasswordAuthenticationToken(principalDetails, "", + principalDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java deleted file mode 100644 index 4c2f9fc..0000000 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java +++ /dev/null @@ -1,16 +0,0 @@ -package gdsc.binaryho.imhere.security.jwt; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class ImhereSecretHolder implements SecretHolder { - - @Value("${jwt.access-token-prefix}") - private String SECRET; - - @Override - public String getSecret() { - return SECRET; - } -} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java new file mode 100644 index 0000000..5510240 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java @@ -0,0 +1,33 @@ +package gdsc.binaryho.imhere.security.jwt; + +import java.time.Duration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ImhereTokenPropertyHolder implements TokenPropertyHolder { + + @Value("${token.secret}") + private String secret; + + @Value("${token.access-token-expire-minute}") + private Integer accessTokenExpireMinute; + + @Value("${token.access-token-prefix}") + private String accessTokenPrefix; + + @Override + public String getSecret() { + return secret; + } + + @Override + public Duration getAccessTokenExpiration() { + return Duration.ofMinutes(accessTokenExpireMinute); + } + + @Override + public String getAccessTokenPrefix() { + return accessTokenPrefix; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java deleted file mode 100644 index 085f0f3..0000000 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java +++ /dev/null @@ -1,6 +0,0 @@ -package gdsc.binaryho.imhere.security.jwt; - -public interface SecretHolder { - - String getSecret(); -} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java new file mode 100644 index 0000000..d8dca34 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java @@ -0,0 +1,12 @@ +package gdsc.binaryho.imhere.security.jwt; + +import java.time.Duration; + +public interface TokenPropertyHolder { + + String getSecret(); + + Duration getAccessTokenExpiration(); + + String getAccessTokenPrefix(); +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java index ea52e77..1745852 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java @@ -18,11 +18,10 @@ @RequiredArgsConstructor public class TokenService { - private final SecretHolder secretHolder; private final SeoulDateTimeHolder seoulDateTimeHolder; + private final TokenPropertyHolder tokenPropertyHolder; private static final String ROLE_KEY = "role"; - private static final Duration ACCESS_TOKEN_EXPIRATION_TIME = Duration.ofMinutes(30); // TODO : 기존 토큰 생성 방식은 삭제할 예정 public Token createToken(String univId, String roleKey) { @@ -52,8 +51,9 @@ public boolean validateTokenExpirationTimeNotExpired(String token) { } public Long getId(String token) { + String tokenSecret = tokenPropertyHolder.getSecret(); String subject = Jwts.parser() - .setSigningKey(secretHolder.getSecret()) + .setSigningKey(tokenSecret) .parseClaimsJws(token) .getBody() .getSubject(); @@ -75,17 +75,21 @@ private Boolean isNullOrEmpty(String token) { private String buildJwtToken(Claims claims) { long seoulTimeNow = seoulDateTimeHolder.getSeoulMilliseconds(); + Duration accessTokenExpiration = tokenPropertyHolder.getAccessTokenExpiration(); + String tokenSecret = tokenPropertyHolder.getSecret(); + return Jwts.builder() .setClaims(claims) .setIssuedAt(new Date(seoulTimeNow)) - .setExpiration(new Date(seoulTimeNow + ACCESS_TOKEN_EXPIRATION_TIME.toMillis())) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .setExpiration(new Date(seoulTimeNow + accessTokenExpiration.toMillis())) + .signWith(SignatureAlgorithm.HS256, tokenSecret) .compact(); } private void parseToValidateToken(String token) { + String tokenSecret = tokenPropertyHolder.getSecret(); Jwts.parser() - .setSigningKey(secretHolder.getSecret()) + .setSigningKey(tokenSecret) .parseClaimsJws(token); } } diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java index d056b1a..f7a905a 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java @@ -2,6 +2,7 @@ import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenService; import gdsc.binaryho.imhere.util.ClientUrlUtil; import java.io.IOException; @@ -18,10 +19,10 @@ public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; - private static final String ACCESS_TOKEN_PREFIX = "Token "; private final TokenService tokenService; private final ClientUrlUtil clientUrlUtil; + private final TokenPropertyHolder tokenPropertyHolder; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, @@ -39,8 +40,10 @@ private void setRedirectUrl(HttpServletRequest request, HttpServletResponse resp } private void setAccessToken(HttpServletResponse response, CustomOAuth2User oAuthUser) { - Token jwtToken = tokenService.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); - response.addHeader(HEADER_STRING, ACCESS_TOKEN_PREFIX + jwtToken.getAccessToken()); + + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); + Token jwtToken = tokenService.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); + response.addHeader(HEADER_STRING, accessTokenPrefix + jwtToken.getAccessToken()); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 18bc924..c1c7261 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,11 +15,12 @@ spring: client-id: ENC(18FNZ6cjSHBH889DgERn3ZscMgAY8R02b6DBg9l47Xs=) client-secret: ENC(hUvBA0g45hgzXQ2Ia/ovO8dQWDR67BsVt6Um0h1cSh/qrwKaWdVND06XApXIJMSaZy5Kem6nJx4=) -jwt: +token: secret: ENC(Wi5BD1R9VNidIJj2xI10Ww==) prefix: "Bearer " header-string: "Authorization" access-token-prefix: "Token " + access-token-expire-minute: 30 # SMTP mail: diff --git a/src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java b/src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java new file mode 100644 index 0000000..73bb2b9 --- /dev/null +++ b/src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java @@ -0,0 +1,31 @@ +package gdsc.binaryho.imhere.mock; + +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import java.time.Duration; + +public class FakeTokenPropertyHolder implements TokenPropertyHolder { + + private final String secret; + private final Duration accessTokenExpiration; + + + public FakeTokenPropertyHolder(String secret, Duration accessTokenExpiration) { + this.secret = secret; + this.accessTokenExpiration = accessTokenExpiration; + } + + @Override + public String getSecret() { + return secret; + } + + @Override + public Duration getAccessTokenExpiration() { + return accessTokenExpiration; + } + + @Override + public String getAccessTokenPrefix() { + return "prefix"; + } +} diff --git a/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java b/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java index 41d3634..0248db2 100644 --- a/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java +++ b/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java @@ -7,8 +7,6 @@ public class FixedSeoulTimeHolder implements SeoulDateTimeHolder { public static final LocalDateTime FIXED_LOCAL_DATE_TIME = LocalDateTime.now(); -// LocalDateTime -// .of(2023, Month.MAY, 6, 11, 0, 0); public static final Long FIXED_MILLISECONDS = FIXED_LOCAL_DATE_TIME .toInstant(ZoneOffset.UTC).toEpochMilli(); diff --git a/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java b/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java deleted file mode 100644 index ab8795f..0000000 --- a/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java +++ /dev/null @@ -1,17 +0,0 @@ -package gdsc.binaryho.imhere.mock; - -import gdsc.binaryho.imhere.security.jwt.SecretHolder; - -public class TestSecretHolder implements SecretHolder { - - private final String secret; - - public TestSecretHolder(String secret) { - this.secret = secret; - } - - @Override - public String getSecret() { - return secret; - } -} diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index 54e2cdb..1ca88ee 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -12,11 +12,11 @@ import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenService; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -24,7 +24,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.test.web.servlet.MockMvc; @SpringBootTest @@ -41,10 +40,8 @@ public class SecurityConfigTest { @MockBean private MemberRepository memberRepository; - @Mock - private DefaultOAuth2UserService defaultOAuth2UserService; - - private static final String ACCESS_TOKEN_PREFIX = "Token "; + @Autowired + private TokenPropertyHolder tokenPropertyHolder; @Test public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { @@ -61,9 +58,10 @@ public class SecurityConfigTest { .willReturn(Optional.of(MOCK_STUDENT)); Token token = tokenService.createToken(1L, Role.STUDENT); + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(get("/api/lecture") .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN_PREFIX + token.getAccessToken()) + .header(HttpHeaders.AUTHORIZATION, accessTokenPrefix + token.getAccessToken()) ) .andExpect(status().is2xxSuccessful()); } @@ -74,9 +72,10 @@ public class SecurityConfigTest { .willReturn(Optional.of(MOCK_STUDENT)); Token token = tokenService.createToken(1L, Role.STUDENT); + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(post("/api/admin/role/1") .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN_PREFIX + token.getAccessToken()) + .header(HttpHeaders.AUTHORIZATION, accessTokenPrefix + token.getAccessToken()) ) .andExpect(status().isForbidden()); } diff --git a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java index ffa8dec..3ca2899 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.mock.FixedSeoulTimeHolder; -import gdsc.binaryho.imhere.mock.TestSecretHolder; +import gdsc.binaryho.imhere.mock.FakeTokenPropertyHolder; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -27,8 +27,8 @@ public class TokenServiceTest { private static final long ACCESS_TOKEN_EXPIRATION_TIME = 1000L * 60L * 20L; private static final long TIME_NOW = FixedSeoulTimeHolder.FIXED_MILLISECONDS; - SecretHolder secretHolder = new TestSecretHolder(SECRET); - TokenService tokenService = new TokenService(secretHolder, new FixedSeoulTimeHolder()); + TokenPropertyHolder tokenPropertyHolder = new FakeTokenPropertyHolder(SECRET, Duration.ofDays(999L)); + TokenService tokenService = new TokenService(new FixedSeoulTimeHolder(), tokenPropertyHolder); @Test void 이메일과_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { @@ -77,7 +77,7 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW + ACCESS_TOKEN_EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); @@ -98,7 +98,7 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW + ACCESS_TOKEN_EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); @@ -119,7 +119,7 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW - Duration.ofDays(7777L).toMillis())) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index abc80df..ef93ca7 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -38,11 +38,12 @@ spring: port: 6378 password: ENC(t9qeZ6WjZb8nTCX6USgT0Q==) -jwt: +token: secret: ENC(Wi5BD1R9VNidIJj2xI10Ww==) - prefix: "Bearer " - header-string: "Authorization" - access-token-prefix: "Token " + prefix: "" + header-string: "" + access-token-prefix: "" + access-token-expire-minute: 1 admin: univ-id: ENC(MnnJJNaW4by9rR1oo90AfA==) From 12bc99bde2649b926b1d3f21717cd468e33e9563 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 20:36:14 +0900 Subject: [PATCH 21/27] =?UTF-8?q?refactor=20:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EA=B3=BC=20=EC=86=8C=ED=86=B5=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20TokenService=EC=9D=98=20=EC=9D=B4=EB=A6=84=EC=9D=84?= =?UTF-8?q?=20TokenUtil=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 6 +++--- .../filter/JwtAuthenticationFilter.java | 6 +++--- .../filter/JwtAuthorizationFilter.java | 12 +++++------ .../jwt/{TokenService.java => TokenUtil.java} | 6 +++--- .../oauth/CustomOAuth2SuccessHandler.java | 6 +++--- .../imhere/security/SecurityConfigTest.java | 8 ++++---- ...kenServiceTest.java => TokenUtilTest.java} | 20 +++++++++---------- src/test/resources/application.yml | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) rename src/main/java/gdsc/binaryho/imhere/security/jwt/{TokenService.java => TokenUtil.java} (97%) rename src/test/java/gdsc/binaryho/imhere/security/jwt/{TokenServiceTest.java => TokenUtilTest.java} (87%) diff --git a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java index cb7e34e..070b2ee 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java +++ b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java @@ -4,7 +4,7 @@ import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.filter.JwtAuthorizationFilter; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import gdsc.binaryho.imhere.security.oauth.CustomOAuth2SuccessHandler; import gdsc.binaryho.imhere.security.oauth.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; @@ -41,7 +41,7 @@ public class SecurityConfig { private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; private final CustomOAuth2UserService customOAuth2UserService; - private final TokenService tokenService; + private final TokenUtil tokenUtil; private final TokenPropertyHolder tokenPropertyHolder; @Value("${actuator.username}") @@ -118,7 +118,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.addFilterBefore(new JwtAuthorizationFilter( authenticationManager(authenticationConfiguration), - tokenService, memberRepository, tokenPropertyHolder), + tokenUtil, memberRepository, tokenPropertyHolder), BasicAuthenticationFilter.class); return http.build(); diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java index aa966dc..5fc9922 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.security.jwt.Token; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -28,7 +28,7 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; private final AuthenticationManager authenticationManager; - private final TokenService tokenService; + private final TokenUtil tokenUtil; private final TokenPropertyHolder tokenPropertyHolder; @Override @@ -67,7 +67,7 @@ public void successfulAuthentication(HttpServletRequest request, .orElseThrow() .toString(); - Token jwtToken = tokenService.createToken(authResult.getPrincipal().toString(), grantedAuthority); + Token jwtToken = tokenUtil.createToken(authResult.getPrincipal().toString(), grantedAuthority); String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java index bd2e165..291c3a6 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java @@ -4,7 +4,7 @@ import gdsc.binaryho.imhere.core.member.Member; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import gdsc.binaryho.imhere.security.principal.PrincipalDetails; import java.io.IOException; import java.util.Objects; @@ -23,16 +23,16 @@ public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private static final String TOKEN_HEADER_STRING = HttpHeaders.AUTHORIZATION; - private final TokenService tokenService; + private final TokenUtil tokenUtil; private final MemberRepository memberRepository; private final TokenPropertyHolder tokenPropertyHolder; public JwtAuthorizationFilter( AuthenticationManager authenticationManager, - TokenService tokenService, MemberRepository memberRepository, + TokenUtil tokenUtil, MemberRepository memberRepository, TokenPropertyHolder tokenPropertyHolder) { super(authenticationManager); - this.tokenService = tokenService; + this.tokenUtil = tokenUtil; this.memberRepository = memberRepository; this.tokenPropertyHolder = tokenPropertyHolder; } @@ -49,7 +49,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); String tokenValue = jwtToken.replace(accessTokenPrefix, ""); - if (tokenService.validateTokenExpirationTimeNotExpired(tokenValue)) { + if (tokenUtil.validateTokenExpirationTimeNotExpired(tokenValue)) { setAuthentication(tokenValue); } chain.doFilter(request, response); @@ -62,7 +62,7 @@ private boolean isTokenNullOrInvalidate(String token) { } private void setAuthentication(String jwtToken) { - Long id = tokenService.getId(jwtToken); + Long id = tokenUtil.getId(jwtToken); Member member = memberRepository.findById(id) .orElseThrow(() -> MemberNotFoundException.EXCEPTION); diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java similarity index 97% rename from src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java rename to src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java index 1745852..63a5b85 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java @@ -11,12 +11,12 @@ import java.util.Date; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; @Log4j2 -@Service +@Component @RequiredArgsConstructor -public class TokenService { +public class TokenUtil { private final SeoulDateTimeHolder seoulDateTimeHolder; private final TokenPropertyHolder tokenPropertyHolder; diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java index f7a905a..67fd348 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java @@ -3,7 +3,7 @@ import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; import gdsc.binaryho.imhere.security.jwt.Token; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import gdsc.binaryho.imhere.util.ClientUrlUtil; import java.io.IOException; import javax.servlet.http.HttpServletRequest; @@ -20,7 +20,7 @@ public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHa private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; - private final TokenService tokenService; + private final TokenUtil tokenUtil; private final ClientUrlUtil clientUrlUtil; private final TokenPropertyHolder tokenPropertyHolder; @@ -43,7 +43,7 @@ private void setAccessToken(HttpServletResponse response, CustomOAuth2User oAuth response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); - Token jwtToken = tokenService.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); + Token jwtToken = tokenUtil.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); response.addHeader(HEADER_STRING, accessTokenPrefix + jwtToken.getAccessToken()); } } diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index 1ca88ee..53278a3 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -13,7 +13,7 @@ import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.Token; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,7 +35,7 @@ public class SecurityConfigTest { private MockMvc mockMvc; @Autowired - private TokenService tokenService; + private TokenUtil tokenUtil; @MockBean private MemberRepository memberRepository; @@ -56,7 +56,7 @@ public class SecurityConfigTest { public void 토큰을_통해_인가_할_수_있다() throws Exception { given(memberRepository.findById(any())) .willReturn(Optional.of(MOCK_STUDENT)); - Token token = tokenService.createToken(1L, Role.STUDENT); + Token token = tokenUtil.createToken(1L, Role.LECTURER); String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(get("/api/lecture") @@ -70,7 +70,7 @@ public class SecurityConfigTest { public void 권한이_없는_토큰_요청은_403_응답을_반환한다() throws Exception { given(memberRepository.findById(any())) .willReturn(Optional.of(MOCK_STUDENT)); - Token token = tokenService.createToken(1L, Role.STUDENT); + Token token = tokenUtil.createToken(1L, Role.STUDENT); String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(post("/api/admin/role/1") diff --git a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java similarity index 87% rename from src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java rename to src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java index 3ca2899..a942734 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java @@ -20,7 +20,7 @@ import java.util.UUID; import org.junit.jupiter.api.Test; -public class TokenServiceTest { +public class TokenUtilTest { private static final Role ROLE = MOCK_STUDENT.getRole(); private static final String SECRET = "TEST_SECRET"; @@ -28,11 +28,11 @@ public class TokenServiceTest { private static final long TIME_NOW = FixedSeoulTimeHolder.FIXED_MILLISECONDS; TokenPropertyHolder tokenPropertyHolder = new FakeTokenPropertyHolder(SECRET, Duration.ofDays(999L)); - TokenService tokenService = new TokenService(new FixedSeoulTimeHolder(), tokenPropertyHolder); + TokenUtil tokenUtil = new TokenUtil(new FixedSeoulTimeHolder(), tokenPropertyHolder); @Test void 이메일과_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { - Token token = tokenService.createToken(UNIV_ID, ROLE.getKey()); + Token token = tokenUtil.createToken(UNIV_ID, ROLE.getKey()); String accessToken = token.getAccessToken(); String[] splitToken = accessToken.split("\\."); @@ -51,7 +51,7 @@ public class TokenServiceTest { @Test void 맴버_아이디와_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { Long id = MOCK_STUDENT.getId(); - Token token = tokenService.createToken(id, ROLE); + Token token = tokenUtil.createToken(id, ROLE); String accessToken = token.getAccessToken(); String[] splitToken = accessToken.split("\\."); @@ -84,7 +84,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isNotNull(); } @@ -105,7 +105,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isTrue(); } @@ -126,7 +126,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -138,7 +138,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -150,7 +150,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -162,7 +162,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index ef93ca7..61325af 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -43,7 +43,7 @@ token: prefix: "" header-string: "" access-token-prefix: "" - access-token-expire-minute: 1 + access-token-expire-minute: 9999 admin: univ-id: ENC(MnnJJNaW4by9rR1oo90AfA==) From a877b25a0f1f3f489b35f1b5a0a4f681e98830c8 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 20:38:47 +0900 Subject: [PATCH 22/27] =?UTF-8?q?test=20:=20Token=20Member=20Id=EB=A5=BC?= =?UTF-8?q?=20Fixture=EC=9D=98=20id=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gdsc/binaryho/imhere/security/SecurityConfigTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index 53278a3..4b9600f 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -56,7 +56,7 @@ public class SecurityConfigTest { public void 토큰을_통해_인가_할_수_있다() throws Exception { given(memberRepository.findById(any())) .willReturn(Optional.of(MOCK_STUDENT)); - Token token = tokenUtil.createToken(1L, Role.LECTURER); + Token token = tokenUtil.createToken(MOCK_STUDENT.getId(), Role.LECTURER); String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(get("/api/lecture") From 0102f772fba1d8561d02f1d9aaa730e1c15562cb Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 20:58:53 +0900 Subject: [PATCH 23/27] =?UTF-8?q?test=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{mock => security}/FakeTokenPropertyHolder.java | 8 +++++--- .../binaryho/imhere/security/SecurityConfigTest.java | 10 ++-------- .../binaryho/imhere/security/jwt/TokenUtilTest.java | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) rename src/test/java/gdsc/binaryho/imhere/{mock => security}/FakeTokenPropertyHolder.java (75%) diff --git a/src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java b/src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java similarity index 75% rename from src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java rename to src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java index 73bb2b9..d01e1fe 100644 --- a/src/test/java/gdsc/binaryho/imhere/mock/FakeTokenPropertyHolder.java +++ b/src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java @@ -1,4 +1,4 @@ -package gdsc.binaryho.imhere.mock; +package gdsc.binaryho.imhere.security; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import java.time.Duration; @@ -7,11 +7,13 @@ public class FakeTokenPropertyHolder implements TokenPropertyHolder { private final String secret; private final Duration accessTokenExpiration; + private final String accessTokenPrefix; - public FakeTokenPropertyHolder(String secret, Duration accessTokenExpiration) { + public FakeTokenPropertyHolder(String secret, Duration accessTokenExpiration, String accessTokenPrefix) { this.secret = secret; this.accessTokenExpiration = accessTokenExpiration; + this.accessTokenPrefix = accessTokenPrefix; } @Override @@ -26,6 +28,6 @@ public Duration getAccessTokenExpiration() { @Override public String getAccessTokenPrefix() { - return "prefix"; + return accessTokenPrefix; } } diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index 4b9600f..d41b766 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -12,7 +12,6 @@ import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.Token; -import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -40,9 +39,6 @@ public class SecurityConfigTest { @MockBean private MemberRepository memberRepository; - @Autowired - private TokenPropertyHolder tokenPropertyHolder; - @Test public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { mockMvc.perform(post("/") @@ -58,10 +54,9 @@ public class SecurityConfigTest { .willReturn(Optional.of(MOCK_STUDENT)); Token token = tokenUtil.createToken(MOCK_STUDENT.getId(), Role.LECTURER); - String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(get("/api/lecture") .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, accessTokenPrefix + token.getAccessToken()) + .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) ) .andExpect(status().is2xxSuccessful()); } @@ -72,10 +67,9 @@ public class SecurityConfigTest { .willReturn(Optional.of(MOCK_STUDENT)); Token token = tokenUtil.createToken(1L, Role.STUDENT); - String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); mockMvc.perform(post("/api/admin/role/1") .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, accessTokenPrefix + token.getAccessToken()) + .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) ) .andExpect(status().isForbidden()); } diff --git a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java index a942734..42e7964 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.mock.FixedSeoulTimeHolder; -import gdsc.binaryho.imhere.mock.FakeTokenPropertyHolder; +import gdsc.binaryho.imhere.security.FakeTokenPropertyHolder; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -27,7 +27,7 @@ public class TokenUtilTest { private static final long ACCESS_TOKEN_EXPIRATION_TIME = 1000L * 60L * 20L; private static final long TIME_NOW = FixedSeoulTimeHolder.FIXED_MILLISECONDS; - TokenPropertyHolder tokenPropertyHolder = new FakeTokenPropertyHolder(SECRET, Duration.ofDays(999L)); + TokenPropertyHolder tokenPropertyHolder = new FakeTokenPropertyHolder(SECRET, Duration.ofDays(999L), "prefix"); TokenUtil tokenUtil = new TokenUtil(new FixedSeoulTimeHolder(), tokenPropertyHolder); @Test From 6126c4fa05047d0df4bab4f074268b4feacf9be3 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 21:03:13 +0900 Subject: [PATCH 24/27] =?UTF-8?q?test=20:=20=EC=9D=B8=EA=B0=80=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20300=EB=B2=88=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EB=93=A4=20=EB=B9=8C=EB=93=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=86=B5=EA=B3=BC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20commit=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gdsc/binaryho/imhere/security/SecurityConfigTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index d41b766..f63558d 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -58,7 +58,7 @@ public class SecurityConfigTest { .contentType(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) ) - .andExpect(status().is2xxSuccessful()); + .andExpect(status().is3xxRedirection()); } @Test From 0b29e9ba12f04c21d8e1df26a2eded7bb54ef231 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Tue, 12 Mar 2024 21:07:28 +0900 Subject: [PATCH 25/27] =?UTF-8?q?test=20:=20=EC=9D=B8=EA=B0=80=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20300=EB=B2=88=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EB=93=A4=20=EB=B9=8C=EB=93=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=86=B5=EA=B3=BC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20commit=202=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gdsc/binaryho/imhere/security/SecurityConfigTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index f63558d..f9eba75 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -15,8 +15,6 @@ import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.util.Optional; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -26,7 +24,6 @@ import org.springframework.test.web.servlet.MockMvc; @SpringBootTest -@ExtendWith(MockitoExtension.class) @AutoConfigureMockMvc public class SecurityConfigTest { @@ -58,7 +55,7 @@ public class SecurityConfigTest { .contentType(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) ) - .andExpect(status().is3xxRedirection()); + .andExpect(status().is2xxSuccessful()); } @Test From 588f5a9c5e01ad4306f37bca9f65801f39b7288b Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 31 Mar 2024 18:20:23 +0900 Subject: [PATCH 26/27] =?UTF-8?q?test=20:=20SecurityConfigTest=EC=97=90=20?= =?UTF-8?q?token=20prefix=20=EC=B6=94=EA=B0=80=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imhere/security/SecurityConfigTest.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index f9eba75..96b98f7 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -1,5 +1,6 @@ package gdsc.binaryho.imhere.security; +import static gdsc.binaryho.imhere.mock.fixture.MemberFixture.MOCK_LECTURER; import static gdsc.binaryho.imhere.mock.fixture.MemberFixture.MOCK_STUDENT; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; @@ -12,6 +13,7 @@ import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -36,6 +38,9 @@ public class SecurityConfigTest { @MockBean private MemberRepository memberRepository; + @Autowired + private TokenPropertyHolder tokenPropertyHolder; + @Test public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { mockMvc.perform(post("/") @@ -48,12 +53,14 @@ public class SecurityConfigTest { @Test public void 토큰을_통해_인가_할_수_있다() throws Exception { given(memberRepository.findById(any())) - .willReturn(Optional.of(MOCK_STUDENT)); - Token token = tokenUtil.createToken(MOCK_STUDENT.getId(), Role.LECTURER); + .willReturn(Optional.of(MOCK_LECTURER)); + String prefix = tokenPropertyHolder.getAccessTokenPrefix(); + Token token = tokenUtil.createToken(MOCK_LECTURER.getId(), Role.LECTURER); mockMvc.perform(get("/api/lecture") .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) + .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION) + .header(HttpHeaders.AUTHORIZATION, prefix + token.getAccessToken()) ) .andExpect(status().is2xxSuccessful()); } From 3b6dc092e02430f010c7219a51f872155fbd8b40 Mon Sep 17 00:00:00 2001 From: binary_ho Date: Sun, 31 Mar 2024 18:30:55 +0900 Subject: [PATCH 27/27] =?UTF-8?q?test=20:=20=ED=86=A0=ED=81=B0=20=EC=9D=B8?= =?UTF-8?q?=EA=B0=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=20LectureRepos?= =?UTF-8?q?itory=20Mocking=20=EC=B6=94=EA=B0=80=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binaryho/imhere/security/SecurityConfigTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java index 96b98f7..eebb047 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -10,11 +10,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import gdsc.binaryho.imhere.core.lecture.infrastructure.LectureRepository; import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; import gdsc.binaryho.imhere.security.jwt.Token; import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; import gdsc.binaryho.imhere.security.jwt.TokenUtil; +import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -35,11 +37,14 @@ public class SecurityConfigTest { @Autowired private TokenUtil tokenUtil; + @Autowired + private TokenPropertyHolder tokenPropertyHolder; + @MockBean private MemberRepository memberRepository; - @Autowired - private TokenPropertyHolder tokenPropertyHolder; + @MockBean + private LectureRepository lectureRepository; @Test public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { @@ -54,6 +59,9 @@ public class SecurityConfigTest { public void 토큰을_통해_인가_할_수_있다() throws Exception { given(memberRepository.findById(any())) .willReturn(Optional.of(MOCK_LECTURER)); + given(lectureRepository.findAllByLectureStateNot(any())) + .willReturn(Collections.emptyList()); + String prefix = tokenPropertyHolder.getAccessTokenPrefix(); Token token = tokenUtil.createToken(MOCK_LECTURER.getId(), Role.LECTURER);