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

[김대희] 프리코스 미션 제출합니다. + Day 1 미션 (김대희, 김혜지) #20

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

1. LoginController 를 통한 인증 관리
- /login URL 에 `MemberAuthorizationInterceptor` 를 적용
- `MemberAuthorizationInterceptor` 는 세션에 이미 저장된 인증값이 있는 경우 바이패스 됨
- 세션 인증값이 없는 경우 주어진 request parameter 로 repository 에서 `Member` 를 조회
- 조회 결과가 있는 경우 세션 업데이트, 인증 처리 함


2. MemberController 를 통한 인가 관리
- /member URL 에 `BasicAuthenticationInterceptor` 를 적용
- `BasicAuthenticationInterceptor` 는 토큰을 받아 해당 토큰이 유효한지 여부를 판단
- 이 때 토큰을 decode 하기 위해 `BasicAuthenticationService` 에서 Base64 기준 토큰 분해 및 `Member` 객체에 담아서 리턴
- `BasicAuthenticationInterceptor` 는 토큰을 분해해서 얻은 Member 의 email 을 조회하여 인가 여부를 결정
- 토큰값이 유효하지 않은 경우 `InvalidTokenExcpetion` 발생
- 그 외 Interceptor 에서 무효 처리 (단순 false 리턴)

10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@ repositories {
mavenCentral()
}

ext {
lombokVersion = '1.18.30'
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.commons:commons-lang3'

compileOnly group: 'org.projectlombok', name: 'lombok', version : lombokVersion
annotationProcessor group: 'org.projectlombok', name: 'lombok', version : lombokVersion
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version : lombokVersion

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package nextstep.app.configuration;

import java.util.List;
import java.util.Optional;

import lombok.RequiredArgsConstructor;
import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.app.ui.AuthenticationException;
import nextstep.security.configuration.filter.BasicAuthenticationFilter;
import nextstep.security.configuration.filter.FormLoginFilter;
import nextstep.security.configuration.filter.SecurityContextHolderFilter;
import nextstep.security.model.UserDetails;
import nextstep.security.service.UserDetailsService;
import nextstep.security.service.filter.DefaultSecurityFilterChain;
import nextstep.security.service.filter.DelegateFilterProxy;
import nextstep.security.service.filter.FilterChainProxy;
import nextstep.security.service.filter.SecurityFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RequiredArgsConstructor
public class SecurityConfiguration {

private final MemberRepository memberRepository;

@Bean
public DelegateFilterProxy delegateFilterProxy() {
return new DelegateFilterProxy(filterChainProxy(List.of(securityFilterChain())));
}

@Bean
public FilterChainProxy filterChainProxy(List<SecurityFilterChain> securityFilterChains) {
return new FilterChainProxy(securityFilterChains);
}

@Bean
public SecurityFilterChain securityFilterChain() {
return new DefaultSecurityFilterChain(List.of(
new SecurityContextHolderFilter(),
new FormLoginFilter(userDetailsService()),
new BasicAuthenticationFilter(userDetailsService())
));
}

@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public Optional<UserDetails> loadUserByUsername(String username) {
Member member = memberRepository.findByEmail(username).orElseThrow(AuthenticationException::new);
return Optional.ofNullable(
UserDetails.builder().userName(member.getEmail()).password(member.getPassword()).build());
}

@Override
public Optional<UserDetails> loadUserByUsernameAndEmail(String username, String password) {
Member member =
memberRepository.findByEmailAndPassword(username, password)
.orElseThrow(AuthenticationException::new);
return Optional.ofNullable(
UserDetails.builder().userName(member.getEmail()).password(member.getPassword()).build());
}
};
}


}
24 changes: 8 additions & 16 deletions src/main/java/nextstep/app/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package nextstep.app.domain;

