Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[이준현,최지혜] 인증 과제 제출합니다. #23

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# spring-security-authentication

## 기능 요구 사항

1. 아이디 비밀번호 기반 로그인 기능 구현
2. Basic 인증 및 사용자를 식별하는 기능 구현

## 구현 기능 목록

### 아이디 비밀번호 기반 로그인 기능 구현

1.사용자가 입력한 아이디와 비밀번호를 바탕으로 사용자 정보를 읽어 온 후 인증
2.로그인 성공 시 Session에 인증 정보를 저장

### Basic 인증 구현

1. Basic Token을 디코딩하는 기능
2. 디코딩된 내용을 바탕으로 사용자를 식별하는 기능

### 리팩토링 사항

인증 로직과 서비스 로직 사이의 패키지 분리
패키지 사이의 의존성이 단반향으로 흐르도록 변경


Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nextstep.app;
package nextstep;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/nextstep/app/application/UserDetailServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nextstep.app.application;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.security.userdetail.UserDetail;
import nextstep.security.userdetail.UserDetailService;
import org.springframework.stereotype.Service;

@Service
public class UserDetailServiceImpl implements UserDetailService {

private final MemberRepository memberRepository;

public UserDetailServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@Override
public UserDetail getUserDetail(String username) {
return memberRepository.findByEmail(username)
.map(this::convertToUserDetail)
.orElse(null);
}

public UserDetail convertToUserDetail(Member member) {
return new UserDetail(member.getEmail(), member.getPassword());
}
}
20 changes: 2 additions & 18 deletions src/main/java/nextstep/app/ui/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
package nextstep.app.ui;

import nextstep.app.domain.MemberRepository;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
public class LoginController {
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";

private final MemberRepository memberRepository;

public LoginController(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@PostMapping("/login")
public ResponseEntity<Void> login(HttpServletRequest request, HttpSession session) {
return ResponseEntity.ok().build();
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<Void> handleAuthenticationException() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
10 changes: 10 additions & 0 deletions src/main/java/nextstep/security/authentication/Authentication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package nextstep.security.authentication;

public interface Authentication {

Object getCredentials();

Object getPrincipal();

boolean isAuthenticated();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nextstep.security.authentication;

public interface AuthenticationManager {

Authentication authenticate(Authentication authentication);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package nextstep.security.authentication;

import nextstep.security.exception.AuthenticationException;

public interface AuthenticationProvider {

Authentication authenticate(Authentication authentication) throws AuthenticationException;

boolean supports(Class<?> authentication);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package nextstep.security.authentication;

import nextstep.security.exception.AuthenticationException;
import nextstep.security.userdetail.UserDetail;
import nextstep.security.userdetail.UserDetailService;

public class DaoAuthenticationProvider implements AuthenticationProvider {

private final UserDetailService userDetailService;

public DaoAuthenticationProvider(UserDetailService userDetailService) {
this.userDetailService = userDetailService;
}

@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UserDetail userDetail = userDetailService.getUserDetail(
authentication.getPrincipal().toString());
if (userDetail.verifyPassword(authentication.getCredentials().toString())) {
throw new AuthenticationException();
}
return UsernamePasswordAuthenticationToken.authenticated(userDetail.getUsername(),
userDetail.getPassword());
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.security.authentication;

import java.util.List;

public class ProviderManager implements AuthenticationManager {

private final List<AuthenticationProvider> authenticationProviders;

public ProviderManager(List<AuthenticationProvider> authenticationProviders) {
this.authenticationProviders = authenticationProviders;
}

@Override
public Authentication authenticate(Authentication authentication) {
for (AuthenticationProvider provider : authenticationProviders) {
if (provider.supports(authentication.getClass())) {
return provider.authenticate(authentication);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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 this.credentials;
}

@Override
public Object getPrincipal() {
return this.principal;
}

@Override
public boolean isAuthenticated() {
return this.authenticated;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package nextstep.security.authentication.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import nextstep.security.authentication.AuthenticationManager;
import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
import nextstep.security.userdetail.UserDetail;
import nextstep.security.util.TokenDecoder;
import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.OncePerRequestFilter;

public class BasicAuthFilter extends OncePerRequestFilter {

private final TokenDecoder tokenDecoder;
private final AuthenticationManager authenticationManager;

public BasicAuthFilter(TokenDecoder tokenDecoder, AuthenticationManager authenticationManager) {
this.tokenDecoder = tokenDecoder;
this.authenticationManager = authenticationManager;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {

String token = request.getHeader(HttpHeaders.AUTHORIZATION);

UserDetail decodedUserInfo = tokenDecoder.decodeToken(token);

UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.unauthenticated(
decodedUserInfo.getUsername(), decodedUserInfo.getPassword());

authenticationManager.authenticate(authentication);

filterChain.doFilter(request, response);

} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package nextstep.security.authentication.filter;

import static nextstep.security.util.SecurityConstants.LOGIN_REQUEST_URI;
import static nextstep.security.util.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY;

import java.io.IOException;
import java.util.Objects;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import nextstep.security.authentication.Authentication;
import nextstep.security.authentication.AuthenticationManager;
import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
import nextstep.security.exception.AuthenticationException;
import nextstep.security.userdetail.UserDetail;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.GenericFilterBean;

public class FormLoginAuthFilter extends GenericFilterBean {

private final AuthenticationManager authenticationManager;

public FormLoginAuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
try {
var request = (HttpServletRequest) servletRequest;

if (!Objects.equals(request.getRequestURI(), LOGIN_REQUEST_URI)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}

validateParamAndSession(request);

String username = request.getParameter("username");
String password = request.getParameter("password");

UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.unauthenticated(
username, password);

Authentication authenticate = authenticationManager.authenticate(authentication);

filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
var response = (HttpServletResponse) servletResponse;
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}

private void validateParamAndSession(HttpServletRequest request) {
HttpSession session = request.getSession();

String username = request.getParameter("username");
String password = request.getParameter("password");

if (session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null) {
session.removeAttribute(SPRING_SECURITY_CONTEXT_KEY);
}

if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) {
throw new AuthenticationException();
}
}

private void verifyUserDetail(UserDetail userDetail, String password) {
if (Objects.isNull(userDetail)) {
throw new AuthenticationException();
}

if (!userDetail.verifyPassword(password)) {
throw new AuthenticationException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.security.config;

import java.util.List;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;

public class DefaultSecurityFilterChain implements SecurityFilterChain {

private final List<Filter> filters;

public DefaultSecurityFilterChain(List<Filter> filters) {
this.filters = filters;
}

@Override
public boolean matches(HttpServletRequest request) {
return true;
}

@Override
public List<Filter> getFilters() {
return filters;
}
}
Loading