From 75c6bb65c504042210bd611d14752024c86db5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 14:53:45 +0900 Subject: [PATCH 1/8] =?UTF-8?q?docs=20:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EB=B0=94=ED=83=95=20Readme=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 1e7ba65..454a7f5 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ # spring-security-authentication + +## 기능 요구 사항 + +1. 아이디 비밀번호 기반 로그인 기능 구현 +2. Basic 인증 및 사용자를 식별하는 기능 구현 + +## 구현 기능 목록 + +### 아이디 비밀번호 기반 로그인 기능 구현 + + 1.사용자가 입력한 아이디와 비밀번호를 바탕으로 사용자 정보를 읽어 온 후 인증 + 2.로그인 성공 시 Session에 인증 정보를 저장 + +### Basic 인증 구현 + + 1. Basic Token을 디코딩하는 기능 + 2. 디코딩된 내용을 바탕으로 사용자를 식별하는 기능 + +### 리팩토링 사항 + + 인증 로직과 서비스 로직 사이의 패키지 분리 + 패키지 사이의 의존성이 단반향으로 흐르도록 변경 + + \ No newline at end of file From 5f3d15e74ba245abf53b00b9b85db9eca443ba5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 14:57:14 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat=20:=20=20FormLoginInterceptor=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit username, password를 바탕으로 사용자를 식별하는 FormLoginInterceptor 구현 --- .../application/UserDetailServiceImpl.java | 31 +++++++++++ .../java/nextstep/app/config/WebConfig.java | 23 ++++++++ .../java/nextstep/app/ui/LoginController.java | 20 +------ .../FormLoginAuthInterceptor.java | 54 +++++++++++++++++++ .../exception}/AuthenticationException.java | 3 +- .../security/userdetail/UserDetail.java | 21 ++++++++ .../userdetail/UserDetailService.java | 6 +++ 7 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 src/main/java/nextstep/app/application/UserDetailServiceImpl.java create mode 100644 src/main/java/nextstep/app/config/WebConfig.java create mode 100644 src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java rename src/main/java/nextstep/{app/ui => security/exception}/AuthenticationException.java (63%) create mode 100644 src/main/java/nextstep/security/userdetail/UserDetail.java create mode 100644 src/main/java/nextstep/security/userdetail/UserDetailService.java diff --git a/src/main/java/nextstep/app/application/UserDetailServiceImpl.java b/src/main/java/nextstep/app/application/UserDetailServiceImpl.java new file mode 100644 index 0000000..3400a76 --- /dev/null +++ b/src/main/java/nextstep/app/application/UserDetailServiceImpl.java @@ -0,0 +1,31 @@ +package nextstep.app.application; + +import java.util.Objects; +import nextstep.app.domain.Member; +import nextstep.app.domain.MemberRepository; +import nextstep.security.userdetail.UserDetail; +import nextstep.security.userdetail.UserDetailService; +import org.springframework.stereotype.Service; + +@Service +public class UserDetailServiceImpl implements UserDetailService { + + private final MemberRepository memberRepository; + + public UserDetailServiceImpl(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public UserDetail getUserDetail(String username, String password) { + return memberRepository.findByEmail(username) + .filter(member -> Objects.equals(member.getPassword(), password)) + .map(this::convertToUserDetail) + .orElse(null); + } + + + public UserDetail convertToUserDetail(Member member) { + return new UserDetail(member.getEmail(), member.getPassword()); + } +} diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java new file mode 100644 index 0000000..3326f66 --- /dev/null +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -0,0 +1,23 @@ +package nextstep.app.config; + +import nextstep.security.authentication.FormLoginAuthInterceptor; +import nextstep.security.userdetail.UserDetailService; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final UserDetailService userDetailService; + + public WebConfig(UserDetailService userDetailService) { + this.userDetailService = userDetailService; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new FormLoginAuthInterceptor(userDetailService)) + .addPathPatterns("/login"); + } +} diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 0ea94f1..00dd61e 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,32 +1,16 @@ package nextstep.app.ui; -import nextstep.app.domain.MemberRepository; -import org.springframework.http.HttpStatus; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - @RestController public class LoginController { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - - private final MemberRepository memberRepository; - - public LoginController(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } @PostMapping("/login") public ResponseEntity login(HttpServletRequest request, HttpSession session) { return ResponseEntity.ok().build(); } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException() { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } } diff --git a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java new file mode 100644 index 0000000..c8dec0e --- /dev/null +++ b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java @@ -0,0 +1,54 @@ +package nextstep.security.authentication; + +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import nextstep.security.exception.AuthenticationException; +import nextstep.security.userdetail.UserDetail; +import nextstep.security.userdetail.UserDetailService; +import org.springframework.util.ObjectUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +public class FormLoginAuthInterceptor implements HandlerInterceptor { + + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final UserDetailService userDetailService; + + public FormLoginAuthInterceptor(UserDetailService userDetailService) { + this.userDetailService = userDetailService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, + Object handler) throws Exception { + try { + HttpSession session = request.getSession(); + + if (session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null) { + session.removeAttribute(SPRING_SECURITY_CONTEXT_KEY); + } + + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) { + throw new AuthenticationException(); + } + + UserDetail userDetail = userDetailService.getUserDetail(username, password); + + if (Objects.isNull(userDetail)) { + throw new AuthenticationException(); + } + + request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); + + return true; + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + } +} diff --git a/src/main/java/nextstep/app/ui/AuthenticationException.java b/src/main/java/nextstep/security/exception/AuthenticationException.java similarity index 63% rename from src/main/java/nextstep/app/ui/AuthenticationException.java rename to src/main/java/nextstep/security/exception/AuthenticationException.java index f809b6e..63a5166 100644 --- a/src/main/java/nextstep/app/ui/AuthenticationException.java +++ b/src/main/java/nextstep/security/exception/AuthenticationException.java @@ -1,4 +1,5 @@ -package nextstep.app.ui; +package nextstep.security.exception; public class AuthenticationException extends RuntimeException { + } diff --git a/src/main/java/nextstep/security/userdetail/UserDetail.java b/src/main/java/nextstep/security/userdetail/UserDetail.java new file mode 100644 index 0000000..0f9dbdc --- /dev/null +++ b/src/main/java/nextstep/security/userdetail/UserDetail.java @@ -0,0 +1,21 @@ +package nextstep.security.userdetail; + +public class UserDetail { + + private final String username; + + private final String password; + + public UserDetail(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/nextstep/security/userdetail/UserDetailService.java b/src/main/java/nextstep/security/userdetail/UserDetailService.java new file mode 100644 index 0000000..abc2c90 --- /dev/null +++ b/src/main/java/nextstep/security/userdetail/UserDetailService.java @@ -0,0 +1,6 @@ +package nextstep.security.userdetail; + +public interface UserDetailService { + + UserDetail getUserDetail(String username, String password); +} From 94741fb42ae11e8c8e962a6fa544cd7a45302981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 14:58:10 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat=20:=20BasicTokenDecoder=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic Token을 디코딩하여 사용자 정보를 가져오는 BasicTokenDecoder 구현 --- .../nextstep/app/util/BasicTokenDecoder.java | 39 +++++++++++++++++++ .../security/authentication/TokenDecoder.java | 8 ++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/java/nextstep/app/util/BasicTokenDecoder.java create mode 100644 src/main/java/nextstep/security/authentication/TokenDecoder.java diff --git a/src/main/java/nextstep/app/util/BasicTokenDecoder.java b/src/main/java/nextstep/app/util/BasicTokenDecoder.java new file mode 100644 index 0000000..cde77bc --- /dev/null +++ b/src/main/java/nextstep/app/util/BasicTokenDecoder.java @@ -0,0 +1,39 @@ +package nextstep.app.util; + +import java.nio.charset.StandardCharsets; +import nextstep.security.authentication.TokenDecoder; +import nextstep.security.exception.AuthenticationException; +import nextstep.security.userdetail.UserDetail; +import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; + +@Component +public class BasicTokenDecoder implements TokenDecoder { + + public static final String BASIC_TOKEN_PREFIX = "Basic "; + + @Override + public UserDetail decodeToken(String token) { + String base64Token = token.substring(BASIC_TOKEN_PREFIX.length()); + String decodedToken = new String(Base64Utils.decodeFromString(base64Token), + StandardCharsets.UTF_8); + + validateBasicToken(token); + + String[] parts = decodedToken.split(":"); + if (parts.length != 2) { + throw new AuthenticationException(); + } + return new UserDetail(parts[0], parts[1]); + } + + private void validateBasicToken(String authorization) { + if (authorization == null) { + throw new AuthenticationException(); + } + + if (!authorization.startsWith(BASIC_TOKEN_PREFIX)) { + throw new AuthenticationException(); + } + } +} diff --git a/src/main/java/nextstep/security/authentication/TokenDecoder.java b/src/main/java/nextstep/security/authentication/TokenDecoder.java new file mode 100644 index 0000000..bfca4fd --- /dev/null +++ b/src/main/java/nextstep/security/authentication/TokenDecoder.java @@ -0,0 +1,8 @@ +package nextstep.security.authentication; + +import nextstep.security.userdetail.UserDetail; + +public interface TokenDecoder { + + UserDetail decodeToken(String token); +} From 7a887357bbdc9f7ab125ae67105fe49b2b3a7610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 14:59:05 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat=20:=20BasicAuthInterceptor=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authorization Header의 Basic 토큰을 가져와 사용자를 식별하는 BasicAuthInterceptor 구현 --- .../java/nextstep/app/config/WebConfig.java | 8 +++- .../authentication/BasicAuthInterceptor.java | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index 3326f66..ff21c0d 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,6 +1,8 @@ package nextstep.app.config; +import nextstep.security.authentication.BasicAuthInterceptor; import nextstep.security.authentication.FormLoginAuthInterceptor; +import nextstep.security.authentication.TokenDecoder; import nextstep.security.userdetail.UserDetailService; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -10,14 +12,18 @@ public class WebConfig implements WebMvcConfigurer { private final UserDetailService userDetailService; + private final TokenDecoder tokenDecoder; - public WebConfig(UserDetailService userDetailService) { + public WebConfig(UserDetailService userDetailService, TokenDecoder tokenDecoder) { this.userDetailService = userDetailService; + this.tokenDecoder = tokenDecoder; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FormLoginAuthInterceptor(userDetailService)) .addPathPatterns("/login"); + registry.addInterceptor(new BasicAuthInterceptor(tokenDecoder, userDetailService)) + .addPathPatterns("/members"); } } diff --git a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java new file mode 100644 index 0000000..390f190 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java @@ -0,0 +1,41 @@ +package nextstep.security.authentication; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import nextstep.security.userdetail.UserDetail; +import nextstep.security.userdetail.UserDetailService; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.HandlerInterceptor; + +public class BasicAuthInterceptor implements HandlerInterceptor { + + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final TokenDecoder tokenDecoder; + private final UserDetailService userDetailService; + + + public BasicAuthInterceptor(TokenDecoder tokenDecoder, UserDetailService userDetailService) { + this.tokenDecoder = tokenDecoder; + this.userDetailService = userDetailService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, + Object handler) throws Exception { + try { + String token = request.getHeader(HttpHeaders.AUTHORIZATION); + + UserDetail decodedUserInfo = tokenDecoder.decodeToken(token); + + UserDetail userDetail = userDetailService.getUserDetail(decodedUserInfo.getUsername(), + decodedUserInfo.getPassword()); + + request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); + return true; + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + } +} From f0c36e864f1639ced3b69082d68b6c7a14547947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 15:19:57 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor=20:=20Security=EC=9A=A9=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=83=81=EC=88=98=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic Token Prefix, Context_key 등 여러 클래스에서 사용하는 상수들을 하나의 클래스에서 관리할 수 있도록 변경 --- src/main/java/nextstep/app/util/BasicTokenDecoder.java | 4 ++-- .../security/authentication/BasicAuthInterceptor.java | 5 +++-- .../security/authentication/FormLoginAuthInterceptor.java | 4 ++-- .../security/authentication/SecurityConstants.java | 8 ++++++++ 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/main/java/nextstep/security/authentication/SecurityConstants.java diff --git a/src/main/java/nextstep/app/util/BasicTokenDecoder.java b/src/main/java/nextstep/app/util/BasicTokenDecoder.java index cde77bc..934b63a 100644 --- a/src/main/java/nextstep/app/util/BasicTokenDecoder.java +++ b/src/main/java/nextstep/app/util/BasicTokenDecoder.java @@ -1,5 +1,7 @@ package nextstep.app.util; +import static nextstep.security.authentication.SecurityConstants.BASIC_TOKEN_PREFIX; + import java.nio.charset.StandardCharsets; import nextstep.security.authentication.TokenDecoder; import nextstep.security.exception.AuthenticationException; @@ -10,8 +12,6 @@ @Component public class BasicTokenDecoder implements TokenDecoder { - public static final String BASIC_TOKEN_PREFIX = "Basic "; - @Override public UserDetail decodeToken(String token) { String base64Token = token.substring(BASIC_TOKEN_PREFIX.length()); diff --git a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java index 390f190..340d66d 100644 --- a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java @@ -1,7 +1,10 @@ package nextstep.security.authentication; +import static nextstep.security.authentication.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import nextstep.security.exception.AuthenticationException; import nextstep.security.userdetail.UserDetail; import nextstep.security.userdetail.UserDetailService; import org.springframework.http.HttpHeaders; @@ -9,8 +12,6 @@ public class BasicAuthInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final TokenDecoder tokenDecoder; private final UserDetailService userDetailService; diff --git a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java index c8dec0e..d1431d7 100644 --- a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java @@ -1,5 +1,7 @@ package nextstep.security.authentication; +import static nextstep.security.authentication.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; + import java.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -12,8 +14,6 @@ public class FormLoginAuthInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final UserDetailService userDetailService; public FormLoginAuthInterceptor(UserDetailService userDetailService) { diff --git a/src/main/java/nextstep/security/authentication/SecurityConstants.java b/src/main/java/nextstep/security/authentication/SecurityConstants.java new file mode 100644 index 0000000..0e29cba --- /dev/null +++ b/src/main/java/nextstep/security/authentication/SecurityConstants.java @@ -0,0 +1,8 @@ +package nextstep.security.authentication; + +public class SecurityConstants { + + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + public static final String BASIC_TOKEN_PREFIX = "Basic "; +} From fd4fcab4f6eab2f46341b2191b46333d761c77f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 15:21:27 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor=20:=20=EC=9D=B8=EC=A6=9D=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20app=20->=20security=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit userDetailService의 구현체에서 관리하던 사용자 인증 로직을 security 하위 패키지에 있는 클래스들이 사용하도록 변경 refactor : 잘못된 줄바꿈 수정 불필요한 줄바꿈 제거 --- .../nextstep/app/application/UserDetailServiceImpl.java | 5 +---- .../security/authentication/BasicAuthInterceptor.java | 7 +++++-- .../security/authentication/FormLoginAuthInterceptor.java | 6 +++++- src/main/java/nextstep/security/userdetail/UserDetail.java | 4 ++++ .../nextstep/security/userdetail/UserDetailService.java | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/nextstep/app/application/UserDetailServiceImpl.java b/src/main/java/nextstep/app/application/UserDetailServiceImpl.java index 3400a76..4eaaa4b 100644 --- a/src/main/java/nextstep/app/application/UserDetailServiceImpl.java +++ b/src/main/java/nextstep/app/application/UserDetailServiceImpl.java @@ -1,6 +1,5 @@ package nextstep.app.application; -import java.util.Objects; import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; import nextstep.security.userdetail.UserDetail; @@ -17,14 +16,12 @@ public UserDetailServiceImpl(MemberRepository memberRepository) { } @Override - public UserDetail getUserDetail(String username, String password) { + public UserDetail getUserDetail(String username) { return memberRepository.findByEmail(username) - .filter(member -> Objects.equals(member.getPassword(), password)) .map(this::convertToUserDetail) .orElse(null); } - public UserDetail convertToUserDetail(Member member) { return new UserDetail(member.getEmail(), member.getPassword()); } diff --git a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java index 340d66d..f3c4660 100644 --- a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java @@ -29,8 +29,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons UserDetail decodedUserInfo = tokenDecoder.decodeToken(token); - UserDetail userDetail = userDetailService.getUserDetail(decodedUserInfo.getUsername(), - decodedUserInfo.getPassword()); + UserDetail userDetail = userDetailService.getUserDetail(decodedUserInfo.getUsername()); + + if (!userDetail.verifyPassword(decodedUserInfo.getPassword())) { + throw new AuthenticationException(); + } request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); return true; diff --git a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java index d1431d7..6256117 100644 --- a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java @@ -37,12 +37,16 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons throw new AuthenticationException(); } - UserDetail userDetail = userDetailService.getUserDetail(username, password); + UserDetail userDetail = userDetailService.getUserDetail(username); if (Objects.isNull(userDetail)) { throw new AuthenticationException(); } + if (!userDetail.verifyPassword(password)) { + throw new AuthenticationException(); + } + request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); return true; diff --git a/src/main/java/nextstep/security/userdetail/UserDetail.java b/src/main/java/nextstep/security/userdetail/UserDetail.java index 0f9dbdc..145058a 100644 --- a/src/main/java/nextstep/security/userdetail/UserDetail.java +++ b/src/main/java/nextstep/security/userdetail/UserDetail.java @@ -18,4 +18,8 @@ public String getUsername() { public String getPassword() { return password; } + + public boolean verifyPassword(String password) { + return this.password.equals(password); + } } diff --git a/src/main/java/nextstep/security/userdetail/UserDetailService.java b/src/main/java/nextstep/security/userdetail/UserDetailService.java index abc2c90..1a6ea92 100644 --- a/src/main/java/nextstep/security/userdetail/UserDetailService.java +++ b/src/main/java/nextstep/security/userdetail/UserDetailService.java @@ -2,5 +2,5 @@ public interface UserDetailService { - UserDetail getUserDetail(String username, String password); + UserDetail getUserDetail(String username); } From fccac9db88f0ba091c638242796c63a8e44e1447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 15:32:33 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor=20:=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EA=B3=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FormLoginAuthInterceptor의 parameter 정보와 세션정보를 검증하는 로직과 사용자가 맞는지 인증하는 로직을 별도의 메서드 분리 feat : SecurityConstants 빈 생성자 추가 해당 클래스가 오로지 상수용 클래스로만 사용될 수 있도록 private 빈 생성자 추가 --- .../authentication/BasicAuthInterceptor.java | 1 - .../FormLoginAuthInterceptor.java | 45 +++++++++++-------- .../authentication/SecurityConstants.java | 3 ++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java index f3c4660..bb99a3a 100644 --- a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java @@ -28,7 +28,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons String token = request.getHeader(HttpHeaders.AUTHORIZATION); UserDetail decodedUserInfo = tokenDecoder.decodeToken(token); - UserDetail userDetail = userDetailService.getUserDetail(decodedUserInfo.getUsername()); if (!userDetail.verifyPassword(decodedUserInfo.getPassword())) { diff --git a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java index 6256117..cb5d925 100644 --- a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java @@ -24,29 +24,13 @@ public FormLoginAuthInterceptor(UserDetailService userDetailService) { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { - HttpSession session = request.getSession(); - - if (session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null) { - session.removeAttribute(SPRING_SECURITY_CONTEXT_KEY); - } + validateParamAndSession(request); String username = request.getParameter("username"); String password = request.getParameter("password"); - if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) { - throw new AuthenticationException(); - } - UserDetail userDetail = userDetailService.getUserDetail(username); - - if (Objects.isNull(userDetail)) { - throw new AuthenticationException(); - } - - if (!userDetail.verifyPassword(password)) { - throw new AuthenticationException(); - } - + verifyUserDetail(userDetail, password); request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); return true; @@ -55,4 +39,29 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return false; } } + + private void validateParamAndSession(HttpServletRequest request) { + HttpSession session = request.getSession(); + + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + if (session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null) { + session.removeAttribute(SPRING_SECURITY_CONTEXT_KEY); + } + + if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) { + throw new AuthenticationException(); + } + } + + private void verifyUserDetail(UserDetail userDetail, String password) { + if (Objects.isNull(userDetail)) { + throw new AuthenticationException(); + } + + if (!userDetail.verifyPassword(password)) { + throw new AuthenticationException(); + } + } } diff --git a/src/main/java/nextstep/security/authentication/SecurityConstants.java b/src/main/java/nextstep/security/authentication/SecurityConstants.java index 0e29cba..4c7b9f9 100644 --- a/src/main/java/nextstep/security/authentication/SecurityConstants.java +++ b/src/main/java/nextstep/security/authentication/SecurityConstants.java @@ -2,6 +2,9 @@ public class SecurityConstants { + private SecurityConstants() { + } + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; public static final String BASIC_TOKEN_PREFIX = "Basic "; From 4fa20530e92fd249b9b79a9738e494dc7967313c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=ED=98=84?= Date: Sun, 3 Nov 2024 15:37:49 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor=20:=20Decoder=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20Component=20Scan?= =?UTF-8?q?=20=EB=B2=94=EC=9C=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BasicTokenDecoder가 app이 아닌 sercurity 패키지에 속하도록 변경하고 컴포넌트 스캔 범위를 app, security 패키지를 사용할 수 있게 SecurityAuthenticationApplication 위치 변경 --- .../{app => }/SecurityAuthenticationApplication.java | 2 +- src/main/java/nextstep/app/config/WebConfig.java | 2 +- .../security/authentication/BasicAuthInterceptor.java | 3 ++- .../security/authentication/FormLoginAuthInterceptor.java | 2 +- .../nextstep/{app => security}/util/BasicTokenDecoder.java | 5 ++--- .../security/{authentication => util}/SecurityConstants.java | 2 +- .../security/{authentication => util}/TokenDecoder.java | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/nextstep/{app => }/SecurityAuthenticationApplication.java (93%) rename src/main/java/nextstep/{app => security}/util/BasicTokenDecoder.java (86%) rename src/main/java/nextstep/security/{authentication => util}/SecurityConstants.java (84%) rename src/main/java/nextstep/security/{authentication => util}/TokenDecoder.java (75%) diff --git a/src/main/java/nextstep/app/SecurityAuthenticationApplication.java b/src/main/java/nextstep/SecurityAuthenticationApplication.java similarity index 93% rename from src/main/java/nextstep/app/SecurityAuthenticationApplication.java rename to src/main/java/nextstep/SecurityAuthenticationApplication.java index 0f8eb47..1ecd05f 100644 --- a/src/main/java/nextstep/app/SecurityAuthenticationApplication.java +++ b/src/main/java/nextstep/SecurityAuthenticationApplication.java @@ -1,4 +1,4 @@ -package nextstep.app; +package nextstep; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index ff21c0d..70ab41c 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -2,8 +2,8 @@ import nextstep.security.authentication.BasicAuthInterceptor; import nextstep.security.authentication.FormLoginAuthInterceptor; -import nextstep.security.authentication.TokenDecoder; import nextstep.security.userdetail.UserDetailService; +import nextstep.security.util.TokenDecoder; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java index bb99a3a..14284ab 100644 --- a/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/BasicAuthInterceptor.java @@ -1,12 +1,13 @@ package nextstep.security.authentication; -import static nextstep.security.authentication.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; +import static nextstep.security.util.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import nextstep.security.exception.AuthenticationException; import nextstep.security.userdetail.UserDetail; import nextstep.security.userdetail.UserDetailService; +import nextstep.security.util.TokenDecoder; import org.springframework.http.HttpHeaders; import org.springframework.web.servlet.HandlerInterceptor; diff --git a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java index cb5d925..6692a9b 100644 --- a/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java +++ b/src/main/java/nextstep/security/authentication/FormLoginAuthInterceptor.java @@ -1,6 +1,6 @@ package nextstep.security.authentication; -import static nextstep.security.authentication.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; +import static nextstep.security.util.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY; import java.util.Objects; import javax.servlet.http.HttpServletRequest; diff --git a/src/main/java/nextstep/app/util/BasicTokenDecoder.java b/src/main/java/nextstep/security/util/BasicTokenDecoder.java similarity index 86% rename from src/main/java/nextstep/app/util/BasicTokenDecoder.java rename to src/main/java/nextstep/security/util/BasicTokenDecoder.java index 934b63a..68da04c 100644 --- a/src/main/java/nextstep/app/util/BasicTokenDecoder.java +++ b/src/main/java/nextstep/security/util/BasicTokenDecoder.java @@ -1,9 +1,8 @@ -package nextstep.app.util; +package nextstep.security.util; -import static nextstep.security.authentication.SecurityConstants.BASIC_TOKEN_PREFIX; +import static nextstep.security.util.SecurityConstants.BASIC_TOKEN_PREFIX; import java.nio.charset.StandardCharsets; -import nextstep.security.authentication.TokenDecoder; import nextstep.security.exception.AuthenticationException; import nextstep.security.userdetail.UserDetail; import org.springframework.stereotype.Component; diff --git a/src/main/java/nextstep/security/authentication/SecurityConstants.java b/src/main/java/nextstep/security/util/SecurityConstants.java similarity index 84% rename from src/main/java/nextstep/security/authentication/SecurityConstants.java rename to src/main/java/nextstep/security/util/SecurityConstants.java index 4c7b9f9..4466064 100644 --- a/src/main/java/nextstep/security/authentication/SecurityConstants.java +++ b/src/main/java/nextstep/security/util/SecurityConstants.java @@ -1,4 +1,4 @@ -package nextstep.security.authentication; +package nextstep.security.util; public class SecurityConstants { diff --git a/src/main/java/nextstep/security/authentication/TokenDecoder.java b/src/main/java/nextstep/security/util/TokenDecoder.java similarity index 75% rename from src/main/java/nextstep/security/authentication/TokenDecoder.java rename to src/main/java/nextstep/security/util/TokenDecoder.java index bfca4fd..522018d 100644 --- a/src/main/java/nextstep/security/authentication/TokenDecoder.java +++ b/src/main/java/nextstep/security/util/TokenDecoder.java @@ -1,4 +1,4 @@ -package nextstep.security.authentication; +package nextstep.security.util; import nextstep.security.userdetail.UserDetail;