From 27e8b1e809aecd8187e6c78fa08ce967624bdfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:14:14 +0900 Subject: [PATCH 01/16] =?UTF-8?q?docs:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=EC=99=80=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1e7ba65..6bdf82e 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ # spring-security-authentication +### 아이디와 비밀번호 기반 로그인 구현 + - 아이디와 비밀번호 확인 기능 + - session 에 인증정보 저장 \ No newline at end of file From f2decaa759c96791596d3000d43b0531ec045d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:15:27 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=EC=99=80=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/app/ui/LoginController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 0ea94f1..6982dd0 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,5 +1,6 @@ package nextstep.app.ui; +import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -22,6 +23,14 @@ public LoginController(MemberRepository memberRepository) { @PostMapping("/login") public ResponseEntity login(HttpServletRequest request, HttpSession session) { + String userName = request.getParameter("username"); + String passWord = request.getParameter("password"); + + Member member = memberRepository.findByEmail(userName).orElseThrow(AuthenticationException::new); + if (!member.getPassword().equals(passWord)) { + throw new AuthenticationException(); + } + return ResponseEntity.ok().build(); } From 8dda0fd2127eac13ca8cf8d4dd3be13b922b1c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:20:37 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20session=20=EC=97=90=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/app/ui/LoginController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 6982dd0..4043e23 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -31,6 +31,8 @@ public ResponseEntity login(HttpServletRequest request, HttpSession sessio throw new AuthenticationException(); } + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member); + return ResponseEntity.ok().build(); } From 56accb036265f0fff0cc537fa10659ce02237dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:50:26 +0900 Subject: [PATCH 04/16] =?UTF-8?q?docs:=20Basic=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=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 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bdf82e..38b79b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # spring-security-authentication ### 아이디와 비밀번호 기반 로그인 구현 - 아이디와 비밀번호 확인 기능 - - session 에 인증정보 저장 \ No newline at end of file + - session 에 인증정보 저장 + +### Basic 인증 구현 + - Basic 유저 정보 추출 + - 인증 기능 + - session 에 인증정보 저장 \ No newline at end of file From fc19cae06902ef0882d94fd37d5af317c21b7aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:52:42 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20Basic=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/ui/MemberController.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index c8cc74d..1a1154f 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -3,9 +3,12 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; import org.springframework.http.ResponseEntity; +import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; - +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.nio.charset.StandardCharsets; import java.util.List; @RestController @@ -18,9 +21,24 @@ public MemberController(MemberRepository memberRepository) { } @GetMapping("/members") - public ResponseEntity> list() { + public ResponseEntity> list(HttpServletRequest request, HttpSession session) { List members = memberRepository.findAll(); + + String basicToken = request.getHeader("Authorization"); + + authenticate(basicToken); + return ResponseEntity.ok(members); } + private void authenticate(String token) { + String payload = token.split(" ")[1]; + + String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); + String[] memberInfo = decodedPayload.split(":"); + + if (memberInfo.length != 2) { + throw new AuthenticationException(); + } + } } From 8fa05b397ab66301d915c748eb8d9461bdb6c73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 11:58:24 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20member=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/ui/MemberController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index 1a1154f..33e3a57 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,8 +2,10 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.Base64Utils; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @@ -40,5 +42,19 @@ private void authenticate(String token) { if (memberInfo.length != 2) { throw new AuthenticationException(); } + + isValidMember(memberInfo[0], memberInfo[1]); + } + + private void isValidMember(String email, String password) { + Member member = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); + if (!member.getPassword().equals(password)) { + throw new AuthenticationException(); + } + } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException() { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } From 507014bc1ffa3676fc194d3159a55e1a7e681007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 12:03:02 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20session=20=EC=97=90=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/app/ui/MemberController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index 33e3a57..83afc06 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -15,6 +15,7 @@ @RestController public class MemberController { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; private final MemberRepository memberRepository; @@ -28,12 +29,12 @@ public ResponseEntity> list(HttpServletRequest request, HttpSession String basicToken = request.getHeader("Authorization"); - authenticate(basicToken); + authenticate(basicToken, session); return ResponseEntity.ok(members); } - private void authenticate(String token) { + private void authenticate(String token, HttpSession session) { String payload = token.split(" ")[1]; String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); @@ -43,14 +44,16 @@ private void authenticate(String token) { throw new AuthenticationException(); } - isValidMember(memberInfo[0], memberInfo[1]); + Member validMember = isValidMember(memberInfo[0], memberInfo[1]); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, validMember); } - private void isValidMember(String email, String password) { + private Member isValidMember(String email, String password) { Member member = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); if (!member.getPassword().equals(password)) { throw new AuthenticationException(); } + return member; } @ExceptionHandler(AuthenticationException.class) From 480cca515ab9db2f053ea974fe58cfc5ddbc1b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 12:08:53 +0900 Subject: [PATCH 08/16] =?UTF-8?q?docs:=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EB=B6=84=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38b79b3..997c481 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,8 @@ ### Basic 인증 구현 - Basic 유저 정보 추출 - 인증 기능 - - session 에 인증정보 저장 \ No newline at end of file + - session 에 인증정보 저장 + +### 인터셉터 분리 + - LoginController에서 IdPasswordAuthInterceptor로 인증 방식 분리 + - MemeberController에서 BasicAuthInterceptor로 인증 방식 분리 \ No newline at end of file From be162d74dcec2fdd252907410785f466647901e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 12:21:22 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20LoginController=EC=97=90=EC=84=9C?= =?UTF-8?q?=20IdPasswordAuthInterceptor=EB=A1=9C=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/config/WebConfig.java | 21 ++++++++++++ .../IdPasswordAuthInterceptor.java | 34 +++++++++++++++++++ .../java/nextstep/app/ui/LoginController.java | 22 +----------- 3 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 src/main/java/nextstep/app/config/WebConfig.java create mode 100644 src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.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..cb2906f --- /dev/null +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -0,0 +1,21 @@ +package nextstep.app.config; + +import nextstep.app.interceptor.IdPasswordAuthInterceptor; +import nextstep.app.domain.MemberRepository; +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 MemberRepository memberRepository; + + public WebConfig(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new IdPasswordAuthInterceptor(memberRepository)) + .addPathPatterns("/login"); + } +} diff --git a/src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java b/src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java new file mode 100644 index 0000000..f431143 --- /dev/null +++ b/src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java @@ -0,0 +1,34 @@ +package nextstep.app.interceptor; + +import nextstep.app.domain.Member; +import nextstep.app.domain.MemberRepository; +import nextstep.app.ui.AuthenticationException; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +public class IdPasswordAuthInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private final MemberRepository memberRepository; + + public IdPasswordAuthInterceptor(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String userName = request.getParameter("username"); + String passWord = request.getParameter("password"); + + Member member = memberRepository.findByEmail(userName).orElseThrow(AuthenticationException::new); + if (!member.getPassword().equals(passWord)) { + throw new AuthenticationException(); + } + + HttpSession session = request.getSession(); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member); + return true; + } +} diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 4043e23..f4d054b 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,38 +1,18 @@ package nextstep.app.ui; -import nextstep.app.domain.Member; -import nextstep.app.domain.MemberRepository; 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; -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) { - String userName = request.getParameter("username"); - String passWord = request.getParameter("password"); - - Member member = memberRepository.findByEmail(userName).orElseThrow(AuthenticationException::new); - if (!member.getPassword().equals(passWord)) { - throw new AuthenticationException(); - } - - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member); - + public ResponseEntity login() { return ResponseEntity.ok().build(); } From 029e22b786806b2eac5f61fe2e78e71e5630b4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 12:30:29 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20MemeberController=EC=97=90?= =?UTF-8?q?=EC=84=9C=20BasicAuthInterceptor=EB=A1=9C=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/config/WebConfig.java | 3 + .../app/interceptor/BasicAuthInterceptor.java | 59 +++++++++++++++++++ .../nextstep/app/ui/MemberController.java | 37 +----------- 3 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index cb2906f..deaec90 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,5 +1,6 @@ package nextstep.app.config; +import nextstep.app.interceptor.BasicAuthInterceptor; import nextstep.app.interceptor.IdPasswordAuthInterceptor; import nextstep.app.domain.MemberRepository; import org.springframework.context.annotation.Configuration; @@ -17,5 +18,7 @@ public WebConfig(MemberRepository memberRepository) { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new IdPasswordAuthInterceptor(memberRepository)) .addPathPatterns("/login"); + registry.addInterceptor(new BasicAuthInterceptor(memberRepository)) + .addPathPatterns("/members"); } } diff --git a/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java b/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java new file mode 100644 index 0000000..4a0ca73 --- /dev/null +++ b/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java @@ -0,0 +1,59 @@ +package nextstep.app.interceptor; + +import nextstep.app.domain.Member; +import nextstep.app.domain.MemberRepository; +import nextstep.app.ui.AuthenticationException; +import org.springframework.util.Base64Utils; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.nio.charset.StandardCharsets; + +public class BasicAuthInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private final MemberRepository memberRepository; + + public BasicAuthInterceptor(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String basicToken = request.getHeader("Authorization"); + HttpSession session = request.getSession(); + + try { + authenticate(basicToken, session); + } catch (AuthenticationException e) { + response.setStatus(401); + return false; + } + + return true; + } + + + private void authenticate(String token, HttpSession session) throws AuthenticationException { + String payload = token.split(" ")[1]; + + String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); + String[] memberInfo = decodedPayload.split(":"); + + if (memberInfo.length != 2) { + throw new AuthenticationException(); + } + + Member validMember = isValidMember(memberInfo[0], memberInfo[1]); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, validMember); + } + + private Member isValidMember(String email, String password) { + Member member = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); + if (!member.getPassword().equals(password)) { + throw new AuthenticationException(); + } + return member; + } +} diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index 83afc06..8d771ce 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,20 +2,16 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.util.Base64Utils; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import java.nio.charset.StandardCharsets; import java.util.List; @RestController public class MemberController { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private final MemberRepository memberRepository; @@ -27,37 +23,6 @@ public MemberController(MemberRepository memberRepository) { public ResponseEntity> list(HttpServletRequest request, HttpSession session) { List members = memberRepository.findAll(); - String basicToken = request.getHeader("Authorization"); - - authenticate(basicToken, session); - return ResponseEntity.ok(members); } - - private void authenticate(String token, HttpSession session) { - String payload = token.split(" ")[1]; - - String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); - String[] memberInfo = decodedPayload.split(":"); - - if (memberInfo.length != 2) { - throw new AuthenticationException(); - } - - Member validMember = isValidMember(memberInfo[0], memberInfo[1]); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, validMember); - } - - private Member isValidMember(String email, String password) { - Member member = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); - if (!member.getPassword().equals(password)) { - throw new AuthenticationException(); - } - return member; - } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException() { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } } From 608228d5dbc7dc866f0f72ca58483fe2b59df239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sat, 2 Nov 2024 12:32:23 +0900 Subject: [PATCH 11/16] =?UTF-8?q?docs:=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EA=B3=BC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=84=EC=9D=98=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94=EA=B5=AC=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 997c481..ad879a8 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,7 @@ ### 인터셉터 분리 - LoginController에서 IdPasswordAuthInterceptor로 인증 방식 분리 - - MemeberController에서 BasicAuthInterceptor로 인증 방식 분리 \ No newline at end of file + - MemeberController에서 BasicAuthInterceptor로 인증 방식 분리 + +### 인증 로직과 서비스 로직 간의 패키지 분리 + - 패키지 분리 및 리팩토링 \ No newline at end of file From f688bd819bd2998813b1836a0186cd64f1099d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Sun, 3 Nov 2024 19:26:08 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/app/config/WebConfig.java | 16 ++--- src/main/java/nextstep/app/domain/Member.java | 4 +- .../app/interceptor/BasicAuthInterceptor.java | 59 ------------------- .../nextstep/app/service/MemberService.java | 24 ++++++++ .../interceptor/BasicAuthInterceptor.java | 52 ++++++++++++++++ .../IdPasswordAuthInterceptor.java | 20 +++---- .../nextstep/security/model/UserDetail.java | 6 ++ .../security/service/UserDetailService.java | 8 +++ 8 files changed, 109 insertions(+), 80 deletions(-) delete mode 100644 src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java create mode 100644 src/main/java/nextstep/app/service/MemberService.java create mode 100644 src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java rename src/main/java/nextstep/{app => security}/interceptor/IdPasswordAuthInterceptor.java (52%) create mode 100644 src/main/java/nextstep/security/model/UserDetail.java create mode 100644 src/main/java/nextstep/security/service/UserDetailService.java diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index deaec90..2995dff 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,24 +1,24 @@ package nextstep.app.config; -import nextstep.app.interceptor.BasicAuthInterceptor; -import nextstep.app.interceptor.IdPasswordAuthInterceptor; -import nextstep.app.domain.MemberRepository; +import nextstep.app.service.MemberService; +import nextstep.security.interceptor.BasicAuthInterceptor; +import nextstep.security.interceptor.IdPasswordAuthInterceptor; 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 MemberRepository memberRepository; + private final MemberService memberService; - public WebConfig(MemberRepository memberRepository) { - this.memberRepository = memberRepository; + public WebConfig(MemberService memberService) { + this.memberService = memberService; } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new IdPasswordAuthInterceptor(memberRepository)) + registry.addInterceptor(new IdPasswordAuthInterceptor(memberService)) .addPathPatterns("/login"); - registry.addInterceptor(new BasicAuthInterceptor(memberRepository)) + registry.addInterceptor(new BasicAuthInterceptor(memberService)) .addPathPatterns("/members"); } } diff --git a/src/main/java/nextstep/app/domain/Member.java b/src/main/java/nextstep/app/domain/Member.java index 6cafa9c..20c7f7f 100644 --- a/src/main/java/nextstep/app/domain/Member.java +++ b/src/main/java/nextstep/app/domain/Member.java @@ -1,6 +1,8 @@ package nextstep.app.domain; -public class Member { +import nextstep.security.model.UserDetail; + +public class Member implements UserDetail{ private final String email; private final String password; private final String name; diff --git a/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java b/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java deleted file mode 100644 index 4a0ca73..0000000 --- a/src/main/java/nextstep/app/interceptor/BasicAuthInterceptor.java +++ /dev/null @@ -1,59 +0,0 @@ -package nextstep.app.interceptor; - -import nextstep.app.domain.Member; -import nextstep.app.domain.MemberRepository; -import nextstep.app.ui.AuthenticationException; -import org.springframework.util.Base64Utils; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.nio.charset.StandardCharsets; - -public class BasicAuthInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final MemberRepository memberRepository; - - public BasicAuthInterceptor(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String basicToken = request.getHeader("Authorization"); - HttpSession session = request.getSession(); - - try { - authenticate(basicToken, session); - } catch (AuthenticationException e) { - response.setStatus(401); - return false; - } - - return true; - } - - - private void authenticate(String token, HttpSession session) throws AuthenticationException { - String payload = token.split(" ")[1]; - - String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); - String[] memberInfo = decodedPayload.split(":"); - - if (memberInfo.length != 2) { - throw new AuthenticationException(); - } - - Member validMember = isValidMember(memberInfo[0], memberInfo[1]); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, validMember); - } - - private Member isValidMember(String email, String password) { - Member member = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); - if (!member.getPassword().equals(password)) { - throw new AuthenticationException(); - } - return member; - } -} diff --git a/src/main/java/nextstep/app/service/MemberService.java b/src/main/java/nextstep/app/service/MemberService.java new file mode 100644 index 0000000..4e2dcfa --- /dev/null +++ b/src/main/java/nextstep/app/service/MemberService.java @@ -0,0 +1,24 @@ +package nextstep.app.service; + +import nextstep.app.domain.MemberRepository; +import nextstep.app.ui.AuthenticationException; +import nextstep.security.model.UserDetail; +import nextstep.security.service.UserDetailService; +import org.springframework.stereotype.Service; + +@Service +public class MemberService implements UserDetailService { + private final MemberRepository memberRepository; + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public UserDetail isValidUser(String email, String password) { + UserDetail userDetail = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); + if (!userDetail.getPassword().equals(password)) { + throw new AuthenticationException(); + } + return userDetail; + } +} diff --git a/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java new file mode 100644 index 0000000..aecaf7d --- /dev/null +++ b/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java @@ -0,0 +1,52 @@ +package nextstep.security.interceptor; + +import nextstep.security.model.UserDetail; +import nextstep.security.service.UserDetailService; +import org.springframework.http.HttpStatus; +import org.springframework.util.Base64Utils; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.nio.charset.StandardCharsets; + +public class BasicAuthInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private final UserDetailService userDetailService; + + public BasicAuthInterceptor(UserDetailService userDetailService) { + this.userDetailService = userDetailService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String basicToken = request.getHeader("Authorization"); + HttpSession session = request.getSession(); + + if (!isAuthentication(basicToken, session)) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + return false; + } + + return true; + } + + + private boolean isAuthentication(String token, HttpSession session) { + String payload = token.split(" ")[1]; + + String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); + String[] memberInfo = decodedPayload.split(":"); + + if (memberInfo.length != 2) { + return false; + } + + UserDetail userDetail = userDetailService.isValidUser(memberInfo[0], memberInfo[1]); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); + return true; + } + + +} diff --git a/src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java b/src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java similarity index 52% rename from src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java rename to src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java index f431143..2765d11 100644 --- a/src/main/java/nextstep/app/interceptor/IdPasswordAuthInterceptor.java +++ b/src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java @@ -1,8 +1,7 @@ -package nextstep.app.interceptor; +package nextstep.security.interceptor; -import nextstep.app.domain.Member; -import nextstep.app.domain.MemberRepository; -import nextstep.app.ui.AuthenticationException; +import nextstep.security.model.UserDetail; +import nextstep.security.service.UserDetailService; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; @@ -11,10 +10,10 @@ public class IdPasswordAuthInterceptor implements HandlerInterceptor { public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final MemberRepository memberRepository; + private final UserDetailService userDetailService; - public IdPasswordAuthInterceptor(MemberRepository memberRepository) { - this.memberRepository = memberRepository; + public IdPasswordAuthInterceptor(UserDetailService userDetailService) { + this.userDetailService = userDetailService; } @Override @@ -22,13 +21,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons String userName = request.getParameter("username"); String passWord = request.getParameter("password"); - Member member = memberRepository.findByEmail(userName).orElseThrow(AuthenticationException::new); - if (!member.getPassword().equals(passWord)) { - throw new AuthenticationException(); - } + UserDetail userDetail = userDetailService.isValidUser(userName, passWord); HttpSession session = request.getSession(); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); return true; } } diff --git a/src/main/java/nextstep/security/model/UserDetail.java b/src/main/java/nextstep/security/model/UserDetail.java new file mode 100644 index 0000000..54cc283 --- /dev/null +++ b/src/main/java/nextstep/security/model/UserDetail.java @@ -0,0 +1,6 @@ +package nextstep.security.model; + +public interface UserDetail { + String getEmail(); + String getPassword(); +} diff --git a/src/main/java/nextstep/security/service/UserDetailService.java b/src/main/java/nextstep/security/service/UserDetailService.java new file mode 100644 index 0000000..6af9da5 --- /dev/null +++ b/src/main/java/nextstep/security/service/UserDetailService.java @@ -0,0 +1,8 @@ +package nextstep.security.service; + +import nextstep.security.model.UserDetail; + + +public interface UserDetailService { + UserDetail isValidUser(String email, String password); +} From fbe5133140468f8e518e76e6c270aa5ce6c68937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Mon, 4 Nov 2024 15:21:17 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat:=20SecurityFilterChain=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/config/SecurityConfig.java | 26 +++++++++ .../java/nextstep/app/config/WebConfig.java | 2 + .../security/DefaultSecurityFilterChain.java | 23 ++++++++ .../security/DelegatingFilterProxy.java | 19 +++++++ .../nextstep/security/FilterChainProxy.java | 54 +++++++++++++++++++ .../security/SecurityFilterChain.java | 10 ++++ .../interceptor/BasicAuthInterceptor.java | 4 +- 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/main/java/nextstep/app/config/SecurityConfig.java create mode 100644 src/main/java/nextstep/security/DefaultSecurityFilterChain.java create mode 100644 src/main/java/nextstep/security/DelegatingFilterProxy.java create mode 100644 src/main/java/nextstep/security/FilterChainProxy.java create mode 100644 src/main/java/nextstep/security/SecurityFilterChain.java diff --git a/src/main/java/nextstep/app/config/SecurityConfig.java b/src/main/java/nextstep/app/config/SecurityConfig.java new file mode 100644 index 0000000..0b69bd5 --- /dev/null +++ b/src/main/java/nextstep/app/config/SecurityConfig.java @@ -0,0 +1,26 @@ +package nextstep.app.config; + +import nextstep.security.DefaultSecurityFilterChain; +import nextstep.security.DelegatingFilterProxy; +import nextstep.security.FilterChainProxy; +import nextstep.security.SecurityFilterChain; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +public class SecurityConfig { + + @Bean + public DelegatingFilterProxy delegatingFilterProxy() { + return new DelegatingFilterProxy(filterChainProxy(List.of(securityFilterChain()))); + } + @Bean + public FilterChainProxy filterChainProxy(List securityFilterChains) { + return new FilterChainProxy(securityFilterChains); + } + + @Bean + public SecurityFilterChain securityFilterChain() { + return new DefaultSecurityFilterChain(List.of()); + } +} diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index 2995dff..b968a9f 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -21,4 +21,6 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new BasicAuthInterceptor(memberService)) .addPathPatterns("/members"); } + + } diff --git a/src/main/java/nextstep/security/DefaultSecurityFilterChain.java b/src/main/java/nextstep/security/DefaultSecurityFilterChain.java new file mode 100644 index 0000000..c44660e --- /dev/null +++ b/src/main/java/nextstep/security/DefaultSecurityFilterChain.java @@ -0,0 +1,23 @@ +package nextstep.security; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +public class DefaultSecurityFilterChain implements SecurityFilterChain{ + private final List filters; + + public DefaultSecurityFilterChain(List filters) { + this.filters = filters; + } + + @Override + public boolean matches(HttpServletRequest request) { + return true; + } + + @Override + public List getFilters() { + return List.of(); + } +} diff --git a/src/main/java/nextstep/security/DelegatingFilterProxy.java b/src/main/java/nextstep/security/DelegatingFilterProxy.java new file mode 100644 index 0000000..b5ebaf5 --- /dev/null +++ b/src/main/java/nextstep/security/DelegatingFilterProxy.java @@ -0,0 +1,19 @@ +package nextstep.security; + +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.*; +import java.io.IOException; + +public class DelegatingFilterProxy extends GenericFilterBean { + private final Filter delegate; + + public DelegatingFilterProxy(Filter delegate) { + this.delegate = delegate; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + delegate.doFilter(servletRequest, servletResponse, filterChain); + } +} diff --git a/src/main/java/nextstep/security/FilterChainProxy.java b/src/main/java/nextstep/security/FilterChainProxy.java new file mode 100644 index 0000000..e15d855 --- /dev/null +++ b/src/main/java/nextstep/security/FilterChainProxy.java @@ -0,0 +1,54 @@ +package nextstep.security; + +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 List filterChains; + + public FilterChainProxy(List filterChains) { + this.filterChains = filterChains; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + List filters = getFilters((HttpServletRequest) servletRequest); + VirtualFilterChain virtualFilterChain = new VirtualFilterChain(filterChain, filters); + virtualFilterChain.doFilter(servletRequest, servletResponse); + } + + private List getFilters(HttpServletRequest request) { + for (SecurityFilterChain securityFilterChain : filterChains) { + if (securityFilterChain.matches(request)) { + return securityFilterChain.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); + } + } +} diff --git a/src/main/java/nextstep/security/SecurityFilterChain.java b/src/main/java/nextstep/security/SecurityFilterChain.java new file mode 100644 index 0000000..6fa9aef --- /dev/null +++ b/src/main/java/nextstep/security/SecurityFilterChain.java @@ -0,0 +1,10 @@ +package nextstep.security; + +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/interceptor/BasicAuthInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java index aecaf7d..b8f068d 100644 --- a/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java +++ b/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java @@ -47,6 +47,4 @@ private boolean isAuthentication(String token, HttpSession session) { session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); return true; } - - -} +} \ No newline at end of file From e055d6d2bcdf73ee571861bfdde4850b2ac9f916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Mon, 4 Nov 2024 15:53:42 +0900 Subject: [PATCH 14/16] =?UTF-8?q?feat:=20SecurityFilterChain=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9-2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/config/SecurityConfig.java | 39 ++++++++++++++- .../java/nextstep/app/config/WebConfig.java | 26 ---------- .../InmemoryMemberRepository.java | 6 +-- .../nextstep/app/service/MemberService.java | 24 --------- .../app/ui/AuthenticationException.java | 7 +++ .../security/DefaultSecurityFilterChain.java | 2 +- .../filter/BasicAuthenticationFilter.java | 31 ++++++++++++ .../UsernamePasswordAuthenticationFilter.java | 46 +++++++++++++++++ .../interceptor/BasicAuthInterceptor.java | 50 ------------------- .../IdPasswordAuthInterceptor.java | 30 ----------- .../nextstep/security/model/UserDetail.java | 2 +- .../security/service/UserDetailService.java | 3 +- 12 files changed, 129 insertions(+), 137 deletions(-) delete mode 100644 src/main/java/nextstep/app/config/WebConfig.java delete mode 100644 src/main/java/nextstep/app/service/MemberService.java create mode 100644 src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java create mode 100644 src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java delete mode 100644 src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java delete mode 100644 src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java diff --git a/src/main/java/nextstep/app/config/SecurityConfig.java b/src/main/java/nextstep/app/config/SecurityConfig.java index 0b69bd5..a72dd20 100644 --- a/src/main/java/nextstep/app/config/SecurityConfig.java +++ b/src/main/java/nextstep/app/config/SecurityConfig.java @@ -1,14 +1,29 @@ package nextstep.app.config; +import nextstep.app.domain.Member; +import nextstep.app.domain.MemberRepository; +import nextstep.app.ui.AuthenticationException; import nextstep.security.DefaultSecurityFilterChain; import nextstep.security.DelegatingFilterProxy; import nextstep.security.FilterChainProxy; import nextstep.security.SecurityFilterChain; +import nextstep.security.filter.BasicAuthenticationFilter; +import nextstep.security.filter.UsernamePasswordAuthenticationFilter; +import nextstep.security.model.UserDetail; +import nextstep.security.service.UserDetailService; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import java.util.List; +@Configuration public class SecurityConfig { + private final MemberRepository memberRepository; + + public SecurityConfig(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + @Bean public DelegatingFilterProxy delegatingFilterProxy() { @@ -21,6 +36,28 @@ public FilterChainProxy filterChainProxy(List securityFilte @Bean public SecurityFilterChain securityFilterChain() { - return new DefaultSecurityFilterChain(List.of()); + return new DefaultSecurityFilterChain(List.of( + new BasicAuthenticationFilter(userDetailService()), + new UsernamePasswordAuthenticationFilter(userDetailService()) + )); + } + @Bean + public UserDetailService userDetailService() { + return username -> { + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new AuthenticationException("존재하지 않는 사용자입니다.")); + + return new UserDetail() { + @Override + public String getUserName() { + return member.getUserName(); + } + + @Override + public String getPassword() { + return member.getPassword(); + } + }; + }; } } diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java deleted file mode 100644 index b968a9f..0000000 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package nextstep.app.config; - -import nextstep.app.service.MemberService; -import nextstep.security.interceptor.BasicAuthInterceptor; -import nextstep.security.interceptor.IdPasswordAuthInterceptor; -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 IdPasswordAuthInterceptor(memberService)) - .addPathPatterns("/login"); - registry.addInterceptor(new BasicAuthInterceptor(memberService)) - .addPathPatterns("/members"); - } - - -} diff --git a/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java b/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java index 5a6062c..1b06293 100644 --- a/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java +++ b/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java @@ -17,8 +17,8 @@ public class InmemoryMemberRepository implements MemberRepository { private static final Map members = new HashMap<>(); static { - members.put(TEST_MEMBER_1.getEmail(), TEST_MEMBER_1); - members.put(TEST_MEMBER_2.getEmail(), TEST_MEMBER_2); + members.put(TEST_MEMBER_1.getUserName(), TEST_MEMBER_1); + members.put(TEST_MEMBER_2.getUserName(), TEST_MEMBER_2); } @Override @@ -33,6 +33,6 @@ public List findAll() { @Override public void save(Member member) { - members.put(member.getEmail(), member); + members.put(member.getUserName(), member); } } diff --git a/src/main/java/nextstep/app/service/MemberService.java b/src/main/java/nextstep/app/service/MemberService.java deleted file mode 100644 index 4e2dcfa..0000000 --- a/src/main/java/nextstep/app/service/MemberService.java +++ /dev/null @@ -1,24 +0,0 @@ -package nextstep.app.service; - -import nextstep.app.domain.MemberRepository; -import nextstep.app.ui.AuthenticationException; -import nextstep.security.model.UserDetail; -import nextstep.security.service.UserDetailService; -import org.springframework.stereotype.Service; - -@Service -public class MemberService implements UserDetailService { - private final MemberRepository memberRepository; - - public MemberService(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - public UserDetail isValidUser(String email, String password) { - UserDetail userDetail = memberRepository.findByEmail(email).orElseThrow(AuthenticationException::new); - if (!userDetail.getPassword().equals(password)) { - throw new AuthenticationException(); - } - return userDetail; - } -} diff --git a/src/main/java/nextstep/app/ui/AuthenticationException.java b/src/main/java/nextstep/app/ui/AuthenticationException.java index f809b6e..95a3112 100644 --- a/src/main/java/nextstep/app/ui/AuthenticationException.java +++ b/src/main/java/nextstep/app/ui/AuthenticationException.java @@ -1,4 +1,11 @@ package nextstep.app.ui; public class AuthenticationException extends RuntimeException { + public AuthenticationException() { + super("인증에 실패하셨습니다."); + } + + public AuthenticationException(String message) { + super(message); + } } diff --git a/src/main/java/nextstep/security/DefaultSecurityFilterChain.java b/src/main/java/nextstep/security/DefaultSecurityFilterChain.java index c44660e..2b396a4 100644 --- a/src/main/java/nextstep/security/DefaultSecurityFilterChain.java +++ b/src/main/java/nextstep/security/DefaultSecurityFilterChain.java @@ -18,6 +18,6 @@ public boolean matches(HttpServletRequest request) { @Override public List getFilters() { - return List.of(); + return filters; } } diff --git a/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java new file mode 100644 index 0000000..af57aac --- /dev/null +++ b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java @@ -0,0 +1,31 @@ +package nextstep.security.filter; + +import nextstep.app.ui.AuthenticationException; +import nextstep.security.service.UserDetailService; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class BasicAuthenticationFilter extends OncePerRequestFilter { + private final UserDetailService userDetailsService; + + public BasicAuthenticationFilter(UserDetailService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + checkAuthentication(request); + filterChain.doFilter(request, response); + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + + private void checkAuthentication(HttpServletRequest request) { + throw new AuthenticationException(); + } +} 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..fe28714 --- /dev/null +++ b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java @@ -0,0 +1,46 @@ +package nextstep.security.filter; + +import nextstep.app.ui.AuthenticationException; +import nextstep.security.model.UserDetail; +import nextstep.security.service.UserDetailService; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +public class UsernamePasswordAuthenticationFilter extends GenericFilterBean { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private static final String DEFAULT_REQUEST_URI = "/login"; + private final UserDetailService userDetailsService; + public UsernamePasswordAuthenticationFilter(UserDetailService userDetailService) { + this.userDetailsService = userDetailService; + } + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (!DEFAULT_REQUEST_URI.equals(((HttpServletRequest) request).getRequestURI())) { + chain.doFilter(request, response); + return; + } + try { + Map parameterMap = request.getParameterMap(); + String username = parameterMap.get("username")[0]; + String password = parameterMap.get("password")[0]; + UserDetail userDetails = userDetailsService.loadUserByUsername(username); + if (!Objects.equals(userDetails.getPassword(), password)) { + throw new AuthenticationException(); + } + HttpSession session = ((HttpServletRequest) request).getSession(); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetails); + } catch ( + AuthenticationException e) { + ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java deleted file mode 100644 index b8f068d..0000000 --- a/src/main/java/nextstep/security/interceptor/BasicAuthInterceptor.java +++ /dev/null @@ -1,50 +0,0 @@ -package nextstep.security.interceptor; - -import nextstep.security.model.UserDetail; -import nextstep.security.service.UserDetailService; -import org.springframework.http.HttpStatus; -import org.springframework.util.Base64Utils; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.nio.charset.StandardCharsets; - -public class BasicAuthInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final UserDetailService userDetailService; - - public BasicAuthInterceptor(UserDetailService userDetailService) { - this.userDetailService = userDetailService; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String basicToken = request.getHeader("Authorization"); - HttpSession session = request.getSession(); - - if (!isAuthentication(basicToken, session)) { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - return false; - } - - return true; - } - - - private boolean isAuthentication(String token, HttpSession session) { - String payload = token.split(" ")[1]; - - String decodedPayload = new String(Base64Utils.decodeFromString(payload), StandardCharsets.UTF_8); - String[] memberInfo = decodedPayload.split(":"); - - if (memberInfo.length != 2) { - return false; - } - - UserDetail userDetail = userDetailService.isValidUser(memberInfo[0], memberInfo[1]); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); - return true; - } -} \ No newline at end of file diff --git a/src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java b/src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java deleted file mode 100644 index 2765d11..0000000 --- a/src/main/java/nextstep/security/interceptor/IdPasswordAuthInterceptor.java +++ /dev/null @@ -1,30 +0,0 @@ -package nextstep.security.interceptor; - -import nextstep.security.model.UserDetail; -import nextstep.security.service.UserDetailService; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -public class IdPasswordAuthInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final UserDetailService userDetailService; - - public IdPasswordAuthInterceptor(UserDetailService userDetailService) { - this.userDetailService = userDetailService; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String userName = request.getParameter("username"); - String passWord = request.getParameter("password"); - - UserDetail userDetail = userDetailService.isValidUser(userName, passWord); - - HttpSession session = request.getSession(); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetail); - return true; - } -} diff --git a/src/main/java/nextstep/security/model/UserDetail.java b/src/main/java/nextstep/security/model/UserDetail.java index 54cc283..0e95db2 100644 --- a/src/main/java/nextstep/security/model/UserDetail.java +++ b/src/main/java/nextstep/security/model/UserDetail.java @@ -1,6 +1,6 @@ package nextstep.security.model; public interface UserDetail { - String getEmail(); + String getUserName(); String getPassword(); } diff --git a/src/main/java/nextstep/security/service/UserDetailService.java b/src/main/java/nextstep/security/service/UserDetailService.java index 6af9da5..7d6b35e 100644 --- a/src/main/java/nextstep/security/service/UserDetailService.java +++ b/src/main/java/nextstep/security/service/UserDetailService.java @@ -4,5 +4,6 @@ public interface UserDetailService { - UserDetail isValidUser(String email, String password); + UserDetail loadUserByUsername(String username); + } From efdc5856f916e536a0a3491f65e64e59fac7e002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Tue, 5 Nov 2024 09:37:20 +0900 Subject: [PATCH 15/16] =?UTF-8?q?refactor:=20member=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/app/config/SecurityConfig.java | 2 +- src/main/java/nextstep/app/domain/Member.java | 3 +-- .../app/infrastructure/InmemoryMemberRepository.java | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/nextstep/app/config/SecurityConfig.java b/src/main/java/nextstep/app/config/SecurityConfig.java index a72dd20..7b5169f 100644 --- a/src/main/java/nextstep/app/config/SecurityConfig.java +++ b/src/main/java/nextstep/app/config/SecurityConfig.java @@ -50,7 +50,7 @@ public UserDetailService userDetailService() { return new UserDetail() { @Override public String getUserName() { - return member.getUserName(); + return member.getEmail(); } @Override diff --git a/src/main/java/nextstep/app/domain/Member.java b/src/main/java/nextstep/app/domain/Member.java index 20c7f7f..5fe125e 100644 --- a/src/main/java/nextstep/app/domain/Member.java +++ b/src/main/java/nextstep/app/domain/Member.java @@ -1,8 +1,7 @@ package nextstep.app.domain; -import nextstep.security.model.UserDetail; -public class Member implements UserDetail{ +public class Member{ private final String email; private final String password; private final String name; diff --git a/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java b/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java index 1b06293..5a6062c 100644 --- a/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java +++ b/src/main/java/nextstep/app/infrastructure/InmemoryMemberRepository.java @@ -17,8 +17,8 @@ public class InmemoryMemberRepository implements MemberRepository { private static final Map members = new HashMap<>(); static { - members.put(TEST_MEMBER_1.getUserName(), TEST_MEMBER_1); - members.put(TEST_MEMBER_2.getUserName(), TEST_MEMBER_2); + members.put(TEST_MEMBER_1.getEmail(), TEST_MEMBER_1); + members.put(TEST_MEMBER_2.getEmail(), TEST_MEMBER_2); } @Override @@ -33,6 +33,6 @@ public List findAll() { @Override public void save(Member member) { - members.put(member.getUserName(), member); + members.put(member.getEmail(), member); } } From cc980c23c0b98a5a79edb9218d2b02d62a89183c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9B=90=EC=A2=85/=EC=9B=B9=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=84=9C=EB=B2=84=EA=B0=9C=EB=B0=9CCell/Kia?= Date: Tue, 5 Nov 2024 10:16:22 +0900 Subject: [PATCH 16/16] =?UTF-8?q?feat:=20AuthenticationManager=EB=A5=BC=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=9C=20=EC=9D=B8=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/config/SecurityConfig.java | 10 ++-- .../authentication/Authentication.java | 7 +++ .../AuthenticationException.java | 11 ++++ .../authentication/AuthenticationManager.java | 5 ++ .../AuthenticationProvider.java | 9 +++ .../DaoAuthenticationProvider.java | 29 +++++++++ .../authentication/ProviderManager.java | 21 +++++++ .../UsernamePasswordAuthenticationToken.java | 38 ++++++++++++ .../filter/BasicAuthenticationFilter.java | 59 ++++++++++++++++--- .../UsernamePasswordAuthenticationFilter.java | 49 ++++++++++----- .../{UserDetail.java => UserDetails.java} | 4 +- .../security/service/UserDetailService.java | 9 --- .../security/service/UserDetailsService.java | 9 +++ 13 files changed, 223 insertions(+), 37 deletions(-) create mode 100644 src/main/java/nextstep/security/authentication/Authentication.java create mode 100644 src/main/java/nextstep/security/authentication/AuthenticationException.java create mode 100644 src/main/java/nextstep/security/authentication/AuthenticationManager.java create mode 100644 src/main/java/nextstep/security/authentication/AuthenticationProvider.java create mode 100644 src/main/java/nextstep/security/authentication/DaoAuthenticationProvider.java create mode 100644 src/main/java/nextstep/security/authentication/ProviderManager.java create mode 100644 src/main/java/nextstep/security/authentication/UsernamePasswordAuthenticationToken.java rename src/main/java/nextstep/security/model/{UserDetail.java => UserDetails.java} (52%) delete mode 100644 src/main/java/nextstep/security/service/UserDetailService.java create mode 100644 src/main/java/nextstep/security/service/UserDetailsService.java diff --git a/src/main/java/nextstep/app/config/SecurityConfig.java b/src/main/java/nextstep/app/config/SecurityConfig.java index 7b5169f..08d2377 100644 --- a/src/main/java/nextstep/app/config/SecurityConfig.java +++ b/src/main/java/nextstep/app/config/SecurityConfig.java @@ -9,8 +9,8 @@ import nextstep.security.SecurityFilterChain; import nextstep.security.filter.BasicAuthenticationFilter; import nextstep.security.filter.UsernamePasswordAuthenticationFilter; -import nextstep.security.model.UserDetail; -import nextstep.security.service.UserDetailService; +import nextstep.security.model.UserDetails; +import nextstep.security.service.UserDetailsService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -42,14 +42,14 @@ public SecurityFilterChain securityFilterChain() { )); } @Bean - public UserDetailService userDetailService() { + public UserDetailsService userDetailService() { return username -> { Member member = memberRepository.findByEmail(username) .orElseThrow(() -> new AuthenticationException("존재하지 않는 사용자입니다.")); - return new UserDetail() { + return new UserDetails() { @Override - public String getUserName() { + public String getUsername() { return member.getEmail(); } diff --git a/src/main/java/nextstep/security/authentication/Authentication.java b/src/main/java/nextstep/security/authentication/Authentication.java new file mode 100644 index 0000000..516a5e9 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/Authentication.java @@ -0,0 +1,7 @@ +package nextstep.security.authentication; + +public interface Authentication { + Object getCredentials(); // token password + Object getPrincipal(); /// token userName + boolean isAuthenticated(); +} diff --git a/src/main/java/nextstep/security/authentication/AuthenticationException.java b/src/main/java/nextstep/security/authentication/AuthenticationException.java new file mode 100644 index 0000000..f2da636 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/AuthenticationException.java @@ -0,0 +1,11 @@ +package nextstep.security.authentication; + +public class AuthenticationException extends RuntimeException { + public AuthenticationException() { + super("인증에 실패하였습니다."); + } + + public AuthenticationException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/security/authentication/AuthenticationManager.java b/src/main/java/nextstep/security/authentication/AuthenticationManager.java new file mode 100644 index 0000000..ac74b1e --- /dev/null +++ b/src/main/java/nextstep/security/authentication/AuthenticationManager.java @@ -0,0 +1,5 @@ +package nextstep.security.authentication; + +public interface AuthenticationManager { + Authentication authenticate(Authentication authentication); +} diff --git a/src/main/java/nextstep/security/authentication/AuthenticationProvider.java b/src/main/java/nextstep/security/authentication/AuthenticationProvider.java new file mode 100644 index 0000000..b75b7fd --- /dev/null +++ b/src/main/java/nextstep/security/authentication/AuthenticationProvider.java @@ -0,0 +1,9 @@ +package nextstep.security.authentication; + +import nextstep.app.ui.AuthenticationException; + +public interface AuthenticationProvider { + Authentication authenticate(Authentication authentication) throws AuthenticationException; + + boolean supports(Class authentication); +} diff --git a/src/main/java/nextstep/security/authentication/DaoAuthenticationProvider.java b/src/main/java/nextstep/security/authentication/DaoAuthenticationProvider.java new file mode 100644 index 0000000..ccb9e51 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/DaoAuthenticationProvider.java @@ -0,0 +1,29 @@ +package nextstep.security.authentication; + + +import nextstep.security.model.UserDetails; +import nextstep.security.service.UserDetailsService; + +import java.util.Objects; + +public class DaoAuthenticationProvider implements AuthenticationProvider { + private final UserDetailsService userDetailsService; + + public DaoAuthenticationProvider(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getPrincipal().toString()); + if (!Objects.equals(userDetails.getPassword(), authentication.getCredentials())) { + throw new AuthenticationException(); + } + return UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword()); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/nextstep/security/authentication/ProviderManager.java b/src/main/java/nextstep/security/authentication/ProviderManager.java new file mode 100644 index 0000000..d92266a --- /dev/null +++ b/src/main/java/nextstep/security/authentication/ProviderManager.java @@ -0,0 +1,21 @@ +package nextstep.security.authentication; + +import java.util.List; + +public class ProviderManager implements AuthenticationManager { + private final List providers; + + public ProviderManager(List providers) { + this.providers = providers; + } + + @Override + public Authentication authenticate(Authentication authentication) { + for (AuthenticationProvider provider : providers) { + if (provider.supports(authentication.getClass())) { + return provider.authenticate(authentication); + } + } + return null; + } +} diff --git a/src/main/java/nextstep/security/authentication/UsernamePasswordAuthenticationToken.java b/src/main/java/nextstep/security/authentication/UsernamePasswordAuthenticationToken.java new file mode 100644 index 0000000..c0bf98b --- /dev/null +++ b/src/main/java/nextstep/security/authentication/UsernamePasswordAuthenticationToken.java @@ -0,0 +1,38 @@ +package nextstep.security.authentication; + +public class UsernamePasswordAuthenticationToken implements Authentication { + + private final Object principal; + private final Object credentials; + private final boolean authenticated; + + private UsernamePasswordAuthenticationToken(Object principal, Object credentials, boolean authenticated) { + this.principal = principal; + this.credentials = credentials; + this.authenticated = authenticated; + } + + public static UsernamePasswordAuthenticationToken unauthenticated(String principal, String credentials) { + return new UsernamePasswordAuthenticationToken(principal, credentials, false); + } + + + public static UsernamePasswordAuthenticationToken authenticated(String principal, String credentials) { + return new UsernamePasswordAuthenticationToken(principal, credentials, true); + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } +} diff --git a/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java index af57aac..1102d5d 100644 --- a/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java @@ -1,31 +1,76 @@ package nextstep.security.filter; import nextstep.app.ui.AuthenticationException; -import nextstep.security.service.UserDetailService; +import nextstep.security.authentication.*; +import nextstep.security.service.UserDetailsService; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; public class BasicAuthenticationFilter extends OncePerRequestFilter { - private final UserDetailService userDetailsService; + public static final String AUTHENTICATION_SCHEME_BASIC = "Basic"; + private final AuthenticationManager authenticationManager; - public BasicAuthenticationFilter(UserDetailService userDetailsService) { - this.userDetailsService = userDetailsService; + public BasicAuthenticationFilter(UserDetailsService userDetailsService) { + this.authenticationManager = new ProviderManager( + List.of(new DaoAuthenticationProvider(userDetailsService)) + ); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { try { - checkAuthentication(request); + Authentication authentication = convert(request); + if (authentication == null) { + filterChain.doFilter(request, response); + return; + } + + this.authenticationManager.authenticate(authentication); + filterChain.doFilter(request, response); } catch (Exception e) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } - private void checkAuthentication(HttpServletRequest request) { - throw new AuthenticationException(); + private Authentication convert(HttpServletRequest request) { + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + if (header == null) { + return null; + } + header = header.trim(); + if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) { + return null; + } + if (header.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) { + throw new AuthenticationException(); + } + + byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8); + byte[] decoded = decode(base64Token); + String token = new String(decoded, StandardCharsets.UTF_8); + int delim = token.indexOf(":"); + if (delim == -1) { + throw new AuthenticationException(); + } + + return UsernamePasswordAuthenticationToken + .unauthenticated(token.substring(0, delim), token.substring(delim + 1)); + } + + private byte[] decode(byte[] base64Token) { + try { + return Base64.getDecoder().decode(base64Token); + } catch (IllegalArgumentException ex) { + throw new AuthenticationException(); + } } } diff --git a/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java index fe28714..80a044b 100644 --- a/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java @@ -1,8 +1,9 @@ package nextstep.security.filter; import nextstep.app.ui.AuthenticationException; -import nextstep.security.model.UserDetail; -import nextstep.security.service.UserDetailService; +import nextstep.security.authentication.*; +import nextstep.security.model.UserDetails; +import nextstep.security.service.UserDetailsService; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; @@ -13,34 +14,54 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Objects; public class UsernamePasswordAuthenticationFilter extends GenericFilterBean { public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; private static final String DEFAULT_REQUEST_URI = "/login"; - private final UserDetailService userDetailsService; - public UsernamePasswordAuthenticationFilter(UserDetailService userDetailService) { - this.userDetailsService = userDetailService; + private final AuthenticationManager authenticationManager; + + public UsernamePasswordAuthenticationFilter(UserDetailsService userDetailsService) { + this.authenticationManager = new ProviderManager( + List.of(new DaoAuthenticationProvider(userDetailsService)) + ); } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!DEFAULT_REQUEST_URI.equals(((HttpServletRequest) request).getRequestURI())) { chain.doFilter(request, response); return; } + try { - Map parameterMap = request.getParameterMap(); - String username = parameterMap.get("username")[0]; - String password = parameterMap.get("password")[0]; - UserDetail userDetails = userDetailsService.loadUserByUsername(username); - if (!Objects.equals(userDetails.getPassword(), password)) { - throw new AuthenticationException(); + Authentication authentication = convert(request); + if (authentication == null) { + chain.doFilter(request, response); + return; } + + Authentication authenticate = this.authenticationManager.authenticate(authentication); + HttpSession session = ((HttpServletRequest) request).getSession(); - session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetails); - } catch ( - AuthenticationException e) { + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, authenticate); + + } catch (Exception e) { ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } + + private Authentication convert(ServletRequest request) { + try { + Map parameterMap = request.getParameterMap(); + String username = parameterMap.get("username")[0]; + String password = parameterMap.get("password")[0]; + + return UsernamePasswordAuthenticationToken.unauthenticated(username, password); + } catch (Exception e) { + return null; + } + + } } \ No newline at end of file diff --git a/src/main/java/nextstep/security/model/UserDetail.java b/src/main/java/nextstep/security/model/UserDetails.java similarity index 52% rename from src/main/java/nextstep/security/model/UserDetail.java rename to src/main/java/nextstep/security/model/UserDetails.java index 0e95db2..d59bf39 100644 --- a/src/main/java/nextstep/security/model/UserDetail.java +++ b/src/main/java/nextstep/security/model/UserDetails.java @@ -1,6 +1,6 @@ package nextstep.security.model; -public interface UserDetail { - String getUserName(); +public interface UserDetails { + String getUsername(); String getPassword(); } diff --git a/src/main/java/nextstep/security/service/UserDetailService.java b/src/main/java/nextstep/security/service/UserDetailService.java deleted file mode 100644 index 7d6b35e..0000000 --- a/src/main/java/nextstep/security/service/UserDetailService.java +++ /dev/null @@ -1,9 +0,0 @@ -package nextstep.security.service; - -import nextstep.security.model.UserDetail; - - -public interface UserDetailService { - UserDetail loadUserByUsername(String username); - -} diff --git a/src/main/java/nextstep/security/service/UserDetailsService.java b/src/main/java/nextstep/security/service/UserDetailsService.java new file mode 100644 index 0000000..0e88f33 --- /dev/null +++ b/src/main/java/nextstep/security/service/UserDetailsService.java @@ -0,0 +1,9 @@ +package nextstep.security.service; + +import nextstep.security.model.UserDetails; + + +public interface UserDetailsService { + UserDetails loadUserByUsername(String username); + +}