ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,58 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import nextstep.security.AuthenticationException;
+import nextstep.security.util.Base64Convertor;
+import nextstep.security.UserDetails;
+import nextstep.security.UserDetailsService;
+import nextstep.security.util.matcher.MvcRequestMatcher;
+import org.springframework.http.HttpMethod;
+
+import java.io.IOException;
+
+public class BasicAuthenticationFilter implements Filter {
+
+    private static final MvcRequestMatcher DEFAULT_REQUEST_MATCHER = new MvcRequestMatcher(HttpMethod.GET, "/members");
+
+    private final UserDetailsService userDetailsService;
+
+    public BasicAuthenticationFilter(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        if (!DEFAULT_REQUEST_MATCHER.matches((HttpServletRequest) servletRequest)) {
+            filterChain.doFilter(servletRequest, servletResponse);
+            return;
+        }
+
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        try {
+            String authorization = request.getHeader("Authorization");
+            String credentials = authorization.split(" ")[1];
+            String decodedString = Base64Convertor.decode(credentials);
+            String[] usernameAndPassword = decodedString.split(":");
+            String username = usernameAndPassword[0];
+            String password = usernameAndPassword[1];
+
+            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+            if (!userDetails.getPassword().equals(password)) {
+                throw new AuthenticationException();
+            }
+
+            filterChain.doFilter(servletRequest, servletResponse);
+
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+}

리뷰: 2단계를 참고하여 어떤 책임들이 `Filter`에서 빠져야하는지 고민해보시면 좋을 것 같아요.

`Filter`의 책임은 request 요청을 받아 처리가 가능한지 확인하고, 결과에 대한 필터링을 하는데 이것만으로도 책임이 크기 때문에 2단계에서 나오는 클래스들에게 책임을 위임한다고 보시면 좋을 것 같습니다.
@@ -0,0 +1,58 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import nextstep.security.AuthenticationException;
+import nextstep.security.util.Base64Convertor;
+import nextstep.security.UserDetails;
+import nextstep.security.UserDetailsService;
+import nextstep.security.util.matcher.MvcRequestMatcher;
+import org.springframework.http.HttpMethod;
+
+import java.io.IOException;
+
+public class BasicAuthenticationFilter implements Filter {
+
+    private static final MvcRequestMatcher DEFAULT_REQUEST_MATCHER = new MvcRequestMatcher(HttpMethod.GET, "/members");
+
+    private final UserDetailsService userDetailsService;
+
+    public BasicAuthenticationFilter(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        if (!DEFAULT_REQUEST_MATCHER.matches((HttpServletRequest) servletRequest)) {
+            filterChain.doFilter(servletRequest, servletResponse);
+            return;
+        }
+
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        try {
+            String authorization = request.getHeader("Authorization");
+            String credentials = authorization.split(" ")[1];
+            String decodedString = Base64Convertor.decode(credentials);
+            String[] usernameAndPassword = decodedString.split(":");
+            String username = usernameAndPassword[0];
+            String password = usernameAndPassword[1];
+
+            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+            if (!userDetails.getPassword().equals(password)) {
+                throw new AuthenticationException();
+            }
+
+            filterChain.doFilter(servletRequest, servletResponse);
+
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+}

리뷰: 어떤 path로 요청되는 지에 대해서 객체에 상수로 고정하여 사용하는 것이 아닌 사용자에게 주입받아 생성하는 것이 `BasicAuthenticationFilter`의 재사용성에 도움을 줄 수 있을 것 같아요.
@@ -0,0 +1,60 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import nextstep.security.AuthenticationException;
+import nextstep.security.UserDetails;
+import nextstep.security.UserDetailsService;
+import nextstep.security.util.matcher.MvcRequestMatcher;
+import org.springframework.http.HttpMethod;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class FormLoginAuthenticationFilter implements Filter {
+
+    public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
+    private static final MvcRequestMatcher DEFAULT_REQUEST_MATCHER = new MvcRequestMatcher(HttpMethod.POST, "/login");
+
+    private final UserDetailsService userDetailsService;
+
+    public FormLoginAuthenticationFilter(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        if (!DEFAULT_REQUEST_MATCHER.matches((HttpServletRequest) servletRequest)) {
+            filterChain.doFilter(servletRequest, servletResponse);
+            return;
+        }
+
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        try {
+            Map<String, String[]> parameterMap = request.getParameterMap();
+            String username = parameterMap.get("username")[0];
+            String password = parameterMap.get("password")[0];
+
+            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+            if (!userDetails.getPassword().equals(password)) {
+                throw new AuthenticationException();
+            }
+
+            HttpSession session = request.getSession();
+            session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, userDetails);
+
+            filterChain.doFilter(servletRequest, servletResponse);
+
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+}

리뷰: ```suggestion
    private static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
```
접근을 막아도 될 것 같네요.
@@ -0,0 +1,33 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.context.SecurityContext;
+import nextstep.security.context.SecurityContextHolder;
+import nextstep.security.context.SecurityContextRepository;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+public class SecurityContextHolderFilter extends GenericFilterBean {
+    private final SecurityContextRepository securityContextRepository;
+
+    public SecurityContextHolderFilter(SecurityContextRepository securityContextRepository) {
+        this.securityContextRepository = securityContextRepository;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        try {
+            SecurityContext securityContext = securityContextRepository.loadContext((HttpServletRequest) request);
+            SecurityContextHolder.setContext(securityContext);
+
+            chain.doFilter(request, response);
+        } finally {
+            SecurityContextHolder.clearContext();
+        }
+    }
+}

리뷰: 현재 SecurityContextHolderFilter에서 securityContextRepository.saveContext(context, request, response);를 호출하는 방식은 Spring Security의 일반적인 흐름과 맞지 않을 것 같아요 😢
인증이 이루어지지 않았음에도 불구하고 SecurityContext를 저장할 가능성이 있을 것 같은데 개선해보면 어떨까요? 😄
@@ -0,0 +1,46 @@
+package nextstep.security.authentication;
+
+import nextstep.security.UserDetails;
+import nextstep.security.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 {
+        check(authentication);
+
+        final String username = authentication.getPrincipal().toString();
+        final String password = authentication.getCredentials().toString();
+        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+        if (Objects.equals(password, userDetails.getPassword())) {
+            return UsernamePasswordAuthenticationToken.ofAuthenticated(username, password);
+        }
+
+        throw new AuthenticationException();
+    }
+
+    private void check(Authentication authentication) {
+        if (authentication == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getPrincipal() == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getCredentials() == null) {
+            throw new AuthenticationException();
+        }
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}

리뷰: 해당 메서드에 static을 사용하신 이유가 있나요?
@@ -0,0 +1,46 @@
+package nextstep.security.authentication;
+
+import nextstep.security.UserDetails;
+import nextstep.security.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 {
+        check(authentication);
+
+        final String username = authentication.getPrincipal().toString();
+        final String password = authentication.getCredentials().toString();
+        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+        if (Objects.equals(password, userDetails.getPassword())) {
+            return UsernamePasswordAuthenticationToken.ofAuthenticated(username, password);
+        }
+
+        throw new AuthenticationException();
+    }
+
+    private void check(Authentication authentication) {
+        if (authentication == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getPrincipal() == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getCredentials() == null) {
+            throw new AuthenticationException();
+        }
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}

리뷰: == 연산자를 사용하여 UsernamePasswordAuthenticationToken과 직접 비교하고 있는데요,
isAssignableFrom() 로 비교하는 것과 어떠한 차이가 있을까요? 😄
@@ -0,0 +1,39 @@
+package nextstep.security.context;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
+    private static final String DEFAULT_SESSION_KEY = "SPRING_SECURITY_CONTEXT";
+
+    private final String sessionKey;
+
+    public HttpSessionSecurityContextRepository() {
+        this(DEFAULT_SESSION_KEY);
+    }
+
+    public HttpSessionSecurityContextRepository(String sessionKey) {
+        if (sessionKey == null) {
+            sessionKey = DEFAULT_SESSION_KEY;
+        }
+        this.sessionKey = sessionKey;
+    }
+
+    @Override
+    public SecurityContext loadContext(HttpServletRequest request) {
+        HttpSession session = request.getSession(false);
+        if (session == null) {
+            return SecurityContextHolder.createEmptyContext();
+        }
+        return (SecurityContext) session.getAttribute(sessionKey);
+    }
+
+    @Override
+    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+        session.setAttribute(sessionKey, context);
+
+        SecurityContextHolder.setContext(context);
+    }
+}

리뷰: 현재 request.getSession()을 호출하면 세션이 존재하지 않을 경우 자동으로 새 세션을 생성하게 됩니다.
세션이 필요하지 않은 요청에도 불필요한 세션이 생성될 수 있을 것 같은데, getSession(false)를 사용하면 어떤 차이가 있을까요? 🤔
기존 세션이 있는 경우에만 SecurityContext를 로드하도록 개선해보면 어떨까요? 😄
@@ -0,0 +1,60 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
+import nextstep.security.context.SecurityContextHolder;
+import nextstep.security.util.Base64Convertor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+public class BasicAuthenticationFilter extends OncePerRequestFilter {
+    private static final String BASIC_PREFIX = "basic ";
+
+    private final AuthenticationManager authenticationManager;
+
+    public BasicAuthenticationFilter(AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        final String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
+        if (isNotBasicAuth(authorization)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+        try {
+            String[] usernameAndPassword = extractUsernameAndPassword(authorization);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(usernameAndPassword[0], usernameAndPassword[1]);
+            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
+
+            SecurityContextHolder.getContext().setAuthentication(authenticate);
+
+            filterChain.doFilter(request, response);
+        } catch (Exception e) {
+            SecurityContextHolder.clearContext();
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+        }
+    }
+
+    private String[] extractUsernameAndPassword(String authorization) {
+        String base64Credentials = authorization.substring(BASIC_PREFIX.length());
+        String decodedString = Base64Convertor.decode(base64Credentials);
+        return decodedString.split(":");
+    }
+
+    private boolean isNotBasicAuth(String authorization) {
+        if (authorization == null) {
+            return true;
+        }
+        return !authorization.toLowerCase().startsWith(BASIC_PREFIX);
+    }
+}

리뷰: isNotBasicAuth() 메서드의 네이밍을 보면
Basic 인증이 아닐 경우 true를 반환해야 할 것 같지만, 
현재 구현에서는 Basic 인증일 때 true를 반환하고 있네요 😄
@@ -0,0 +1,33 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.context.SecurityContext;
+import nextstep.security.context.SecurityContextHolder;
+import nextstep.security.context.SecurityContextRepository;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+public class SecurityContextHolderFilter extends GenericFilterBean {
+    private final SecurityContextRepository securityContextRepository;
+
+    public SecurityContextHolderFilter(SecurityContextRepository securityContextRepository) {
+        this.securityContextRepository = securityContextRepository;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        try {
+            SecurityContext securityContext = securityContextRepository.loadContext((HttpServletRequest) request);
+            SecurityContextHolder.setContext(securityContext);
+
+            chain.doFilter(request, response);
+        } finally {
+            SecurityContextHolder.clearContext();
+        }
+    }
+}

리뷰: 앗 그러네요 !
인증을 받자마자 securityContextRepository 에 저장하는게 좋겠네요

[57d0f5](https://github.com/next-step/spring-security-authentication/pull/32/commits/57d0f520733998b10aec3ac441e30bf93b21cfa6) 해당 커밋에 반영하였습니다
@@ -0,0 +1,46 @@
+package nextstep.security.authentication;
+
+import nextstep.security.UserDetails;
+import nextstep.security.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 {
+        check(authentication);
+
+        final String username = authentication.getPrincipal().toString();
+        final String password = authentication.getCredentials().toString();
+        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+        if (Objects.equals(password, userDetails.getPassword())) {
+            return UsernamePasswordAuthenticationToken.ofAuthenticated(username, password);
+        }
+
+        throw new AuthenticationException();
+    }
+
+    private void check(Authentication authentication) {
+        if (authentication == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getPrincipal() == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getCredentials() == null) {
+            throw new AuthenticationException();
+        }
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}

리뷰: 😢 단축키를 잘 못써서 오타가 났나봐요..
[0f8f88b](https://github.com/next-step/spring-security-authentication/pull/32/commits/0f8f88b23614c091cf8dc72c8c06aca34a7c6b4c) 해당커밋에 반영하였습니다~
@@ -0,0 +1,46 @@
+package nextstep.security.authentication;
+
+import nextstep.security.UserDetails;
+import nextstep.security.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 {
+        check(authentication);
+
+        final String username = authentication.getPrincipal().toString();
+        final String password = authentication.getCredentials().toString();
+        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+        if (Objects.equals(password, userDetails.getPassword())) {
+            return UsernamePasswordAuthenticationToken.ofAuthenticated(username, password);
+        }
+
+        throw new AuthenticationException();
+    }
+
+    private void check(Authentication authentication) {
+        if (authentication == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getPrincipal() == null) {
+            throw new AuthenticationException();
+        }
+        if (authentication.getCredentials() == null) {
+            throw new AuthenticationException();
+        }
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}

리뷰: isAssignableFrom() 비교 하면 상위타입까지 비교 할 수 있군요!

음... 제가 생각하기에 프레임워크 사용자에 대해서 상속을 통해 UsernamePasswordAuthenticationToken를 확장하여 사용할 수 있을꺼 같네요. 

이슬님의 다른의견이 있다면 듣고 싶네요

e36ce53464741014a8391df7e2761816fb381512 사용자에게 확장을 제공하고자 해당 커밋에 변경하였습니다.
@@ -0,0 +1,39 @@
+package nextstep.security.context;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
+    private static final String DEFAULT_SESSION_KEY = "SPRING_SECURITY_CONTEXT";
+
+    private final String sessionKey;
+
+    public HttpSessionSecurityContextRepository() {
+        this(DEFAULT_SESSION_KEY);
+    }
+
+    public HttpSessionSecurityContextRepository(String sessionKey) {
+        if (sessionKey == null) {
+            sessionKey = DEFAULT_SESSION_KEY;
+        }
+        this.sessionKey = sessionKey;
+    }
+
+    @Override
+    public SecurityContext loadContext(HttpServletRequest request) {
+        HttpSession session = request.getSession(false);
+        if (session == null) {
+            return SecurityContextHolder.createEmptyContext();
+        }
+        return (SecurityContext) session.getAttribute(sessionKey);
+    }
+
+    @Override
+    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+        session.setAttribute(sessionKey, context);
+
+        SecurityContextHolder.setContext(context);
+    }
+}

리뷰: 앗.. 상세한 옵션에 대해서는 고려하지 못했었네요 getSession(false)은 세션을 생성하지 않고 null 을 반환 하는 군요

불필요한 세션이 생성되지 않도록 [291d54e](https://github.com/next-step/spring-security-authentication/pull/32/commits/291d54ecda1c0cae213d2bf9432e339a34c8c7f6) 해당 커밋에 수정하였습니다