From 41655c174930877100aa5a39df4e0668836b9fc2 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sat, 2 Nov 2024 22:56:08 +0900 Subject: [PATCH 01/16] =?UTF-8?q?docs(readme.md)=20:=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Login api 관련 기능 요구사항 정리 - Member api 관련 기능 요구사항 정리 --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 1e7ba65..8221704 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ # spring-security-authentication + +기능 요구 사항 + +아이디와 비밀번호를 기반으로 로그인 기능울 구현, Basic 인증을 사용하여 사용자를 식별 할 수 있도록 프레임워크 사용. + +웹앱으로 구현 + +아이디, 비밀번호 기반 로그인 구현 +- POST /login 경로로 로그인 요청 +- 사용자의 아이디 비밀번호르 확인하여 인증 +- 로그인 성공시 session을 사용해 인증 정보 저장 +- LoginTest의 모든 테스트가 통과해야함. + +Basic 인증 구현 +- GET /member 요청 시 사용자 목록을 조회 +- 단 member로 등록되어 있느 ㄴ사용자만 간으하도록 +- 이를 위해 basic 인증을 사용해 사용자 식별 +- Authorization 헤더에서 Basic 인증정보를 추출하여 인증처리 +- 인증 성공 시 session에 에 인증 정보 저장 +- MemberTest의 모든 테스트가 통과 \ No newline at end of file From 18c7e93c71459511ad1df607190358f8876703d3 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sat, 2 Nov 2024 22:59:45 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat(AuthErrorCodes,=20AuthenticationExce?= =?UTF-8?q?ption):=20ErrorCode=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AuthenticationException : Exception관련 detail 정보 추가 - AuthErrorCode : Auth Error를 Enum으로 관리 --- .../app/exception/AuthErrorCodes.java | 23 +++++++++++++++++++ .../exception/AuthenticationException.java | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/nextstep/app/exception/AuthErrorCodes.java create mode 100644 src/main/java/nextstep/app/exception/AuthenticationException.java diff --git a/src/main/java/nextstep/app/exception/AuthErrorCodes.java b/src/main/java/nextstep/app/exception/AuthErrorCodes.java new file mode 100644 index 0000000..5f8a1f6 --- /dev/null +++ b/src/main/java/nextstep/app/exception/AuthErrorCodes.java @@ -0,0 +1,23 @@ +package nextstep.app.exception; + +import org.springframework.http.HttpStatus; + +public enum AuthErrorCodes { + UNAUTHORIZED_LOGIN_REQUEST(HttpStatus.UNAUTHORIZED, "Username not exist or Wrong password"); + + private final HttpStatus statusCode; + private final String message; + + AuthErrorCodes(HttpStatus statusCode, String message) { + this.statusCode = statusCode; + this.message = message; + } + + public String getMessage() { + return message; + } + + public HttpStatus getStatusCode() { + return statusCode; + } +} diff --git a/src/main/java/nextstep/app/exception/AuthenticationException.java b/src/main/java/nextstep/app/exception/AuthenticationException.java new file mode 100644 index 0000000..0c43309 --- /dev/null +++ b/src/main/java/nextstep/app/exception/AuthenticationException.java @@ -0,0 +1,19 @@ +package nextstep.app.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Map; + +public class AuthenticationException extends RuntimeException{ + HttpStatus status; + + public AuthenticationException(AuthErrorCodes exceptions) { + super(exceptions.getMessage()); + this.status = exceptions.getStatusCode(); + } + + public Map getResponseBody() { + return Map.of("detail", this.getMessage()); + } +} From 0386b57a7f6cd5e7f593f8d55cdaf6d9503e5fff Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sat, 2 Nov 2024 23:01:45 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat(MemberService):=20Login=EB=B0=8F=20M?= =?UTF-8?q?ember=EB=93=A4=20=EA=B4=80=EB=A0=A8=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/domain/MemberService.java | 35 +++++++++++++++++++ .../app/ui/AuthenticationException.java | 4 --- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/main/java/nextstep/app/domain/MemberService.java delete mode 100644 src/main/java/nextstep/app/ui/AuthenticationException.java diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java new file mode 100644 index 0000000..fc7ca0e --- /dev/null +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -0,0 +1,35 @@ +package nextstep.app.domain; + +import nextstep.app.exception.AuthErrorCodes; +import nextstep.app.exception.AuthenticationException; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpSession; + +@Service +public class MemberService { + private MemberRepository memberRepo; + + + public MemberService(MemberRepository memberRepo) { + this.memberRepo = memberRepo; + } + + public void login(HttpSession session, String email, String password) { + memberRepo.findByEmail(email).ifPresentOrElse( + + member -> { + if (member.getPassword().equals(password)) { + session.setAttribute("SPRING_SECURITY_CONTEXT", member); + return; + } + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + }, + + () -> { + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + } + ); + + } +} diff --git a/src/main/java/nextstep/app/ui/AuthenticationException.java b/src/main/java/nextstep/app/ui/AuthenticationException.java deleted file mode 100644 index f809b6e..0000000 --- a/src/main/java/nextstep/app/ui/AuthenticationException.java +++ /dev/null @@ -1,4 +0,0 @@ -package nextstep.app.ui; - -public class AuthenticationException extends RuntimeException { -} From 3bd6d9018587a568cdbf3a8ad0f719240c8bdf9e Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sat, 2 Nov 2024 23:02:28 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat(LoginController):=20Login=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20api=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20Authentication=20Exception=20hanlding?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/ui/LoginController.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 0ea94f1..d21b7b9 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,6 +1,8 @@ package nextstep.app.ui; import nextstep.app.domain.MemberRepository; +import nextstep.app.domain.MemberService; +import nextstep.app.exception.AuthenticationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -14,19 +16,22 @@ public class LoginController { public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final MemberRepository memberRepository; + private final MemberService memberService; - public LoginController(MemberRepository memberRepository) { - this.memberRepository = memberRepository; + public LoginController(MemberService memberService) { + this.memberService = memberService; } @PostMapping("/login") public ResponseEntity login(HttpServletRequest request, HttpSession session) { + String email = request.getParameter("username"); + String password = request.getParameter("password"); + memberService.login(session, email, password); return ResponseEntity.ok().build(); } @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException() { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + public ResponseEntity handleAuthenticationException(AuthenticationException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getResponseBody()); } } From 1748c0f35421cf339cd344a111d2e4fa3ff53a6e Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sat, 2 Nov 2024 23:04:17 +0900 Subject: [PATCH 05/16] refactor(MemberSerivce): reformatting Code --- .../nextstep/app/domain/MemberService.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index fc7ca0e..61c960f 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -16,20 +16,14 @@ public MemberService(MemberRepository memberRepo) { } public void login(HttpSession session, String email, String password) { - memberRepo.findByEmail(email).ifPresentOrElse( - - member -> { - if (member.getPassword().equals(password)) { - session.setAttribute("SPRING_SECURITY_CONTEXT", member); - return; - } - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - }, - - () -> { - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - } - ); - + memberRepo.findByEmail(email).ifPresentOrElse(member -> { + if (member.getPassword().equals(password)) { + session.setAttribute("SPRING_SECURITY_CONTEXT", member); + return; + } + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + }, () -> { + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + }); } } From 756125f6da3cdcfd194c6dcfc01799e75114f644 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 13:40:44 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat(MemberService):=20Basic=20token=20va?= =?UTF-8?q?lidation=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AuthErrorCodes에 basic token parsing error 처리 로직 추가 --- .../nextstep/app/domain/MemberService.java | 24 +++++++++++++++++++ .../app/exception/AuthErrorCodes.java | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index 61c960f..13cfa98 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import javax.servlet.http.HttpSession; +import java.util.Base64; @Service public class MemberService { @@ -26,4 +27,27 @@ public void login(HttpSession session, String email, String password) { throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); }); } + + public void validate(String basicToken){ + String decoded = new String(Base64.getDecoder().decode(basicToken.replace("Basic ", ""))); + String email = ""; + String password = ""; + try { + email = decoded.substring(0, decoded.indexOf(":")); + password = decoded.substring(decoded.indexOf(":") + 1); + } + catch (StringIndexOutOfBoundsException e){ + throw new AuthenticationException(AuthErrorCodes.WRONG_BASIC_TOKEN_FORMAT); + } + + final String finalPassword = password; + memberRepo.findByEmail(email).ifPresentOrElse(member -> { + if (member.getPassword().equals(finalPassword)) { + return; + } + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + }, () -> { + throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); + }); + } } diff --git a/src/main/java/nextstep/app/exception/AuthErrorCodes.java b/src/main/java/nextstep/app/exception/AuthErrorCodes.java index 5f8a1f6..103dc1e 100644 --- a/src/main/java/nextstep/app/exception/AuthErrorCodes.java +++ b/src/main/java/nextstep/app/exception/AuthErrorCodes.java @@ -3,7 +3,8 @@ import org.springframework.http.HttpStatus; public enum AuthErrorCodes { - UNAUTHORIZED_LOGIN_REQUEST(HttpStatus.UNAUTHORIZED, "Username not exist or Wrong password"); + UNAUTHORIZED_LOGIN_REQUEST(HttpStatus.UNAUTHORIZED, "Username not exist or Wrong password"), + WRONG_BASIC_TOKEN_FORMAT(HttpStatus.UNAUTHORIZED, "Wrong basic token format"); private final HttpStatus statusCode; private final String message; From afa4dd2e84fc47c4dac93dcca6929d3b0473de43 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 13:47:02 +0900 Subject: [PATCH 07/16] =?UTF-8?q?refactor(BaseContoller):=20ExceptionHandl?= =?UTF-8?q?ing=20logic=EC=9D=84=20=EB=94=B0=EB=A1=9C=20=EB=B6=84=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/ui/BaseController.java | 14 ++++++++++++++ .../java/nextstep/app/ui/LoginController.java | 9 +-------- .../java/nextstep/app/ui/MemberController.java | 16 +++++++++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 src/main/java/nextstep/app/ui/BaseController.java diff --git a/src/main/java/nextstep/app/ui/BaseController.java b/src/main/java/nextstep/app/ui/BaseController.java new file mode 100644 index 0000000..f074762 --- /dev/null +++ b/src/main/java/nextstep/app/ui/BaseController.java @@ -0,0 +1,14 @@ +package nextstep.app.ui; + +import nextstep.app.exception.AuthenticationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; + +public class BaseController { + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException(AuthenticationException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getResponseBody()); + } +} diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index d21b7b9..6861fe4 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -13,9 +13,7 @@ import javax.servlet.http.HttpSession; @RestController -public class LoginController { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - +public class LoginController extends BaseController { private final MemberService memberService; public LoginController(MemberService memberService) { @@ -29,9 +27,4 @@ public ResponseEntity login(HttpServletRequest request, HttpSession sessio memberService.login(session, email, password); return ResponseEntity.ok().build(); } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException(AuthenticationException e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getResponseBody()); - } } diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index c8cc74d..a4caa14 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,23 +2,33 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; +import nextstep.app.domain.MemberService; +import nextstep.app.exception.AuthenticationException; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import java.util.Base64; import java.util.List; @RestController -public class MemberController { +public class MemberController extends BaseController { + private final MemberService memberService; private final MemberRepository memberRepository; - public MemberController(MemberRepository memberRepository) { + + public MemberController(MemberService memberService, MemberRepository memberRepository) { + this.memberService = memberService; this.memberRepository = memberRepository; } @GetMapping("/members") - public ResponseEntity> list() { + public ResponseEntity> list(@RequestHeader("Authorization") String basicToken) { + memberService.validate(basicToken); List members = memberRepository.findAll(); return ResponseEntity.ok(members); } From f816ce9ee699bc71e7ce34ff0630ebf9f5e64f41 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 13:59:26 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor(MemberService):=20email,=20pw?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20Member=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=EB=B6=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/domain/MemberService.java | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index 13cfa98..b8ebd80 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -6,9 +6,12 @@ import javax.servlet.http.HttpSession; import java.util.Base64; +import java.util.Optional; @Service public class MemberService { + + private static String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; private MemberRepository memberRepo; @@ -17,15 +20,8 @@ public MemberService(MemberRepository memberRepo) { } public void login(HttpSession session, String email, String password) { - memberRepo.findByEmail(email).ifPresentOrElse(member -> { - if (member.getPassword().equals(password)) { - session.setAttribute("SPRING_SECURITY_CONTEXT", member); - return; - } - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - }, () -> { - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - }); + Member member = findUserByCredential(email, password); + session.setAttribute(SPRING_SECURITY_CONTEXT, member); } public void validate(String basicToken){ @@ -35,19 +31,15 @@ public void validate(String basicToken){ try { email = decoded.substring(0, decoded.indexOf(":")); password = decoded.substring(decoded.indexOf(":") + 1); + findUserByCredential(email, password); } catch (StringIndexOutOfBoundsException e){ throw new AuthenticationException(AuthErrorCodes.WRONG_BASIC_TOKEN_FORMAT); } - - final String finalPassword = password; - memberRepo.findByEmail(email).ifPresentOrElse(member -> { - if (member.getPassword().equals(finalPassword)) { - return; - } - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - }, () -> { - throw new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST); - }); + } + private Member findUserByCredential(String email, String pw) { + return memberRepo.findByEmail(email) + .filter(user -> user.getPassword().equals(pw)) + .orElseThrow(() -> new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST)); } } From f2de6e01ef9518eaa31e10d66809ef9676ca5352 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 16:47:23 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat(BasicAuthenticationInterceptor):=20B?= =?UTF-8?q?asic=20Auth=20=EC=B2=98=EB=A6=AC=EC=9A=A9=20interceptor=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해당 interceptor를 등록하기 위한 webconfig 생성 및 httpStatus 조회용 api 추가 --- .../java/nextstep/app/config/WebConfig.java | 21 ++++++++++++ .../exception/AuthenticationException.java | 6 +++- .../BasicAuthenticationInterceptor.java | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nextstep/app/config/WebConfig.java create mode 100644 src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java 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..38c1e03 --- /dev/null +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -0,0 +1,21 @@ +package nextstep.app.config; + +import nextstep.app.domain.MemberService; +import nextstep.app.interceptor.BasicAuthenticationInterceptor; +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 MemberService memberService; + + public WebConfig(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new BasicAuthenticationInterceptor(memberService)).addPathPatterns("/members"); + } +} diff --git a/src/main/java/nextstep/app/exception/AuthenticationException.java b/src/main/java/nextstep/app/exception/AuthenticationException.java index 0c43309..7ac0b01 100644 --- a/src/main/java/nextstep/app/exception/AuthenticationException.java +++ b/src/main/java/nextstep/app/exception/AuthenticationException.java @@ -6,13 +6,17 @@ import java.util.Map; public class AuthenticationException extends RuntimeException{ - HttpStatus status; + private HttpStatus status; public AuthenticationException(AuthErrorCodes exceptions) { super(exceptions.getMessage()); this.status = exceptions.getStatusCode(); } + public HttpStatus getStatus() { + return status; + } + public Map getResponseBody() { return Map.of("detail", this.getMessage()); } diff --git a/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java b/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java new file mode 100644 index 0000000..4d4982c --- /dev/null +++ b/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java @@ -0,0 +1,32 @@ +package nextstep.app.interceptor; + +import nextstep.app.domain.MemberService; +import nextstep.app.exception.AuthenticationException; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class BasicAuthenticationInterceptor implements HandlerInterceptor { + + private MemberService memberService; + + public BasicAuthenticationInterceptor(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + String basicToken = request.getHeader("Authorization"); + try { + memberService.validate(basicToken); + } + catch(AuthenticationException e){ + response.setStatus(e.getStatus().value()); + response.getWriter().println(String.format("\"%s\" : \"%s\"\n","message", e.getMessage())); + return false; + } + return true; + } +} From 3f94f3629a90514d739e4432867ba738ab603299 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 16:48:22 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat(MemberListResponse):=20Response?= =?UTF-8?q?=EC=9A=A9=20dto=20=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Response가 직접적으로 entity를 리턴하지 안도록 수정 - Password 필드의 경우 리턴하면 안되므로 해당 내용도 추가 --- .../nextstep/app/domain/MemberService.java | 9 ++++++++ .../app/domain/dto/MemberListResponse.java | 21 +++++++++++++++++++ .../nextstep/app/ui/MemberController.java | 11 ++++------ 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/main/java/nextstep/app/domain/dto/MemberListResponse.java diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index b8ebd80..4e69975 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -1,11 +1,14 @@ package nextstep.app.domain; +import nextstep.app.domain.dto.MemberListResponse; import nextstep.app.exception.AuthErrorCodes; import nextstep.app.exception.AuthenticationException; import org.springframework.stereotype.Service; import javax.servlet.http.HttpSession; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Optional; @Service @@ -24,6 +27,12 @@ public void login(HttpSession session, String email, String password) { session.setAttribute(SPRING_SECURITY_CONTEXT, member); } + public List findAllMembers(){ + List response = new ArrayList<>(); + memberRepo.findAll().stream().map(MemberListResponse::of).forEach(response::add); + return response; + } + public void validate(String basicToken){ String decoded = new String(Base64.getDecoder().decode(basicToken.replace("Basic ", ""))); String email = ""; diff --git a/src/main/java/nextstep/app/domain/dto/MemberListResponse.java b/src/main/java/nextstep/app/domain/dto/MemberListResponse.java new file mode 100644 index 0000000..76aaccf --- /dev/null +++ b/src/main/java/nextstep/app/domain/dto/MemberListResponse.java @@ -0,0 +1,21 @@ +package nextstep.app.domain.dto; + +import nextstep.app.domain.Member; + +public class MemberListResponse { + private final String email; + private final String name; + private final String imageUrl; + + public MemberListResponse(String email, String name, String imageUrl) { + this.email = email; + this.name = name; + this.imageUrl = imageUrl; + } + + public static MemberListResponse of(Member member){ + return new MemberListResponse( + member.getEmail(), member.getName(), member.getImageUrl() + ); + } +} diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index a4caa14..d7b3cf3 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -3,6 +3,7 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; import nextstep.app.domain.MemberService; +import nextstep.app.domain.dto.MemberListResponse; import nextstep.app.exception.AuthenticationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,18 +19,14 @@ public class MemberController extends BaseController { private final MemberService memberService; - private final MemberRepository memberRepository; - - public MemberController(MemberService memberService, MemberRepository memberRepository) { + public MemberController(MemberService memberService) { this.memberService = memberService; - this.memberRepository = memberRepository; } @GetMapping("/members") - public ResponseEntity> list(@RequestHeader("Authorization") String basicToken) { - memberService.validate(basicToken); - List members = memberRepository.findAll(); + public ResponseEntity> list() { + List members = memberService.findAllMembers(); return ResponseEntity.ok(members); } From c9f9a09c23370a4d8f834523816b110ee5956e43 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Sun, 3 Nov 2024 16:49:34 +0900 Subject: [PATCH 11/16] =?UTF-8?q?fix(MemberListResponse):=20Getter?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/domain/dto/MemberListResponse.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/nextstep/app/domain/dto/MemberListResponse.java b/src/main/java/nextstep/app/domain/dto/MemberListResponse.java index 76aaccf..f598c3c 100644 --- a/src/main/java/nextstep/app/domain/dto/MemberListResponse.java +++ b/src/main/java/nextstep/app/domain/dto/MemberListResponse.java @@ -18,4 +18,16 @@ public static MemberListResponse of(Member member){ member.getEmail(), member.getName(), member.getImageUrl() ); } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public String getImageUrl() { + return imageUrl; + } } From 50b642613c9b4f76d35c1fb496f8b7db7ededbe5 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Mon, 4 Nov 2024 21:07:06 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat(step2)=20:=20Interceptor=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EA=B5=AC=ED=98=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EB=B0=8F=20security=20pakcage=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ .../java/nextstep/app/config/WebConfig.java | 2 +- src/main/java/nextstep/app/domain/Member.java | 26 +++---------------- .../nextstep/app/domain/MemberService.java | 22 +++++++++++----- .../java/nextstep/app/ui/BaseController.java | 2 +- .../java/nextstep/app/ui/LoginController.java | 4 --- .../nextstep/app/ui/MemberController.java | 7 ----- .../exception/AuthErrorCodes.java | 12 +++------ .../exception/AuthenticationException.java | 11 +++----- .../BasicAuthenticationInterceptor.java | 6 ++--- 10 files changed, 34 insertions(+), 61 deletions(-) rename src/main/java/nextstep/{app => security}/exception/AuthErrorCodes.java (73%) rename src/main/java/nextstep/{app => security}/exception/AuthenticationException.java (69%) rename src/main/java/nextstep/{app => security}/interceptor/BasicAuthenticationInterceptor.java (86%) diff --git a/build.gradle b/build.gradle index 9976616..fca4249 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,9 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + compileOnly 'org.projectlombok:lombok:1.18.30' // Use the latest version + annotationProcessor 'org.projectlombok:lombok:1.18.30' } tasks.named('test') { diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index 38c1e03..2c76e25 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,7 +1,7 @@ package nextstep.app.config; import nextstep.app.domain.MemberService; -import nextstep.app.interceptor.BasicAuthenticationInterceptor; +import nextstep.security.interceptor.BasicAuthenticationInterceptor; 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/app/domain/Member.java b/src/main/java/nextstep/app/domain/Member.java index 6cafa9c..cc52bec 100644 --- a/src/main/java/nextstep/app/domain/Member.java +++ b/src/main/java/nextstep/app/domain/Member.java @@ -1,31 +1,13 @@ package nextstep.app.domain; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter @AllArgsConstructor public class Member { private final String email; private final String password; private final String name; private final String imageUrl; - public Member(String email, String password, String name, String imageUrl) { - this.email = email; - this.password = password; - this.name = name; - this.imageUrl = imageUrl; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } - - public String getName() { - return name; - } - - public String getImageUrl() { - return imageUrl; - } } diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index 4e69975..77277fd 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -1,8 +1,10 @@ package nextstep.app.domain; import nextstep.app.domain.dto.MemberListResponse; -import nextstep.app.exception.AuthErrorCodes; -import nextstep.app.exception.AuthenticationException; +import nextstep.security.core.UserDetail; +import nextstep.security.core.UserDetailService; +import nextstep.security.exception.AuthErrorCodes; +import nextstep.security.exception.AuthenticationException; import org.springframework.stereotype.Service; import javax.servlet.http.HttpSession; @@ -12,10 +14,10 @@ import java.util.Optional; @Service -public class MemberService { +public class MemberService implements UserDetailService { - private static String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; - private MemberRepository memberRepo; + private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; + private final MemberRepository memberRepo; public MemberService(MemberRepository memberRepo) { @@ -35,8 +37,8 @@ public List findAllMembers(){ public void validate(String basicToken){ String decoded = new String(Base64.getDecoder().decode(basicToken.replace("Basic ", ""))); - String email = ""; - String password = ""; + String email; + String password; try { email = decoded.substring(0, decoded.indexOf(":")); password = decoded.substring(decoded.indexOf(":") + 1); @@ -51,4 +53,10 @@ private Member findUserByCredential(String email, String pw) { .filter(user -> user.getPassword().equals(pw)) .orElseThrow(() -> new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST)); } + + @Override + public UserDetail findUserByUsername(String username) { + Optional member = memberRepo.findByEmail(username); + return member.map(value -> new UserDetail(value.getEmail(), value.getPassword())).orElse(null); + } } diff --git a/src/main/java/nextstep/app/ui/BaseController.java b/src/main/java/nextstep/app/ui/BaseController.java index f074762..7ff93c2 100644 --- a/src/main/java/nextstep/app/ui/BaseController.java +++ b/src/main/java/nextstep/app/ui/BaseController.java @@ -1,6 +1,6 @@ package nextstep.app.ui; -import nextstep.app.exception.AuthenticationException; +import nextstep.security.exception.AuthenticationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 6861fe4..7635094 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,11 +1,7 @@ package nextstep.app.ui; -import nextstep.app.domain.MemberRepository; import nextstep.app.domain.MemberService; -import nextstep.app.exception.AuthenticationException; -import org.springframework.http.HttpStatus; 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; diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index d7b3cf3..c5a6d59 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -1,18 +1,11 @@ package nextstep.app.ui; -import nextstep.app.domain.Member; -import nextstep.app.domain.MemberRepository; import nextstep.app.domain.MemberService; import nextstep.app.domain.dto.MemberListResponse; -import nextstep.app.exception.AuthenticationException; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; -import java.util.Base64; import java.util.List; @RestController diff --git a/src/main/java/nextstep/app/exception/AuthErrorCodes.java b/src/main/java/nextstep/security/exception/AuthErrorCodes.java similarity index 73% rename from src/main/java/nextstep/app/exception/AuthErrorCodes.java rename to src/main/java/nextstep/security/exception/AuthErrorCodes.java index 103dc1e..7dd5eb2 100644 --- a/src/main/java/nextstep/app/exception/AuthErrorCodes.java +++ b/src/main/java/nextstep/security/exception/AuthErrorCodes.java @@ -1,7 +1,9 @@ -package nextstep.app.exception; +package nextstep.security.exception; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter public enum AuthErrorCodes { UNAUTHORIZED_LOGIN_REQUEST(HttpStatus.UNAUTHORIZED, "Username not exist or Wrong password"), WRONG_BASIC_TOKEN_FORMAT(HttpStatus.UNAUTHORIZED, "Wrong basic token format"); @@ -13,12 +15,4 @@ public enum AuthErrorCodes { this.statusCode = statusCode; this.message = message; } - - public String getMessage() { - return message; - } - - public HttpStatus getStatusCode() { - return statusCode; - } } diff --git a/src/main/java/nextstep/app/exception/AuthenticationException.java b/src/main/java/nextstep/security/exception/AuthenticationException.java similarity index 69% rename from src/main/java/nextstep/app/exception/AuthenticationException.java rename to src/main/java/nextstep/security/exception/AuthenticationException.java index 7ac0b01..090910b 100644 --- a/src/main/java/nextstep/app/exception/AuthenticationException.java +++ b/src/main/java/nextstep/security/exception/AuthenticationException.java @@ -1,22 +1,19 @@ -package nextstep.app.exception; +package nextstep.security.exception; +import lombok.Getter; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import java.util.Map; +@Getter public class AuthenticationException extends RuntimeException{ - private HttpStatus status; + private final HttpStatus status; public AuthenticationException(AuthErrorCodes exceptions) { super(exceptions.getMessage()); this.status = exceptions.getStatusCode(); } - public HttpStatus getStatus() { - return status; - } - public Map getResponseBody() { return Map.of("detail", this.getMessage()); } diff --git a/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java similarity index 86% rename from src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java rename to src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java index 4d4982c..5ae850d 100644 --- a/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java +++ b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java @@ -1,7 +1,7 @@ -package nextstep.app.interceptor; +package nextstep.security.interceptor; import nextstep.app.domain.MemberService; -import nextstep.app.exception.AuthenticationException; +import nextstep.security.exception.AuthenticationException; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; @@ -10,7 +10,7 @@ public class BasicAuthenticationInterceptor implements HandlerInterceptor { - private MemberService memberService; + private final MemberService memberService; public BasicAuthenticationInterceptor(MemberService memberService) { this.memberService = memberService; From 83615e5ff7cea166431c3cb50a063ba97a51e662 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Mon, 4 Nov 2024 21:10:05 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat(UserDetails)=20:=20userDetail=20?= =?UTF-8?q?=EB=B0=8F=20UserDetailService=20interface=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/security/core/UserDetail.java | 12 ++++++++++++ .../nextstep/security/core/UserDetailService.java | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 src/main/java/nextstep/security/core/UserDetail.java create mode 100644 src/main/java/nextstep/security/core/UserDetailService.java diff --git a/src/main/java/nextstep/security/core/UserDetail.java b/src/main/java/nextstep/security/core/UserDetail.java new file mode 100644 index 0000000..4f1d7c1 --- /dev/null +++ b/src/main/java/nextstep/security/core/UserDetail.java @@ -0,0 +1,12 @@ +package nextstep.security.core; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserDetail { + private String username; + private String password; + +} diff --git a/src/main/java/nextstep/security/core/UserDetailService.java b/src/main/java/nextstep/security/core/UserDetailService.java new file mode 100644 index 0000000..ef5d6c4 --- /dev/null +++ b/src/main/java/nextstep/security/core/UserDetailService.java @@ -0,0 +1,5 @@ +package nextstep.security.core; + +public interface UserDetailService { + UserDetail findUserByUsername(String username); +} From f4ac5dc2ccd2aca17c3480a18d3b0eb107450c6f Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Mon, 4 Nov 2024 22:39:21 +0900 Subject: [PATCH 14/16] =?UTF-8?q?feat(UsernamePasswordAuth):=20id,=20pw?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=EC=A6=9D=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/config/SpringSecurityConfig.java | 41 ++++++++++++ .../java/nextstep/app/config/WebConfig.java | 2 - .../nextstep/app/domain/MemberService.java | 4 +- .../core/DefaultSecurityFilterChain.java | 22 +++++++ .../security/core/FilterChainProxy.java | 63 +++++++++++++++++++ .../security/core/SecurityFilterChain.java | 12 ++++ .../security/core/SecurityPrincipal.java | 14 +++++ .../core/authentication/Authentication.java | 13 ++++ .../authentication/AuthenticationManager.java | 19 ++++++ .../AuthenticationProvider.java | 11 ++++ ...ractUserDetailsAuthenticationProvider.java | 35 +++++++++++ .../UsernamePasswordAuthenticationToken.java | 14 +++++ .../core/{ => userdetails}/UserDetail.java | 2 +- .../{ => userdetails}/UserDetailService.java | 2 +- .../BasicTokenAuthenticationFilter.java | 21 +++++++ .../UsernamePasswordAuthenticationFilter.java | 51 +++++++++++++++ .../BasicAuthenticationInterceptor.java | 32 ---------- 17 files changed, 320 insertions(+), 38 deletions(-) create mode 100644 src/main/java/nextstep/app/config/SpringSecurityConfig.java create mode 100644 src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java create mode 100644 src/main/java/nextstep/security/core/FilterChainProxy.java create mode 100644 src/main/java/nextstep/security/core/SecurityFilterChain.java create mode 100644 src/main/java/nextstep/security/core/SecurityPrincipal.java create mode 100644 src/main/java/nextstep/security/core/authentication/Authentication.java create mode 100644 src/main/java/nextstep/security/core/authentication/AuthenticationManager.java create mode 100644 src/main/java/nextstep/security/core/authentication/AuthenticationProvider.java create mode 100644 src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java create mode 100644 src/main/java/nextstep/security/core/authentication/provider/UsernamePasswordAuthenticationToken.java rename src/main/java/nextstep/security/core/{ => userdetails}/UserDetail.java (79%) rename src/main/java/nextstep/security/core/{ => userdetails}/UserDetailService.java (67%) create mode 100644 src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java create mode 100644 src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java delete mode 100644 src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java diff --git a/src/main/java/nextstep/app/config/SpringSecurityConfig.java b/src/main/java/nextstep/app/config/SpringSecurityConfig.java new file mode 100644 index 0000000..2b8fcac --- /dev/null +++ b/src/main/java/nextstep/app/config/SpringSecurityConfig.java @@ -0,0 +1,41 @@ +package nextstep.app.config; + +import lombok.RequiredArgsConstructor; +import nextstep.security.core.DefaultSecurityFilterChain; +import nextstep.security.core.authentication.AuthenticationManager; +import nextstep.security.core.FilterChainProxy; +import nextstep.security.core.authentication.provider.AbstractUserDetailsAuthenticationProvider; +import nextstep.security.core.userdetails.UserDetailService; +import nextstep.security.filter.UsernamePasswordAuthenticationFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.web.filter.DelegatingFilterProxy; + +import javax.servlet.Filter; +import java.util.List; + +@RequiredArgsConstructor +public class SpringSecurityConfig { + private final UserDetailService userDetailService; + private AuthenticationManager authenticationManager; + + @Bean + public DelegatingFilterProxy delegatingFilterProxy() { + final List filters = List.of(new UsernamePasswordAuthenticationFilter(authenticationManager())); + + FilterChainProxy filterChainProxy = new FilterChainProxy(List.of(new DefaultSecurityFilterChain(filters))); + + return new DelegatingFilterProxy(filterChainProxy); + } + + private AuthenticationManager authenticationManager() { + if (this.authenticationManager == null) { + authenticationManager = new AuthenticationManager(List.of(usernamePasswordAuthenticationProvider())); + } + return this.authenticationManager; + } + + private AbstractUserDetailsAuthenticationProvider usernamePasswordAuthenticationProvider() { + return new AbstractUserDetailsAuthenticationProvider(userDetailService); + } + +} diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index 2c76e25..f14c29f 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,7 +1,6 @@ package nextstep.app.config; import nextstep.app.domain.MemberService; -import nextstep.security.interceptor.BasicAuthenticationInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -16,6 +15,5 @@ public WebConfig(MemberService memberService) { @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new BasicAuthenticationInterceptor(memberService)).addPathPatterns("/members"); } } diff --git a/src/main/java/nextstep/app/domain/MemberService.java b/src/main/java/nextstep/app/domain/MemberService.java index 77277fd..62c510d 100644 --- a/src/main/java/nextstep/app/domain/MemberService.java +++ b/src/main/java/nextstep/app/domain/MemberService.java @@ -1,8 +1,8 @@ package nextstep.app.domain; import nextstep.app.domain.dto.MemberListResponse; -import nextstep.security.core.UserDetail; -import nextstep.security.core.UserDetailService; +import nextstep.security.core.userdetails.UserDetail; +import nextstep.security.core.userdetails.UserDetailService; import nextstep.security.exception.AuthErrorCodes; import nextstep.security.exception.AuthenticationException; import org.springframework.stereotype.Service; diff --git a/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java b/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java new file mode 100644 index 0000000..441078a --- /dev/null +++ b/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java @@ -0,0 +1,22 @@ +package nextstep.security.core; + +import lombok.RequiredArgsConstructor; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +@RequiredArgsConstructor +public class DefaultSecurityFilterChain implements SecurityFilterChain{ + + private final List filters; + @Override + public boolean matches(HttpServletRequest request) { + return true; + } + + @Override + public List getFilters() { + return filters; + } +} diff --git a/src/main/java/nextstep/security/core/FilterChainProxy.java b/src/main/java/nextstep/security/core/FilterChainProxy.java new file mode 100644 index 0000000..ae2eafc --- /dev/null +++ b/src/main/java/nextstep/security/core/FilterChainProxy.java @@ -0,0 +1,63 @@ +package nextstep.security.core; + +import nextstep.security.core.SecurityFilterChain; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; + +public class FilterChainProxy extends GenericFilterBean { + private final List filterChains; + + public FilterChainProxy(List filterChains) { + this.filterChains = filterChains; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + List filters = getFilters((HttpServletRequest) request); + + VirtualFilterChain virtualFilterChain = new VirtualFilterChain(chain, filters); + virtualFilterChain.doFilter(request, response); + } + + private List getFilters(HttpServletRequest request) { + for (SecurityFilterChain chain : this.filterChains) { + if (chain.matches(request)) { + return chain.getFilters(); + } + } + return null; + } + + private static final class VirtualFilterChain implements FilterChain { + + private final FilterChain originalChain; + + private final List additionalFilters; + + private final int size; + + private int currentPosition = 0; + + private VirtualFilterChain(FilterChain chain, List additionalFilters) { + this.originalChain = chain; + this.additionalFilters = additionalFilters; + this.size = additionalFilters.size(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + if (this.currentPosition == this.size) { + this.originalChain.doFilter(request, response); + return; + } + this.currentPosition++; + Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1); + nextFilter.doFilter(request, response, this); + } + + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/security/core/SecurityFilterChain.java b/src/main/java/nextstep/security/core/SecurityFilterChain.java new file mode 100644 index 0000000..e920419 --- /dev/null +++ b/src/main/java/nextstep/security/core/SecurityFilterChain.java @@ -0,0 +1,12 @@ +package nextstep.security.core; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +public interface SecurityFilterChain { + + boolean matches(HttpServletRequest request); + + List getFilters(); +} diff --git a/src/main/java/nextstep/security/core/SecurityPrincipal.java b/src/main/java/nextstep/security/core/SecurityPrincipal.java new file mode 100644 index 0000000..7047293 --- /dev/null +++ b/src/main/java/nextstep/security/core/SecurityPrincipal.java @@ -0,0 +1,14 @@ +package nextstep.security.core; + +public enum SecurityPrincipal { + + USERNAME_PASSWORD_AUTHENTICATION("username password auth"), + BASIC_TOKEN_AUTHENTICATION("basic token auth"); + ; + + final String expression; + + SecurityPrincipal(String expression) { + this.expression = expression; + } +} diff --git a/src/main/java/nextstep/security/core/authentication/Authentication.java b/src/main/java/nextstep/security/core/authentication/Authentication.java new file mode 100644 index 0000000..90a4d21 --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/Authentication.java @@ -0,0 +1,13 @@ +package nextstep.security.core.authentication; + +import lombok.AllArgsConstructor; +import lombok.Data; +import nextstep.security.core.SecurityPrincipal; + +@AllArgsConstructor +@Data +public class Authentication { + private Object credential; + private SecurityPrincipal principal; + private boolean isAuthenticated; +} diff --git a/src/main/java/nextstep/security/core/authentication/AuthenticationManager.java b/src/main/java/nextstep/security/core/authentication/AuthenticationManager.java new file mode 100644 index 0000000..3d61160 --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/AuthenticationManager.java @@ -0,0 +1,19 @@ +package nextstep.security.core.authentication; + +import lombok.RequiredArgsConstructor; +import nextstep.security.core.SecurityPrincipal; +import nextstep.security.exception.AuthErrorCodes; +import nextstep.security.exception.AuthenticationException; + +import java.util.List; + +@RequiredArgsConstructor +public class AuthenticationManager { + private final List authenticationProviders; + + public AuthenticationProvider getAuthenticationProvider(SecurityPrincipal principal){ + return authenticationProviders.stream().filter(provider -> provider.supports(principal)).findAny().orElseThrow( + ()-> new AuthenticationException(AuthErrorCodes.UNAUTHORIZED_LOGIN_REQUEST) + ); + } +} diff --git a/src/main/java/nextstep/security/core/authentication/AuthenticationProvider.java b/src/main/java/nextstep/security/core/authentication/AuthenticationProvider.java new file mode 100644 index 0000000..26eb607 --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/AuthenticationProvider.java @@ -0,0 +1,11 @@ +package nextstep.security.core.authentication; + +import nextstep.security.core.SecurityPrincipal; +import nextstep.security.exception.AuthenticationException; + +public interface AuthenticationProvider { + Authentication authenticate(Authentication authentication) throws AuthenticationException; + + boolean supports(SecurityPrincipal securityPrincipal); + +} diff --git a/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java b/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java new file mode 100644 index 0000000..211c18f --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java @@ -0,0 +1,35 @@ +package nextstep.security.core.authentication.provider; + +import lombok.RequiredArgsConstructor; +import nextstep.security.core.SecurityPrincipal; +import nextstep.security.core.authentication.Authentication; +import nextstep.security.core.authentication.AuthenticationProvider; +import nextstep.security.core.userdetails.UserDetail; +import nextstep.security.core.userdetails.UserDetailService; +import nextstep.security.exception.AuthenticationException; + +import java.util.List; + +@RequiredArgsConstructor +public class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider { + + static private final List supports = List.of(SecurityPrincipal.BASIC_TOKEN_AUTHENTICATION, SecurityPrincipal.USERNAME_PASSWORD_AUTHENTICATION); + + private final UserDetailService userDetailService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication.getCredential(); + UserDetail user = userDetailService.findUserByUsername(token.getUsername()); + authentication.setAuthenticated(false); + if (user != null && user.getPassword().equals(token.getPassword())) { + authentication.setAuthenticated(true); + } + return authentication; + } + + @Override + public boolean supports(SecurityPrincipal securityPrincipal) { + return supports.contains(securityPrincipal); + } +} diff --git a/src/main/java/nextstep/security/core/authentication/provider/UsernamePasswordAuthenticationToken.java b/src/main/java/nextstep/security/core/authentication/provider/UsernamePasswordAuthenticationToken.java new file mode 100644 index 0000000..d61d40c --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/provider/UsernamePasswordAuthenticationToken.java @@ -0,0 +1,14 @@ +package nextstep.security.core.authentication.provider; + + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UsernamePasswordAuthenticationToken { + + private String username; + private String password; + +} diff --git a/src/main/java/nextstep/security/core/UserDetail.java b/src/main/java/nextstep/security/core/userdetails/UserDetail.java similarity index 79% rename from src/main/java/nextstep/security/core/UserDetail.java rename to src/main/java/nextstep/security/core/userdetails/UserDetail.java index 4f1d7c1..af15da5 100644 --- a/src/main/java/nextstep/security/core/UserDetail.java +++ b/src/main/java/nextstep/security/core/userdetails/UserDetail.java @@ -1,4 +1,4 @@ -package nextstep.security.core; +package nextstep.security.core.userdetails; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/nextstep/security/core/UserDetailService.java b/src/main/java/nextstep/security/core/userdetails/UserDetailService.java similarity index 67% rename from src/main/java/nextstep/security/core/UserDetailService.java rename to src/main/java/nextstep/security/core/userdetails/UserDetailService.java index ef5d6c4..0250456 100644 --- a/src/main/java/nextstep/security/core/UserDetailService.java +++ b/src/main/java/nextstep/security/core/userdetails/UserDetailService.java @@ -1,4 +1,4 @@ -package nextstep.security.core; +package nextstep.security.core.userdetails; public interface UserDetailService { UserDetail findUserByUsername(String username); diff --git a/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java new file mode 100644 index 0000000..28f3d4f --- /dev/null +++ b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java @@ -0,0 +1,21 @@ +package nextstep.security.filter; + +import javax.servlet.*; +import java.io.IOException; + +public class BasicTokenAuthenticationFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + @Override + public void destroy() { + Filter.super.destroy(); + } +} diff --git a/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java new file mode 100644 index 0000000..a5d299e --- /dev/null +++ b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java @@ -0,0 +1,51 @@ +package nextstep.security.filter; + +import lombok.RequiredArgsConstructor; +import nextstep.security.core.authentication.provider.UsernamePasswordAuthenticationToken; +import nextstep.security.core.SecurityPrincipal; +import nextstep.security.core.authentication.Authentication; +import nextstep.security.core.authentication.AuthenticationManager; +import nextstep.security.core.authentication.AuthenticationProvider; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RequiredArgsConstructor +public class UsernamePasswordAuthenticationFilter implements Filter { + private static final SecurityPrincipal PRINCIPAL = SecurityPrincipal.USERNAME_PASSWORD_AUTHENTICATION; + private final AuthenticationManager manager; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String username = httpServletRequest.getParameter("username"); + String password = httpServletRequest.getParameter("password"); + Authentication usernamePasswordAuth = new Authentication( + new UsernamePasswordAuthenticationToken(username, password), + PRINCIPAL, + false + ); + + AuthenticationProvider provider = manager.getAuthenticationProvider(PRINCIPAL); + Authentication result = provider.authenticate(usernamePasswordAuth); + + if(result.isAuthenticated()) + chain.doFilter(request, response); + + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpServletResponse.flushBuffer(); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + @Override + public void destroy() { + Filter.super.destroy(); + } +} diff --git a/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java deleted file mode 100644 index 5ae850d..0000000 --- a/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java +++ /dev/null @@ -1,32 +0,0 @@ -package nextstep.security.interceptor; - -import nextstep.app.domain.MemberService; -import nextstep.security.exception.AuthenticationException; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class BasicAuthenticationInterceptor implements HandlerInterceptor { - - private final MemberService memberService; - - public BasicAuthenticationInterceptor(MemberService memberService) { - this.memberService = memberService; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { - String basicToken = request.getHeader("Authorization"); - try { - memberService.validate(basicToken); - } - catch(AuthenticationException e){ - response.setStatus(e.getStatus().value()); - response.getWriter().println(String.format("\"%s\" : \"%s\"\n","message", e.getMessage())); - return false; - } - return true; - } -} From b169298c0fcb7dfdb06a5893e69579932f1386ec Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Mon, 4 Nov 2024 23:02:48 +0900 Subject: [PATCH 15/16] =?UTF-8?q?feat(BasicTokenAuth):=20Basic=20Token=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이후 인증 filter 2회 돌아가지 않도록 설정 필요 --- .../app/config/SpringSecurityConfig.java | 8 ++++- ...ractUserDetailsAuthenticationProvider.java | 19 ++++++---- .../BasicTokenAuthenticationToken.java | 21 +++++++++++ .../BasicTokenAuthenticationFilter.java | 35 +++++++++++++++++++ 4 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java diff --git a/src/main/java/nextstep/app/config/SpringSecurityConfig.java b/src/main/java/nextstep/app/config/SpringSecurityConfig.java index 2b8fcac..ed26ac4 100644 --- a/src/main/java/nextstep/app/config/SpringSecurityConfig.java +++ b/src/main/java/nextstep/app/config/SpringSecurityConfig.java @@ -6,13 +6,16 @@ import nextstep.security.core.FilterChainProxy; import nextstep.security.core.authentication.provider.AbstractUserDetailsAuthenticationProvider; import nextstep.security.core.userdetails.UserDetailService; +import nextstep.security.filter.BasicTokenAuthenticationFilter; import nextstep.security.filter.UsernamePasswordAuthenticationFilter; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; import javax.servlet.Filter; import java.util.List; +@Configuration @RequiredArgsConstructor public class SpringSecurityConfig { private final UserDetailService userDetailService; @@ -20,7 +23,10 @@ public class SpringSecurityConfig { @Bean public DelegatingFilterProxy delegatingFilterProxy() { - final List filters = List.of(new UsernamePasswordAuthenticationFilter(authenticationManager())); + final List filters = List.of( + new UsernamePasswordAuthenticationFilter(authenticationManager()), + new BasicTokenAuthenticationFilter(authenticationManager()) + ); FilterChainProxy filterChainProxy = new FilterChainProxy(List.of(new DefaultSecurityFilterChain(filters))); diff --git a/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java b/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java index 211c18f..89e689a 100644 --- a/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java +++ b/src/main/java/nextstep/security/core/authentication/provider/AbstractUserDetailsAuthenticationProvider.java @@ -19,13 +19,20 @@ public class AbstractUserDetailsAuthenticationProvider implements Authentication @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication.getCredential(); - UserDetail user = userDetailService.findUserByUsername(token.getUsername()); - authentication.setAuthenticated(false); - if (user != null && user.getPassword().equals(token.getPassword())) { - authentication.setAuthenticated(true); + try { + UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication.getCredential(); + UserDetail user = userDetailService.findUserByUsername(token.getUsername()); + authentication.setAuthenticated(false); + if (user != null && user.getPassword().equals(token.getPassword())) { + authentication.setAuthenticated(true); + } + } + catch (Exception e){ + authentication.setAuthenticated(false); + } + finally { + return authentication; } - return authentication; } @Override diff --git a/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java b/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java new file mode 100644 index 0000000..40c5a35 --- /dev/null +++ b/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java @@ -0,0 +1,21 @@ +package nextstep.security.core.authentication.provider; + +import nextstep.security.exception.AuthErrorCodes; +import nextstep.security.exception.AuthenticationException; + +public class BasicTokenAuthenticationToken extends UsernamePasswordAuthenticationToken{ + + + public BasicTokenAuthenticationToken(String basic){ + super(null, null); + try{ + if(basic.startsWith("Basic ")){ + this.setUsername(basic.replace("Basic ", "").split(":")[0]); + this.setPassword(basic.replace("Basic ", "").split(":")[1]); + } + }catch (Exception e){ + throw new AuthenticationException(AuthErrorCodes.WRONG_BASIC_TOKEN_FORMAT); + } + + } +} diff --git a/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java index 28f3d4f..946c89b 100644 --- a/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java @@ -1,12 +1,47 @@ package nextstep.security.filter; +import lombok.RequiredArgsConstructor; +import nextstep.security.core.SecurityPrincipal; +import nextstep.security.core.authentication.Authentication; +import nextstep.security.core.authentication.AuthenticationManager; +import nextstep.security.core.authentication.provider.BasicTokenAuthenticationToken; +import nextstep.security.exception.AuthErrorCodes; +import nextstep.security.exception.AuthenticationException; + import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; +@RequiredArgsConstructor public class BasicTokenAuthenticationFilter implements Filter { + private final AuthenticationManager authenticationManager; + + private static final SecurityPrincipal PRINCIPAL = SecurityPrincipal.BASIC_TOKEN_AUTHENTICATION; + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String basic = httpServletRequest.getHeader("authorization"); + try { + Authentication authentication = new Authentication( + new BasicTokenAuthenticationToken(basic), + SecurityPrincipal.BASIC_TOKEN_AUTHENTICATION, + false + ); + + authentication = authenticationManager.getAuthenticationProvider(PRINCIPAL).authenticate(authentication); + if (authentication.isAuthenticated()) { + chain.doFilter(request, response); + return; + } + }catch (Exception e){ + + } + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpServletResponse.flushBuffer(); } @Override From e985201e18992b5a0febd9b59562c5f7c24852c1 Mon Sep 17 00:00:00 2001 From: rohsik2 Date: Mon, 4 Nov 2024 23:38:59 +0900 Subject: [PATCH 16/16] fix(basicTokenAuthToken): fix base64 logic --- .../nextstep/app/config/SpringSecurityConfig.java | 11 +++++++++-- .../security/core/DefaultSecurityFilterChain.java | 4 +++- .../nextstep/security/core/FilterChainProxy.java | 3 ++- .../provider/BasicTokenAuthenticationToken.java | 10 ++++++++-- .../filter/BasicTokenAuthenticationFilter.java | 14 ++++++++------ 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main/java/nextstep/app/config/SpringSecurityConfig.java b/src/main/java/nextstep/app/config/SpringSecurityConfig.java index ed26ac4..a5b86bd 100644 --- a/src/main/java/nextstep/app/config/SpringSecurityConfig.java +++ b/src/main/java/nextstep/app/config/SpringSecurityConfig.java @@ -24,11 +24,18 @@ public class SpringSecurityConfig { @Bean public DelegatingFilterProxy delegatingFilterProxy() { final List filters = List.of( - new UsernamePasswordAuthenticationFilter(authenticationManager()), + new UsernamePasswordAuthenticationFilter(authenticationManager()) + ); + + final List basicTokenFilters = List.of( new BasicTokenAuthenticationFilter(authenticationManager()) ); - FilterChainProxy filterChainProxy = new FilterChainProxy(List.of(new DefaultSecurityFilterChain(filters))); + FilterChainProxy filterChainProxy = new FilterChainProxy( + List.of( + new DefaultSecurityFilterChain(new String[]{"/members"}, basicTokenFilters), + new DefaultSecurityFilterChain(new String[]{"/login"}, filters) + )); return new DelegatingFilterProxy(filterChainProxy); } diff --git a/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java b/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java index 441078a..366f777 100644 --- a/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java +++ b/src/main/java/nextstep/security/core/DefaultSecurityFilterChain.java @@ -4,15 +4,17 @@ import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; import java.util.List; @RequiredArgsConstructor public class DefaultSecurityFilterChain implements SecurityFilterChain{ + private final String[] paths; private final List filters; @Override public boolean matches(HttpServletRequest request) { - return true; + return Arrays.stream(paths).anyMatch(path -> request.getPathInfo().matches(path)); } @Override diff --git a/src/main/java/nextstep/security/core/FilterChainProxy.java b/src/main/java/nextstep/security/core/FilterChainProxy.java index ae2eafc..224ed10 100644 --- a/src/main/java/nextstep/security/core/FilterChainProxy.java +++ b/src/main/java/nextstep/security/core/FilterChainProxy.java @@ -26,10 +26,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha private List getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : this.filterChains) { if (chain.matches(request)) { + logger.info("SecurityChainMatched"+chain.getClass().getName()); return chain.getFilters(); } } - return null; + return List.of(); } private static final class VirtualFilterChain implements FilterChain { diff --git a/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java b/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java index 40c5a35..41e90de 100644 --- a/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java +++ b/src/main/java/nextstep/security/core/authentication/provider/BasicTokenAuthenticationToken.java @@ -3,6 +3,9 @@ import nextstep.security.exception.AuthErrorCodes; import nextstep.security.exception.AuthenticationException; +import java.util.Arrays; +import java.util.Base64; + public class BasicTokenAuthenticationToken extends UsernamePasswordAuthenticationToken{ @@ -10,10 +13,13 @@ public BasicTokenAuthenticationToken(String basic){ super(null, null); try{ if(basic.startsWith("Basic ")){ - this.setUsername(basic.replace("Basic ", "").split(":")[0]); - this.setPassword(basic.replace("Basic ", "").split(":")[1]); + String base64 = basic.replace("Basic ", ""); + String original = new String(Base64.getDecoder().decode(base64)); + this.setUsername(original.split(":")[0]); + this.setPassword(original.split(":")[1]); } }catch (Exception e){ + e.printStackTrace(); throw new AuthenticationException(AuthErrorCodes.WRONG_BASIC_TOKEN_FORMAT); } diff --git a/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java index 946c89b..ac4a60e 100644 --- a/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/BasicTokenAuthenticationFilter.java @@ -33,15 +33,17 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha authentication = authenticationManager.getAuthenticationProvider(PRINCIPAL).authenticate(authentication); if (authentication.isAuthenticated()) { chain.doFilter(request, response); - return; + }else{ + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpServletResponse.flushBuffer(); } }catch (Exception e){ - + e.printStackTrace(); + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpServletResponse.flushBuffer(); } - - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - httpServletResponse.flushBuffer(); } @Override