From 2f31b70848b5ef6f3fbb9f8c715c8650cd338429 Mon Sep 17 00:00:00 2001 From: hanbee son Date: Mon, 28 Oct 2024 22:05:33 +0900 Subject: [PATCH 1/8] feat: update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 1e7ba652..c342ba83 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ # spring-security-authentication + +## 기능 요구 사항 +- 아이디/비밀번호 기반 로그인 구현 + + ```POST/login``` 경로로 로그인 요청 + + 사용자가 입력한 아이디와 비밀번호를 확인하여 인증 + + 로그인 성공 시 Session 을 사용하여 인증 정보를 저장 + + ```LoginTest```의 모든 테스트가 통과해야 한다. +- Basic 인증 구현 + + ```GET /member``` 요청 시 사용자 목록을 조회한다. + + 단, ```Member```로 등록되어있는 사용자만 가능하도록 한다. + + 이를 위해 Basic 인증을 사용하여 사용자를 식별한다. + + 요청의 Authorization 헤더에서 Basic 인증 정보를 추출하여 인증을 처리한다. + + 인증 성공 시 을 사용하여 인증 정보를 저장한다. + + ```MemberTest```의 모든 테스트가 통과해야 한다. +- 인터셉터 분리 + + ```HandlerInterceptor```를 사용하여 인증 관련 로직을 Controller 클래스에서 분리한다. + - 앞서 구현한 두 인증 방식(아이디 비밀번호 로그인 방식과 Basic 인증 방식) 모두 인터셉터에서 처리되도록 구현한다. + - 가급적이면 하나의 인터셉터는 하나의 작업만 수행하도록 설계한다. +- 인증 로직과 서비스 로직 간의 패키지 분리 + + 서비스 코드와 인증 코드를 명확히 분리하여 관리하도록 한다. + - 서비스 관련 코드는 ```app``` 패키지에 위치시키고, 인증 관련 코드는 ```security``` 패키지에 위치시킨다. + + 리팩터링 과정에서 패키지 간의 양방향 참조가 발생한다면 단방향 참조로 리팩터링한다. + - ```app``` 패키지는 ```security``` 패키지에 의존할 수 있지만, 반대로 ```security``` 패키지는 ```app``` 패키지에 의존하지 않도록 한다. + + 인증 관련 작업은 ```security``` 패키지에서 전담하도록 설계하여, 서비스 로직이 인증 세부 사항에 의존하지 않게 만든다. + + ```LoginTest```와 ```MemberTest```의 모든 테스트는 지속해서 통과해야 한다. \ No newline at end of file From 6142f12a101295af03be604e157e1acd88a9bc75 Mon Sep 17 00:00:00 2001 From: hanbee son Date: Mon, 28 Oct 2024 23:04:57 +0900 Subject: [PATCH 2/8] =?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=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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/app/ui/LoginController.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 0ea94f1b..cb946be1 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -22,6 +22,16 @@ public LoginController(MemberRepository memberRepository) { @PostMapping("/login") public ResponseEntity login(HttpServletRequest request, HttpSession session) { + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + memberRepository.findByEmail(username) + .filter(member -> member.getPassword().equals(password)) + .ifPresentOrElse( + member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), + () -> { throw new AuthenticationException(); } + ); + return ResponseEntity.ok().build(); } From c4f85fdb44cdd56ef7966de75647287d0db45ebf Mon Sep 17 00:00:00 2001 From: hanbee son Date: Mon, 28 Oct 2024 23:06:12 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20basic=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/app/ui/MemberController.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index c8cc74d6..dd540541 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,12 +2,20 @@ 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; +import static nextstep.app.ui.LoginController.SPRING_SECURITY_CONTEXT_KEY; + @RestController public class MemberController { @@ -18,9 +26,49 @@ public MemberController(MemberRepository memberRepository) { } @GetMapping("/members") - public ResponseEntity> list() { + public ResponseEntity> list(HttpServletRequest request, HttpSession session) { + authenticate(request, session); + List members = memberRepository.findAll(); return ResponseEntity.ok(members); } + private void authenticate(HttpServletRequest request, HttpSession session) { + String authorizationHeader = request.getHeader("Authorization"); + + if (!isValidAuthorizationHeader(authorizationHeader)) { + throw new AuthenticationException(); + } + + String[] credentials = parseCredentials(authorizationHeader); + String username = credentials[0]; + String password = credentials[1]; + + memberRepository.findByEmail(username) + .filter(member -> member.getPassword().equals(password)) + .ifPresentOrElse( + member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), + () -> { throw new AuthenticationException(); } + ); + } + + private boolean isValidAuthorizationHeader(String authorizationHeader) { + return authorizationHeader != null && authorizationHeader.startsWith("Basic "); + } + + private String[] parseCredentials(String authorizationHeader) { + String base64Credentials = authorizationHeader.substring("Basic ".length()); + String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); + + String[] values = credentials.split(":", 2); + if (values.length != 2) { + throw new AuthenticationException(); + } + return values; + } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException() { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } } From 5ac4650a4cc638686526d16ee389fe7bf5fb8d38 Mon Sep 17 00:00:00 2001 From: hanbee son Date: Tue, 29 Oct 2024 22:11:49 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20interceptor=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 | 25 ++++++++ .../BasicAuthenticationInterceptor.java | 61 +++++++++++++++++++ .../UsernamePasswordInterceptor.java | 42 +++++++++++++ .../java/nextstep/app/ui/LoginController.java | 10 --- .../nextstep/app/ui/MemberController.java | 40 ------------ 5 files changed, 128 insertions(+), 50 deletions(-) create mode 100644 src/main/java/nextstep/app/config/WebConfig.java create mode 100644 src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java create mode 100644 src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.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 00000000..dc681c9b --- /dev/null +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -0,0 +1,25 @@ +package nextstep.app.config; + +import nextstep.app.domain.MemberRepository; +import nextstep.app.interceptor.BasicAuthenticationInterceptor; +import nextstep.app.interceptor.UsernamePasswordInterceptor; +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 BasicAuthenticationInterceptor(memberRepository)) + .addPathPatterns("/members"); + + registry.addInterceptor(new UsernamePasswordInterceptor(memberRepository)); + } +} 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 00000000..633f1716 --- /dev/null +++ b/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java @@ -0,0 +1,61 @@ +package nextstep.app.interceptor; + +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 java.nio.charset.StandardCharsets; + + +public class BasicAuthenticationInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final MemberRepository memberRepository; + + public BasicAuthenticationInterceptor(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + authenticate(request); + return true; + } + + private void authenticate(HttpServletRequest request) { + String authorizationHeader = request.getHeader("Authorization"); + + if (!isValidAuthorizationHeader(authorizationHeader)) { + throw new AuthenticationException(); + } + + String[] credentials = parseCredentials(authorizationHeader); + String username = credentials[0]; + String password = credentials[1]; + + memberRepository.findByEmail(username) + .filter(member -> member.getPassword().equals(password)) + .ifPresentOrElse( + member -> request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), + () -> { throw new AuthenticationException(); } + ); + } + + private boolean isValidAuthorizationHeader(String authorizationHeader) { + return authorizationHeader != null && authorizationHeader.startsWith("Basic "); + } + + private String[] parseCredentials(String authorizationHeader) { + String base64Credentials = authorizationHeader.substring("Basic ".length()); + String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); + + String[] values = credentials.split(":", 2); + if (values.length != 2) { + throw new AuthenticationException(); + } + return values; + } +} diff --git a/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java b/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java new file mode 100644 index 00000000..4ab481c2 --- /dev/null +++ b/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java @@ -0,0 +1,42 @@ +package nextstep.app.interceptor; + +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 UsernamePasswordInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final MemberRepository memberRepository; + + public UsernamePasswordInterceptor(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(); + + if (session == null || session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null) { + authenticate(request); + } + + return true; + } + + private void authenticate(HttpServletRequest request) { + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + memberRepository.findByEmail(username) + .filter(member -> member.getPassword().equals(password)) + .ifPresentOrElse( + member -> request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), + () -> { throw new AuthenticationException(); } + ); + } +} diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index cb946be1..0ea94f1b 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -22,16 +22,6 @@ public LoginController(MemberRepository memberRepository) { @PostMapping("/login") public ResponseEntity login(HttpServletRequest request, HttpSession session) { - String username = request.getParameter("username"); - String password = request.getParameter("password"); - - memberRepository.findByEmail(username) - .filter(member -> member.getPassword().equals(password)) - .ifPresentOrElse( - member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), - () -> { throw new AuthenticationException(); } - ); - return ResponseEntity.ok().build(); } diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index dd540541..ac94b336 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -4,18 +4,14 @@ 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; -import static nextstep.app.ui.LoginController.SPRING_SECURITY_CONTEXT_KEY; - @RestController public class MemberController { @@ -27,46 +23,10 @@ public MemberController(MemberRepository memberRepository) { @GetMapping("/members") public ResponseEntity> list(HttpServletRequest request, HttpSession session) { - authenticate(request, session); - List members = memberRepository.findAll(); return ResponseEntity.ok(members); } - private void authenticate(HttpServletRequest request, HttpSession session) { - String authorizationHeader = request.getHeader("Authorization"); - - if (!isValidAuthorizationHeader(authorizationHeader)) { - throw new AuthenticationException(); - } - - String[] credentials = parseCredentials(authorizationHeader); - String username = credentials[0]; - String password = credentials[1]; - - memberRepository.findByEmail(username) - .filter(member -> member.getPassword().equals(password)) - .ifPresentOrElse( - member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), - () -> { throw new AuthenticationException(); } - ); - } - - private boolean isValidAuthorizationHeader(String authorizationHeader) { - return authorizationHeader != null && authorizationHeader.startsWith("Basic "); - } - - private String[] parseCredentials(String authorizationHeader) { - String base64Credentials = authorizationHeader.substring("Basic ".length()); - String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); - - String[] values = credentials.split(":", 2); - if (values.length != 2) { - throw new AuthenticationException(); - } - return values; - } - @ExceptionHandler(AuthenticationException.class) public ResponseEntity handleAuthenticationException() { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); From 109f92ffa9d9d8bbf4b423ca3cb757a325b1a0df Mon Sep 17 00:00:00 2001 From: hanbee son Date: Tue, 29 Oct 2024 23:35:09 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SecurityAuthenticationApplication.java | 2 +- .../java/nextstep/app/config/WebConfig.java | 18 ++++---- .../UsernamePasswordInterceptor.java | 42 ----------------- .../app/service/UserDetailsServiceImpl.java | 32 +++++++++++++ .../BasicAuthenticationInterceptor.java | 45 +++++++------------ .../FormLoginAuthenticationInterceptor.java | 33 ++++++++++++++ .../security/service/UserDetailsService.java | 5 +++ 7 files changed, 98 insertions(+), 79 deletions(-) rename src/main/java/nextstep/{app => }/SecurityAuthenticationApplication.java (93%) delete mode 100644 src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java create mode 100644 src/main/java/nextstep/app/service/UserDetailsServiceImpl.java rename src/main/java/nextstep/{app => security}/interceptor/BasicAuthenticationInterceptor.java (54%) create mode 100644 src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java create mode 100644 src/main/java/nextstep/security/service/UserDetailsService.java diff --git a/src/main/java/nextstep/app/SecurityAuthenticationApplication.java b/src/main/java/nextstep/SecurityAuthenticationApplication.java similarity index 93% rename from src/main/java/nextstep/app/SecurityAuthenticationApplication.java rename to src/main/java/nextstep/SecurityAuthenticationApplication.java index 0f8eb47d..1ecd05fe 100644 --- a/src/main/java/nextstep/app/SecurityAuthenticationApplication.java +++ b/src/main/java/nextstep/SecurityAuthenticationApplication.java @@ -1,4 +1,4 @@ -package nextstep.app; +package nextstep; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/nextstep/app/config/WebConfig.java b/src/main/java/nextstep/app/config/WebConfig.java index dc681c9b..de8320a6 100644 --- a/src/main/java/nextstep/app/config/WebConfig.java +++ b/src/main/java/nextstep/app/config/WebConfig.java @@ -1,25 +1,27 @@ package nextstep.app.config; -import nextstep.app.domain.MemberRepository; -import nextstep.app.interceptor.BasicAuthenticationInterceptor; -import nextstep.app.interceptor.UsernamePasswordInterceptor; +import nextstep.security.interceptor.BasicAuthenticationInterceptor; +import nextstep.security.interceptor.FormLoginAuthenticationInterceptor; 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 FormLoginAuthenticationInterceptor formLoginAuthenticationInterceptor; + private final BasicAuthenticationInterceptor basicAuthenticationInterceptor; - public WebConfig(MemberRepository memberRepository) { - this.memberRepository = memberRepository; + public WebConfig(FormLoginAuthenticationInterceptor formLoginAuthenticationInterceptor, + BasicAuthenticationInterceptor basicAuthenticationInterceptor) { + this.formLoginAuthenticationInterceptor = formLoginAuthenticationInterceptor; + this.basicAuthenticationInterceptor = basicAuthenticationInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new BasicAuthenticationInterceptor(memberRepository)) + registry.addInterceptor(basicAuthenticationInterceptor) .addPathPatterns("/members"); - registry.addInterceptor(new UsernamePasswordInterceptor(memberRepository)); + registry.addInterceptor(formLoginAuthenticationInterceptor); } } diff --git a/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java b/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java deleted file mode 100644 index 4ab481c2..00000000 --- a/src/main/java/nextstep/app/interceptor/UsernamePasswordInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -package nextstep.app.interceptor; - -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 UsernamePasswordInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - - private final MemberRepository memberRepository; - - public UsernamePasswordInterceptor(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - HttpSession session = request.getSession(); - - if (session == null || session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null) { - authenticate(request); - } - - return true; - } - - private void authenticate(HttpServletRequest request) { - String username = request.getParameter("username"); - String password = request.getParameter("password"); - - memberRepository.findByEmail(username) - .filter(member -> member.getPassword().equals(password)) - .ifPresentOrElse( - member -> request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), - () -> { throw new AuthenticationException(); } - ); - } -} diff --git a/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java b/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java new file mode 100644 index 00000000..38dbc799 --- /dev/null +++ b/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java @@ -0,0 +1,32 @@ +package nextstep.app.service; + +import nextstep.app.domain.MemberRepository; +import nextstep.app.ui.AuthenticationException; +import nextstep.security.service.UserDetailsService; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpSession; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final MemberRepository memberRepository; + private final HttpSession session; + + public UserDetailsServiceImpl(MemberRepository memberRepository, + HttpSession httpSession) { + this.memberRepository = memberRepository; + this.session = httpSession; + } + + @Override + public void authenticate(String username, String password) { + memberRepository.findByEmail(username) + .filter(member -> member.getPassword().equals(password)) + .ifPresentOrElse( + member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), + () -> { throw new AuthenticationException(); } + ); + } +} diff --git a/src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java similarity index 54% rename from src/main/java/nextstep/app/interceptor/BasicAuthenticationInterceptor.java rename to src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java index 633f1716..75d59200 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.MemberRepository; -import nextstep.app.ui.AuthenticationException; +import nextstep.security.service.UserDetailsService; +import org.springframework.stereotype.Component; import org.springframework.util.Base64Utils; import org.springframework.web.servlet.HandlerInterceptor; @@ -9,39 +9,33 @@ import javax.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; - +@Component public class BasicAuthenticationInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - - private final MemberRepository memberRepository; + private final UserDetailsService userDetailsService; - public BasicAuthenticationInterceptor(MemberRepository memberRepository) { - this.memberRepository = memberRepository; + public BasicAuthenticationInterceptor(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - authenticate(request); - return true; - } - - private void authenticate(HttpServletRequest request) { String authorizationHeader = request.getHeader("Authorization"); if (!isValidAuthorizationHeader(authorizationHeader)) { - throw new AuthenticationException(); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; } String[] credentials = parseCredentials(authorizationHeader); + if (credentials.length != 2) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + String username = credentials[0]; String password = credentials[1]; - - memberRepository.findByEmail(username) - .filter(member -> member.getPassword().equals(password)) - .ifPresentOrElse( - member -> request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), - () -> { throw new AuthenticationException(); } - ); + userDetailsService.authenticate(username, password); + return true; } private boolean isValidAuthorizationHeader(String authorizationHeader) { @@ -51,11 +45,6 @@ private boolean isValidAuthorizationHeader(String authorizationHeader) { private String[] parseCredentials(String authorizationHeader) { String base64Credentials = authorizationHeader.substring("Basic ".length()); String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); - - String[] values = credentials.split(":", 2); - if (values.length != 2) { - throw new AuthenticationException(); - } - return values; + return credentials.split(":", 2); } } diff --git a/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java new file mode 100644 index 00000000..2f975aa8 --- /dev/null +++ b/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java @@ -0,0 +1,33 @@ +package nextstep.security.interceptor; + +import nextstep.security.service.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@Component +public class FormLoginAuthenticationInterceptor implements HandlerInterceptor { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + private final UserDetailsService userDetailsService; + + public FormLoginAuthenticationInterceptor(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(); + + if (session == null || session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null) { + String username = request.getParameter("username"); + String password = request.getParameter("password"); + userDetailsService.authenticate(username, password); + } + + return true; + } +} 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 00000000..f9d4eb6b --- /dev/null +++ b/src/main/java/nextstep/security/service/UserDetailsService.java @@ -0,0 +1,5 @@ +package nextstep.security.service; + +public interface UserDetailsService { + void authenticate(String username, String password); +} From 23db381f149ab4d405cc40bcf28e1fbf69b3d305 Mon Sep 17 00:00:00 2001 From: hanbee son Date: Fri, 1 Nov 2024 20:52:42 +0900 Subject: [PATCH 6/8] feat: refactoring --- .../nextstep/app/domain/UserDetailsImpl.java | 40 +++++++++++++++++++ .../app/domain/UserDetailsServiceImpl.java | 22 ++++++++++ .../app/service/UserDetailsServiceImpl.java | 32 --------------- .../java/nextstep/app/ui/LoginController.java | 9 ----- .../nextstep/app/ui/MemberController.java | 12 +----- .../context/SecurityContextHolder.java | 27 +++++++++++++ .../nextstep/security/domain/UserDetails.java | 7 ++++ .../security/domain/UserDetailsService.java | 5 +++ .../exception}/AuthenticationException.java | 2 +- .../exception/SecurityExceptionHandler.java | 15 +++++++ .../BasicAuthenticationInterceptor.java | 34 +++++++++++----- .../FormLoginAuthenticationInterceptor.java | 20 ++++++---- .../security/service/UserDetailsService.java | 5 --- 13 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 src/main/java/nextstep/app/domain/UserDetailsImpl.java create mode 100644 src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java delete mode 100644 src/main/java/nextstep/app/service/UserDetailsServiceImpl.java create mode 100644 src/main/java/nextstep/security/context/SecurityContextHolder.java create mode 100644 src/main/java/nextstep/security/domain/UserDetails.java create mode 100644 src/main/java/nextstep/security/domain/UserDetailsService.java rename src/main/java/nextstep/{app/ui => security/exception}/AuthenticationException.java (64%) create mode 100644 src/main/java/nextstep/security/exception/SecurityExceptionHandler.java delete mode 100644 src/main/java/nextstep/security/service/UserDetailsService.java diff --git a/src/main/java/nextstep/app/domain/UserDetailsImpl.java b/src/main/java/nextstep/app/domain/UserDetailsImpl.java new file mode 100644 index 00000000..584b6ad6 --- /dev/null +++ b/src/main/java/nextstep/app/domain/UserDetailsImpl.java @@ -0,0 +1,40 @@ +package nextstep.app.domain; + +import nextstep.security.domain.UserDetails; + +public class UserDetailsImpl implements UserDetails { + private final String email; + private final String password; + + private UserDetailsImpl(String email, String password) { + this.email = email; + this.password = password; + } + + public static UserDetailsImpl of(Member member) { + return new UserDetailsImpl(member.getEmail(), member.getPassword()); + } + + public static UserDetailsImpl empty() { + return new UserDetailsImpl(null, null); + } + + @Override + public String getUsername() { + return email; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean isEmpty() { + return email == null && password == null; + } + + public boolean verifyPassword(String password) { + return this.password.equals(password); + } +} diff --git a/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java b/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java new file mode 100644 index 00000000..53f21e1a --- /dev/null +++ b/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java @@ -0,0 +1,22 @@ +package nextstep.app.domain; + +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +import org.springframework.stereotype.Service; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + private final MemberRepository memberRepository; + + public UserDetailsServiceImpl(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Override + public UserDetails loadUserByUsernameAndPassword(String username, String password) { + return memberRepository.findByEmail(username) + .map(UserDetailsImpl::of) + .filter(userDetails -> userDetails.verifyPassword(password)) + .orElse(UserDetailsImpl.empty()); + } +} diff --git a/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java b/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java deleted file mode 100644 index 38dbc799..00000000 --- a/src/main/java/nextstep/app/service/UserDetailsServiceImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -package nextstep.app.service; - -import nextstep.app.domain.MemberRepository; -import nextstep.app.ui.AuthenticationException; -import nextstep.security.service.UserDetailsService; -import org.springframework.stereotype.Service; - -import javax.servlet.http.HttpSession; - -@Service -public class UserDetailsServiceImpl implements UserDetailsService { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - - private final MemberRepository memberRepository; - private final HttpSession session; - - public UserDetailsServiceImpl(MemberRepository memberRepository, - HttpSession httpSession) { - this.memberRepository = memberRepository; - this.session = httpSession; - } - - @Override - public void authenticate(String username, String password) { - memberRepository.findByEmail(username) - .filter(member -> member.getPassword().equals(password)) - .ifPresentOrElse( - member -> session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member), - () -> { throw new AuthenticationException(); } - ); - } -} diff --git a/src/main/java/nextstep/app/ui/LoginController.java b/src/main/java/nextstep/app/ui/LoginController.java index 0ea94f1b..7fddf9c5 100644 --- a/src/main/java/nextstep/app/ui/LoginController.java +++ b/src/main/java/nextstep/app/ui/LoginController.java @@ -1,9 +1,7 @@ package nextstep.app.ui; 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; @@ -12,8 +10,6 @@ @RestController public class LoginController { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final MemberRepository memberRepository; public LoginController(MemberRepository memberRepository) { @@ -24,9 +20,4 @@ public LoginController(MemberRepository memberRepository) { public ResponseEntity login(HttpServletRequest request, HttpSession session) { return ResponseEntity.ok().build(); } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException() { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } } diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index ac94b336..0f706d14 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,19 +2,14 @@ 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.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.util.List; @RestController public class MemberController { - private final MemberRepository memberRepository; public MemberController(MemberRepository memberRepository) { @@ -22,13 +17,8 @@ public MemberController(MemberRepository memberRepository) { } @GetMapping("/members") - public ResponseEntity> list(HttpServletRequest request, HttpSession session) { + public ResponseEntity> list() { List members = memberRepository.findAll(); return ResponseEntity.ok(members); } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException() { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } } diff --git a/src/main/java/nextstep/security/context/SecurityContextHolder.java b/src/main/java/nextstep/security/context/SecurityContextHolder.java new file mode 100644 index 00000000..dd0b99a6 --- /dev/null +++ b/src/main/java/nextstep/security/context/SecurityContextHolder.java @@ -0,0 +1,27 @@ +package nextstep.security.context; + +import nextstep.security.domain.UserDetails; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public final class SecurityContextHolder { + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + + public static UserDetails getUserDetails() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return null; + } + + return (UserDetails) attributes.getAttribute(SPRING_SECURITY_CONTEXT_KEY, RequestAttributes.SCOPE_SESSION); + } + + public static void setUserDetails(UserDetails userDetails) { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return; + } + + attributes.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetails, RequestAttributes.SCOPE_SESSION); + } +} diff --git a/src/main/java/nextstep/security/domain/UserDetails.java b/src/main/java/nextstep/security/domain/UserDetails.java new file mode 100644 index 00000000..9a0eed57 --- /dev/null +++ b/src/main/java/nextstep/security/domain/UserDetails.java @@ -0,0 +1,7 @@ +package nextstep.security.domain; + +public interface UserDetails { + String getUsername(); + String getPassword(); + boolean isEmpty(); +} diff --git a/src/main/java/nextstep/security/domain/UserDetailsService.java b/src/main/java/nextstep/security/domain/UserDetailsService.java new file mode 100644 index 00000000..4a22bd08 --- /dev/null +++ b/src/main/java/nextstep/security/domain/UserDetailsService.java @@ -0,0 +1,5 @@ +package nextstep.security.domain; + +public interface UserDetailsService { + UserDetails loadUserByUsernameAndPassword(String username, String password); +} diff --git a/src/main/java/nextstep/app/ui/AuthenticationException.java b/src/main/java/nextstep/security/exception/AuthenticationException.java similarity index 64% rename from src/main/java/nextstep/app/ui/AuthenticationException.java rename to src/main/java/nextstep/security/exception/AuthenticationException.java index f809b6e4..1271b52d 100644 --- a/src/main/java/nextstep/app/ui/AuthenticationException.java +++ b/src/main/java/nextstep/security/exception/AuthenticationException.java @@ -1,4 +1,4 @@ -package nextstep.app.ui; +package nextstep.security.exception; public class AuthenticationException extends RuntimeException { } diff --git a/src/main/java/nextstep/security/exception/SecurityExceptionHandler.java b/src/main/java/nextstep/security/exception/SecurityExceptionHandler.java new file mode 100644 index 00000000..618c189b --- /dev/null +++ b/src/main/java/nextstep/security/exception/SecurityExceptionHandler.java @@ -0,0 +1,15 @@ +package nextstep.security.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class SecurityExceptionHandler { + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException() { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } +} diff --git a/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java index 75d59200..bd50a428 100644 --- a/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java +++ b/src/main/java/nextstep/security/interceptor/BasicAuthenticationInterceptor.java @@ -1,6 +1,9 @@ package nextstep.security.interceptor; -import nextstep.security.service.UserDetailsService; +import nextstep.security.context.SecurityContextHolder; +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +import nextstep.security.exception.AuthenticationException; import org.springframework.stereotype.Component; import org.springframework.util.Base64Utils; import org.springframework.web.servlet.HandlerInterceptor; @@ -11,6 +14,9 @@ @Component public class BasicAuthenticationInterceptor implements HandlerInterceptor { + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BASIC_PREFIX = "Basic "; + private final UserDetailsService userDetailsService; public BasicAuthenticationInterceptor(UserDetailsService userDetailsService) { @@ -19,32 +25,38 @@ public BasicAuthenticationInterceptor(UserDetailsService userDetailsService) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String authorizationHeader = request.getHeader("Authorization"); + String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER); if (!isValidAuthorizationHeader(authorizationHeader)) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - return false; + return unauthorized(response); } String[] credentials = parseCredentials(authorizationHeader); if (credentials.length != 2) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - return false; + return unauthorized(response); + } + + UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(credentials[0], credentials[1]); + if (userDetails.isEmpty()) { + throw new AuthenticationException(); } - String username = credentials[0]; - String password = credentials[1]; - userDetailsService.authenticate(username, password); + SecurityContextHolder.setUserDetails(userDetails); return true; } private boolean isValidAuthorizationHeader(String authorizationHeader) { - return authorizationHeader != null && authorizationHeader.startsWith("Basic "); + return authorizationHeader != null && authorizationHeader.startsWith(BASIC_PREFIX); } private String[] parseCredentials(String authorizationHeader) { - String base64Credentials = authorizationHeader.substring("Basic ".length()); + String base64Credentials = authorizationHeader.substring(BASIC_PREFIX.length()); String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); return credentials.split(":", 2); } + + private boolean unauthorized(HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } } diff --git a/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java b/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java index 2f975aa8..5558ed97 100644 --- a/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java +++ b/src/main/java/nextstep/security/interceptor/FormLoginAuthenticationInterceptor.java @@ -1,17 +1,17 @@ package nextstep.security.interceptor; -import nextstep.security.service.UserDetailsService; +import nextstep.security.context.SecurityContextHolder; +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +import nextstep.security.exception.AuthenticationException; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; @Component public class FormLoginAuthenticationInterceptor implements HandlerInterceptor { - public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; - private final UserDetailsService userDetailsService; public FormLoginAuthenticationInterceptor(UserDetailsService userDetailsService) { @@ -20,12 +20,16 @@ public FormLoginAuthenticationInterceptor(UserDetailsService userDetailsService) @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - HttpSession session = request.getSession(); - - if (session == null || session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null) { + if (SecurityContextHolder.getUserDetails() == null) { String username = request.getParameter("username"); String password = request.getParameter("password"); - userDetailsService.authenticate(username, password); + + UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(username, password); + if (userDetails.isEmpty()) { + throw new AuthenticationException(); + } + + SecurityContextHolder.setUserDetails(userDetails); } return true; diff --git a/src/main/java/nextstep/security/service/UserDetailsService.java b/src/main/java/nextstep/security/service/UserDetailsService.java deleted file mode 100644 index f9d4eb6b..00000000 --- a/src/main/java/nextstep/security/service/UserDetailsService.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.security.service; - -public interface UserDetailsService { - void authenticate(String username, String password); -} From 36dbe7bddcfd0a3f3a32d5a3be65b29d3f750b1e Mon Sep 17 00:00:00 2001 From: hanbee son Date: Mon, 4 Nov 2024 15:12:44 +0900 Subject: [PATCH 7/8] =?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 --- .../config/DefaultSecurityFilterChain.java | 23 ++++++ .../config/DelegatingFilterProxy.java | 19 +++++ .../security/config/FilterChainProxy.java | 59 +++++++++++++++ .../security/config/SecurityConfig.java | 38 ++++++++++ .../security/config/SecurityFilterChain.java | 10 +++ .../filter/BasicAuthenticationFilter.java | 75 +++++++++++++++++++ .../UsernamePasswordAuthenticationFilter.java | 42 +++++++++++ 7 files changed, 266 insertions(+) create mode 100644 src/main/java/nextstep/security/config/DefaultSecurityFilterChain.java create mode 100644 src/main/java/nextstep/security/config/DelegatingFilterProxy.java create mode 100644 src/main/java/nextstep/security/config/FilterChainProxy.java create mode 100644 src/main/java/nextstep/security/config/SecurityConfig.java create mode 100644 src/main/java/nextstep/security/config/SecurityFilterChain.java create mode 100644 src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java create mode 100644 src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java diff --git a/src/main/java/nextstep/security/config/DefaultSecurityFilterChain.java b/src/main/java/nextstep/security/config/DefaultSecurityFilterChain.java new file mode 100644 index 00000000..b211b0c2 --- /dev/null +++ b/src/main/java/nextstep/security/config/DefaultSecurityFilterChain.java @@ -0,0 +1,23 @@ +package nextstep.security.config; + +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 filters; + } +} diff --git a/src/main/java/nextstep/security/config/DelegatingFilterProxy.java b/src/main/java/nextstep/security/config/DelegatingFilterProxy.java new file mode 100644 index 00000000..714cbbe0 --- /dev/null +++ b/src/main/java/nextstep/security/config/DelegatingFilterProxy.java @@ -0,0 +1,19 @@ +package nextstep.security.config; + +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/config/FilterChainProxy.java b/src/main/java/nextstep/security/config/FilterChainProxy.java new file mode 100644 index 00000000..95ae4dbd --- /dev/null +++ b/src/main/java/nextstep/security/config/FilterChainProxy.java @@ -0,0 +1,59 @@ +package nextstep.security.config; + +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 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 filterChain : filterChains) { + if (filterChain.matches(request)) { + return filterChain.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; + + public VirtualFilterChain(FilterChain originalChain, List additionalFilters) { + this.originalChain = originalChain; + this.additionalFilters = additionalFilters; + this.size = additionalFilters.size(); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { + if (currentPosition == size) { + originalChain.doFilter(servletRequest, servletResponse); + return; + } + this.currentPosition++; + Filter nextFilter = additionalFilters.get(currentPosition - 1); + nextFilter.doFilter(servletRequest, servletResponse, this); + } + } +} diff --git a/src/main/java/nextstep/security/config/SecurityConfig.java b/src/main/java/nextstep/security/config/SecurityConfig.java new file mode 100644 index 00000000..1ff3eecd --- /dev/null +++ b/src/main/java/nextstep/security/config/SecurityConfig.java @@ -0,0 +1,38 @@ +package nextstep.security.config; + +import nextstep.security.domain.UserDetailsService; +import nextstep.security.filter.BasicAuthenticationFilter; +import nextstep.security.filter.UsernamePasswordAuthenticationFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class SecurityConfig { + private final UserDetailsService userDetailsService; + + public SecurityConfig(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @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( + new BasicAuthenticationFilter(userDetailsService), + new UsernamePasswordAuthenticationFilter(userDetailsService) + ) + ); + } +} diff --git a/src/main/java/nextstep/security/config/SecurityFilterChain.java b/src/main/java/nextstep/security/config/SecurityFilterChain.java new file mode 100644 index 00000000..dc44b215 --- /dev/null +++ b/src/main/java/nextstep/security/config/SecurityFilterChain.java @@ -0,0 +1,10 @@ +package nextstep.security.config; + +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/filter/BasicAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java new file mode 100644 index 00000000..7b4b6216 --- /dev/null +++ b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java @@ -0,0 +1,75 @@ +package nextstep.security.filter; + +import nextstep.security.context.SecurityContextHolder; +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +import org.springframework.util.Base64Utils; +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 java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class BasicAuthenticationFilter extends GenericFilterBean { + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BASIC_PREFIX = "Basic "; + + private final UserDetailsService userDetailsService; + + public BasicAuthenticationFilter(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletRequest = (HttpServletRequest) request; + + if (!("GET".equals(servletRequest.getMethod()) + && servletRequest.getRequestURI().startsWith("/members"))) { + chain.doFilter(request, response); + return; + } + + String authorizationHeader = servletRequest.getHeader(AUTHORIZATION_HEADER); + + if (!isValidAuthorizationHeader(authorizationHeader)) { + unauthorized((HttpServletResponse) response); + return; + } + + String[] credentials = parseCredentials(authorizationHeader); + if (credentials.length != 2) { + unauthorized((HttpServletResponse) response); + return; + } + + UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(credentials[0], credentials[1]); + if (userDetails.isEmpty()) { + unauthorized((HttpServletResponse) response); + return; + } + + SecurityContextHolder.setUserDetails(userDetails); + chain.doFilter(request, response); + return; + } + + private boolean isValidAuthorizationHeader(String authorizationHeader) { + return authorizationHeader != null && authorizationHeader.startsWith(BASIC_PREFIX); + } + + private String[] parseCredentials(String authorizationHeader) { + String base64Credentials = authorizationHeader.substring(BASIC_PREFIX.length()); + String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); + return credentials.split(":", 2); + } + + private void unauthorized(HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } +} 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 00000000..71d7cb83 --- /dev/null +++ b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java @@ -0,0 +1,42 @@ +package nextstep.security.filter; + +import nextstep.security.context.SecurityContextHolder; +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +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 java.io.IOException; + +public class UsernamePasswordAuthenticationFilter extends GenericFilterBean { + private final UserDetailsService userDetailsService; + + public UsernamePasswordAuthenticationFilter(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + + if (SecurityContextHolder.getUserDetails() != null) { + filterChain.doFilter(request, servletResponse); + return; + } + + String username = request.getParameter("username"); + String password = request.getParameter("password"); + + UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(username, password); + if (userDetails.isEmpty()) { + ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + SecurityContextHolder.setUserDetails(userDetails); + } +} From 557e429b8892c0dc1a5e1ede4fef5c46cfe1b811 Mon Sep 17 00:00:00 2001 From: hanbee son Date: Mon, 4 Nov 2024 21:17:07 +0900 Subject: [PATCH 8/8] =?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 --- .../app/domain/UserDetailsServiceImpl.java | 7 ++ .../authentication/Authentication.java | 7 ++ .../authentication/AuthenticationManager.java | 5 ++ .../AuthenticationProvider.java | 8 +++ .../DaoAuthenticationProvider.java | 31 ++++++++ .../authentication/ProviderManager.java | 23 ++++++ .../UsernamePasswordAuthenticationToken.java | 38 ++++++++++ .../security/config/SecurityConfig.java | 4 +- .../security/domain/UserDetailsService.java | 1 + .../filter/BasicAuthenticationFilter.java | 72 +++++++++---------- .../UsernamePasswordAuthenticationFilter.java | 50 +++++++++---- 11 files changed, 189 insertions(+), 57 deletions(-) create mode 100644 src/main/java/nextstep/security/authentication/Authentication.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 diff --git a/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java b/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java index 53f21e1a..d323bcdb 100644 --- a/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java +++ b/src/main/java/nextstep/app/domain/UserDetailsServiceImpl.java @@ -19,4 +19,11 @@ public UserDetails loadUserByUsernameAndPassword(String username, String passwor .filter(userDetails -> userDetails.verifyPassword(password)) .orElse(UserDetailsImpl.empty()); } + + @Override + public UserDetails loadUserByUsername(String username) { + return memberRepository.findByEmail(username) + .map(UserDetailsImpl::of) + .orElse(null); + } } 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 00000000..117632b7 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/Authentication.java @@ -0,0 +1,7 @@ +package nextstep.security.authentication; + +public interface Authentication { + Object getCredentials(); + Object getPrincipal(); + boolean isAuthenticated(); +} 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 00000000..ac74b1eb --- /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 00000000..bf1e940d --- /dev/null +++ b/src/main/java/nextstep/security/authentication/AuthenticationProvider.java @@ -0,0 +1,8 @@ +package nextstep.security.authentication; + +import nextstep.security.exception.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 00000000..7ccb8b1b --- /dev/null +++ b/src/main/java/nextstep/security/authentication/DaoAuthenticationProvider.java @@ -0,0 +1,31 @@ +package nextstep.security.authentication; + +import nextstep.security.domain.UserDetails; +import nextstep.security.domain.UserDetailsService; +import nextstep.security.exception.AuthenticationException; + +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 00000000..d6dae866 --- /dev/null +++ b/src/main/java/nextstep/security/authentication/ProviderManager.java @@ -0,0 +1,23 @@ +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 00000000..375ff8e7 --- /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; + + public 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/config/SecurityConfig.java b/src/main/java/nextstep/security/config/SecurityConfig.java index 1ff3eecd..8c8b8fc0 100644 --- a/src/main/java/nextstep/security/config/SecurityConfig.java +++ b/src/main/java/nextstep/security/config/SecurityConfig.java @@ -30,8 +30,8 @@ public FilterChainProxy filterChainProxy(List securityFilte public SecurityFilterChain securityFilterChain() { return new DefaultSecurityFilterChain( List.of( - new BasicAuthenticationFilter(userDetailsService), - new UsernamePasswordAuthenticationFilter(userDetailsService) + new UsernamePasswordAuthenticationFilter(userDetailsService), + new BasicAuthenticationFilter(userDetailsService) ) ); } diff --git a/src/main/java/nextstep/security/domain/UserDetailsService.java b/src/main/java/nextstep/security/domain/UserDetailsService.java index 4a22bd08..d57d47da 100644 --- a/src/main/java/nextstep/security/domain/UserDetailsService.java +++ b/src/main/java/nextstep/security/domain/UserDetailsService.java @@ -2,4 +2,5 @@ public interface UserDetailsService { UserDetails loadUserByUsernameAndPassword(String username, String password); + UserDetails loadUserByUsername(String username); } diff --git a/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java index 7b4b6216..239b6e78 100644 --- a/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/BasicAuthenticationFilter.java @@ -1,75 +1,67 @@ package nextstep.security.filter; -import nextstep.security.context.SecurityContextHolder; -import nextstep.security.domain.UserDetails; +import nextstep.security.authentication.*; import nextstep.security.domain.UserDetailsService; +import nextstep.security.exception.AuthenticationException; +import org.springframework.http.HttpHeaders; import org.springframework.util.Base64Utils; -import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.filter.OncePerRequestFilter; 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 java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; -public class BasicAuthenticationFilter extends GenericFilterBean { - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BASIC_PREFIX = "Basic "; +public class BasicAuthenticationFilter extends OncePerRequestFilter { + private static final String AUTHENTICATION_SCHEME_BASIC = "Basic "; - private final UserDetailsService userDetailsService; + private final AuthenticationManager authenticationManager; public BasicAuthenticationFilter(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; + this.authenticationManager = new ProviderManager( + List.of(new DaoAuthenticationProvider(userDetailsService)) + ); } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest servletRequest = (HttpServletRequest) request; - - if (!("GET".equals(servletRequest.getMethod()) - && servletRequest.getRequestURI().startsWith("/members"))) { - chain.doFilter(request, response); - return; + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + 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); } + } - String authorizationHeader = servletRequest.getHeader(AUTHORIZATION_HEADER); - + private Authentication convert(HttpServletRequest request) { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if (!isValidAuthorizationHeader(authorizationHeader)) { - unauthorized((HttpServletResponse) response); - return; + return null; } - String[] credentials = parseCredentials(authorizationHeader); - if (credentials.length != 2) { - unauthorized((HttpServletResponse) response); - return; + String[] token = parseCredentials(authorizationHeader); + if (token.length != 2) { + throw new AuthenticationException(); } - UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(credentials[0], credentials[1]); - if (userDetails.isEmpty()) { - unauthorized((HttpServletResponse) response); - return; - } - - SecurityContextHolder.setUserDetails(userDetails); - chain.doFilter(request, response); - return; + return UsernamePasswordAuthenticationToken.unauthenticated(token[0], token[1]); } private boolean isValidAuthorizationHeader(String authorizationHeader) { - return authorizationHeader != null && authorizationHeader.startsWith(BASIC_PREFIX); + return authorizationHeader != null && authorizationHeader.startsWith(AUTHENTICATION_SCHEME_BASIC); } private String[] parseCredentials(String authorizationHeader) { - String base64Credentials = authorizationHeader.substring(BASIC_PREFIX.length()); + String base64Credentials = authorizationHeader.substring(AUTHENTICATION_SCHEME_BASIC.length()); String credentials = new String(Base64Utils.decodeFromString(base64Credentials), StandardCharsets.UTF_8); return credentials.split(":", 2); } - - private void unauthorized(HttpServletResponse response) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } } diff --git a/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java index 71d7cb83..f963b1ac 100644 --- a/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java +++ b/src/main/java/nextstep/security/filter/UsernamePasswordAuthenticationFilter.java @@ -1,7 +1,6 @@ package nextstep.security.filter; -import nextstep.security.context.SecurityContextHolder; -import nextstep.security.domain.UserDetails; +import nextstep.security.authentication.*; import nextstep.security.domain.UserDetailsService; import org.springframework.web.filter.GenericFilterBean; @@ -11,32 +10,53 @@ 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.List; public class UsernamePasswordAuthenticationFilter extends GenericFilterBean { - private final UserDetailsService userDetailsService; + public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; + private static final String DEFAULT_REQUEST_URI = "/login"; + + private final AuthenticationManager authenticationManager; public UsernamePasswordAuthenticationFilter(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; + this.authenticationManager = new ProviderManager( + List.of(new DaoAuthenticationProvider(userDetailsService)) + ); } @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) servletRequest; - - if (SecurityContextHolder.getUserDetails() != null) { - filterChain.doFilter(request, servletResponse); + 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; } - String username = request.getParameter("username"); - String password = request.getParameter("password"); + try { + Authentication authentication = convert(request); + if (authentication == null) { + chain.doFilter(request, response); + return; + } + + Authentication authenticate = authenticationManager.authenticate(authentication); - UserDetails userDetails = userDetailsService.loadUserByUsernameAndPassword(username, password); - if (userDetails.isEmpty()) { - ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + HttpSession session = ((HttpServletRequest) request).getSession(); + session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, authenticate); + } catch (Exception e) { + ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); } + } - SecurityContextHolder.setUserDetails(userDetails); + private Authentication convert(ServletRequest servletRequest) { + try { + HttpServletRequest request = (HttpServletRequest) servletRequest; + String username = request.getParameter("username"); + String password = request.getParameter("password"); + return UsernamePasswordAuthenticationToken.unauthenticated(username, password); + } catch (Exception e) { + return null; + } } }