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

[박원종/장현웅] 프리코스 미션 제출합니다. #19

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# spring-security-authentication
### 아이디와 비밀번호 기반 로그인 구현
- 아이디와 비밀번호 확인 기능
- session 에 인증정보 저장

### Basic 인증 구현
- Basic 유저 정보 추출
- 인증 기능
- session 에 인증정보 저장

### 인터셉터 분리
- LoginController에서 IdPasswordAuthInterceptor로 인증 방식 분리
- MemeberController에서 BasicAuthInterceptor로 인증 방식 분리

### 인증 로직과 서비스 로직 간의 패키지 분리
- 패키지 분리 및 리팩토링
63 changes: 63 additions & 0 deletions src/main/java/nextstep/app/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package nextstep.app.config;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.app.ui.AuthenticationException;
import nextstep.security.DefaultSecurityFilterChain;
import nextstep.security.DelegatingFilterProxy;
import nextstep.security.FilterChainProxy;
import nextstep.security.SecurityFilterChain;
import nextstep.security.filter.BasicAuthenticationFilter;
import nextstep.security.filter.UsernamePasswordAuthenticationFilter;
import nextstep.security.model.UserDetails;
import nextstep.security.service.UserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SecurityConfig {
private final MemberRepository memberRepository;

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


@Bean
public DelegatingFilterProxy delegatingFilterProxy() {
return new DelegatingFilterProxy(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 BasicAuthenticationFilter(userDetailService()),
new UsernamePasswordAuthenticationFilter(userDetailService())
));
}
@Bean
public UserDetailsService userDetailService() {
return username -> {
Member member = memberRepository.findByEmail(username)
.orElseThrow(() -> new AuthenticationException("존재하지 않는 사용자입니다."));

return new UserDetails() {
@Override
public String getUsername() {
return member.getEmail();
}

@Override
public String getPassword() {
return member.getPassword();
}
};
};
}
}
3 changes: 2 additions & 1 deletion src/main/java/nextstep/app/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nextstep.app.domain;

public class Member {

public class Member{
private final String email;
private final String password;
private final String name;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/nextstep/app/ui/AuthenticationException.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
package nextstep.app.ui;

public class AuthenticationException extends RuntimeException {
public AuthenticationException() {
super("인증에 실패하셨습니다.");
}

public AuthenticationException(String message) {
super(message);
}
}
11 changes: 1 addition & 10 deletions src/main/java/nextstep/app/ui/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
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();
}

Expand Down
8 changes: 5 additions & 3 deletions src/main/java/nextstep/app/ui/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@
import org.springframework.http.ResponseEntity;
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) {
this.memberRepository = memberRepository;
}

@GetMapping("/members")
public ResponseEntity<List<Member>> list() {
public ResponseEntity<List<Member>> list(HttpServletRequest request, HttpSession session) {
List<Member> members = memberRepository.findAll();

return ResponseEntity.ok(members);
}

}
23 changes: 23 additions & 0 deletions src/main/java/nextstep/security/DefaultSecurityFilterChain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nextstep.security;

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

public class DefaultSecurityFilterChain implements SecurityFilterChain{
private final List<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;
}
}
19 changes: 19 additions & 0 deletions src/main/java/nextstep/security/DelegatingFilterProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package nextstep.security;

import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.*;
import java.io.IOException;

public class DelegatingFilterProxy extends GenericFilterBean {
private final Filter delegate;

public DelegatingFilterProxy(Filter delegate) {
this.delegate = delegate;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
delegate.doFilter(servletRequest, servletResponse, filterChain);
}
}
54 changes: 54 additions & 0 deletions src/main/java/nextstep/security/FilterChainProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package nextstep.security;

import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;

public class FilterChainProxy extends GenericFilterBean {
private List<SecurityFilterChain> filterChains;

public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
List<Filter> filters = getFilters((HttpServletRequest) servletRequest);
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(filterChain, filters);
virtualFilterChain.doFilter(servletRequest, servletResponse);
}

private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain securityFilterChain : filterChains) {
if (securityFilterChain.matches(request)) {
return securityFilterChain.getFilters();
}
}
return null;
}

private static final class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final int size;
private int currentPosition = 0;
private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
this.originalChain.doFilter(request, response);
return;
}
this.currentPosition++;
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
nextFilter.doFilter(request, response, this);
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/nextstep/security/SecurityFilterChain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package nextstep.security;

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

public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.security.authentication;

public interface Authentication {
Object getCredentials(); // token password
Object getPrincipal(); /// token userName
boolean isAuthenticated();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.security.authentication;

public class AuthenticationException extends RuntimeException {
public AuthenticationException() {
super("인증에 실패하였습니다.");
}

public AuthenticationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nextstep.security.authentication;

public interface AuthenticationManager {
Authentication authenticate(Authentication authentication);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.security.authentication;

import nextstep.app.ui.AuthenticationException;

public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;

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


import nextstep.security.model.UserDetails;
import nextstep.security.service.UserDetailsService;

import java.util.Objects;

public class DaoAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;

public DaoAuthenticationProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
if (!Objects.equals(userDetails.getPassword(), authentication.getCredentials())) {
throw new AuthenticationException();
}
return UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword());
}

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

import java.util.List;

public class ProviderManager implements AuthenticationManager {
private final List<AuthenticationProvider> providers;

public ProviderManager(List<AuthenticationProvider> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.security.authentication;

public class UsernamePasswordAuthenticationToken implements Authentication {

private final Object principal;
private final Object credentials;
private final boolean authenticated;

private UsernamePasswordAuthenticationToken(Object principal, Object credentials, boolean authenticated) {
this.principal = principal;
this.credentials = credentials;
this.authenticated = authenticated;
}

public static UsernamePasswordAuthenticationToken unauthenticated(String principal, String credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials, false);
}


public static UsernamePasswordAuthenticationToken authenticated(String principal, String credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials, true);
}

@Override
public Object getCredentials() {
return credentials;
}

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

@Override
public boolean isAuthenticated() {
return authenticated;
}
}
Loading