public class Member {
import java.io.Serializable;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class Member implements Serializable {
private final String email;
private final String password;
private final String name;
Expand All @@ -13,19 +20,4 @@ public Member(String email, String password, String name, String imageUrl) {
this.imageUrl = imageUrl;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

public String getName() {
return name;
}

public String getImageUrl() {
return imageUrl;
}
}
2 changes: 2 additions & 0 deletions src/main/java/nextstep/app/domain/MemberRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
public interface MemberRepository {
Optional<Member> findByEmail(String email);

Optional<Member> findByEmailAndPassword(String email, String password);

List<Member> findAll();

void save(Member member);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package nextstep.app.infrastructure;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import org.springframework.stereotype.Repository;

@Repository
public class InmemoryMemberRepository implements MemberRepository {
public static final Member TEST_MEMBER_1 = new Member("[email protected]", "password", "a", "");
Expand All @@ -26,6 +26,14 @@ public Optional<Member> findByEmail(String email) {
return Optional.ofNullable(members.get(email));
}

@Override
public Optional<Member> findByEmailAndPassword(String email, String password) {
return members.values()
.stream()
.filter(member -> member.getEmail().equals(email) && member.getPassword().equals(password))
.findFirst();
}

@Override
public List<Member> findAll() {
return members.values().stream().collect(Collectors.toUnmodifiableList());
Expand Down
15 changes: 3 additions & 12 deletions src/main/java/nextstep/app/ui/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
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;

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) {
public ResponseEntity<Void> login() {
return ResponseEntity.ok().build();
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<Void> handleAuthenticationException() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

}

17 changes: 11 additions & 6 deletions src/main/java/nextstep/app/ui/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package nextstep.app.ui;

import java.util.List;

import lombok.RequiredArgsConstructor;
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 java.util.List;

@RestController
@RequiredArgsConstructor
public class MemberController {

private final MemberRepository memberRepository;

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

@GetMapping("/members")
public ResponseEntity<List<Member>> list() {
List<Member> members = memberRepository.findAll();
return ResponseEntity.ok(members);
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<Void> handleAuthenticationException() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package nextstep.security.configuration.filter;

import java.io.IOException;
import java.util.List;
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 nextstep.app.ui.AuthenticationException;
import nextstep.security.model.UserDetails;
import nextstep.security.model.authentication.Authentication;
import nextstep.security.model.authentication.UsernamePasswordAuthenticationToken;
import nextstep.security.model.context.SecurityContext;
import nextstep.security.service.BasicAuthenticationService;
import nextstep.security.service.UserDetailsService;
import nextstep.security.service.authentication.AuthenticationManager;
import nextstep.security.service.authentication.DaoAuthenticationProvider;
import nextstep.security.service.authentication.ProviderManager;
import nextstep.security.service.context.SecurityContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.GenericFilterBean;

import static nextstep.security.utils.Constants.BASIC_TOKEN_PREFIX;
import static nextstep.security.utils.Constants.GET_MEMBERS_ENDPOINT_ADDRESS;

public class BasicAuthenticationFilter extends GenericFilterBean {

private final BasicAuthenticationService basicAuthenticationService = new BasicAuthenticationService();
private final AuthenticationManager authenticationManager;

public BasicAuthenticationFilter(UserDetailsService userDetailsService) {
authenticationManager = new ProviderManager(List.of(new DaoAuthenticationProvider(userDetailsService)));
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

if (!GET_MEMBERS_ENDPOINT_ADDRESS.equals(httpRequest.getRequestURI())) {
filterChain.doFilter(servletRequest, servletResponse);
return;
} else if (SecurityContextHolder.getContext().getAuthentication() != null) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}

try {
Authentication authenticationRequest = convert(httpRequest);
Authentication authenticated = authenticationManager.authenticate(authenticationRequest);

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticated);
SecurityContextHolder.setContext(context);

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

}

private Authentication convert(HttpServletRequest httpRequest) {
String authorizationHeader = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);

if (StringUtils.isEmpty(authorizationHeader) || !authorizationHeader.startsWith(BASIC_TOKEN_PREFIX)) {
throw new AuthenticationException();
}

UserDetails decodedUserDetails = basicAuthenticationService.mapTokenToUserDetails(authorizationHeader);

if (Objects.isNull(decodedUserDetails)) {
throw new AuthenticationException();
}

return UsernamePasswordAuthenticationToken.unauthenticated(
decodedUserDetails.getUserName(),
decodedUserDetails.getPassword());
}
}
Loading