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

[이준하] 프리코스 미션 제출합니다. #9

Open
wants to merge 7 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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# spring-security-authentication

## 구현 기능 목록
* refactor: formLoginAuthInterceptor
* feat: 로그인 기능 구현
* feat: 인증 검사 인터셉터 추가
* feat: basic 인증 인터셉터 추가
48 changes: 48 additions & 0 deletions src/main/java/nextstep/app/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nextstep.app.config;

import nextstep.security.DefaultSecurityFilterChain;
import nextstep.security.filter.AuthorizationFilter;
import nextstep.security.filter.BasicAuthenticationFilter;
import nextstep.security.filter.FilterChainProxy;
import nextstep.security.filter.FormLoginAuthFilter;
import nextstep.security.manager.ProviderManager;
import nextstep.security.provider.UsernamePasswordProvider;
import nextstep.security.service.UserDetailsService;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.GenericFilterBean;

import java.util.List;

@Configuration
public class SecurityConfig {

private final UserDetailsService userDetailsService;

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

@Bean
public GenericFilterBean delegatingFilterProxy() {
return new FilterChainProxy(List.of(
new DefaultSecurityFilterChain("/login", List.of(
new FormLoginAuthFilter(new ProviderManager(List.of(new UsernamePasswordProvider(userDetailsService)))),
new AuthorizationFilter()
)),
new DefaultSecurityFilterChain("/members", List.of(
new BasicAuthenticationFilter(new ProviderManager(List.of(new UsernamePasswordProvider(userDetailsService)))),
new AuthorizationFilter()
))
));
}

@Bean
public FilterRegistrationBean<GenericFilterBean> delegatingFilterProxyFilterRegistrationBean(GenericFilterBean delegatingFilterProxy) {
FilterRegistrationBean<GenericFilterBean> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(delegatingFilterProxy);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
25 changes: 25 additions & 0 deletions src/main/java/nextstep/app/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package nextstep.app.config;

import nextstep.security.interceptor.AuthorizationInterceptor;
import nextstep.security.interceptor.BasicAuthenticationInterceptor;
import nextstep.security.interceptor.FormLoginAuthInterceptor;
import nextstep.security.service.UserDetailsService;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//@Configuration
public class WebConfig implements WebMvcConfigurer {

private final UserDetailsService userDetailsService;

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

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new BasicAuthenticationInterceptor(userDetailsService));
registry.addInterceptor(new FormLoginAuthInterceptor(userDetailsService)).addPathPatterns("/login");
registry.addInterceptor(new AuthorizationInterceptor()).addPathPatterns("/members");
}
}
6 changes: 5 additions & 1 deletion src/main/java/nextstep/app/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package nextstep.app.domain;

public class Member {
import nextstep.security.userdetails.UserDetails;

public class Member implements UserDetails {
private final String email;
private final String password;
private final String name;
Expand All @@ -13,10 +15,12 @@ public Member(String email, String password, String name, String imageUrl) {
this.imageUrl = imageUrl;
}

@Override
public String getEmail() {
return email;
}

@Override
public String getPassword() {
return password;
}
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/nextstep/app/domain/MemberService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.app.domain;

import nextstep.security.service.UserDetailsService;
import nextstep.security.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
public class MemberService implements UserDetailsService {

private final MemberRepository memberRepository;

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

@Override
public UserDetails loadUserByEmailAndPassword(String email, String password) {
return memberRepository.findByEmail(email)
.filter(v -> v.getPassword().equals(password))
.orElse(null);
}
}
7 changes: 0 additions & 7 deletions src/main/java/nextstep/app/ui/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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;
Expand All @@ -14,12 +13,6 @@
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();
Expand Down
1 change: 0 additions & 1 deletion src/main/java/nextstep/app/ui/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,4 @@ public ResponseEntity<List<Member>> list() {
List<Member> members = memberRepository.findAll();
return ResponseEntity.ok(members);
}

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

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

public class DefaultSecurityFilterChain implements SecurityFilterChain {

private final String path;
private final List<Filter> filters;

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

@Override
public boolean matches(HttpServletRequest request) {
return request.getRequestURI().startsWith(path);
}

@Override
public List<Filter> getFilters() {
return filters;
}
}
12 changes: 12 additions & 0 deletions src/main/java/nextstep/security/SecurityFilterChain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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,6 @@
package nextstep.security.authentication;

public interface Authentication {
Object getCredentials();
Object getPrincipal();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nextstep.security.authentication;

public class UsernamePasswordAuthenticationToken implements Authentication {
private final Object principal;
private final Object credentials;

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
this.principal = principal;
this.credentials = credentials;
}

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

@Override
public Object getPrincipal() {
return principal;
}
}
28 changes: 28 additions & 0 deletions src/main/java/nextstep/security/context/UserContextHolder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nextstep.security.context;

import nextstep.security.userdetails.UserDetails;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import static nextstep.security.interceptor.BasicAuthenticationInterceptor.SPRING_SECURITY_CONTEXT;

public final class UserContextHolder {

private UserContextHolder() {}

public static UserDetails getUser() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return null;
}
return (UserDetails) attributes.getAttribute(SPRING_SECURITY_CONTEXT, RequestAttributes.SCOPE_SESSION);
}

public static void setUser(UserDetails userDetails) {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
attributes.setAttribute(SPRING_SECURITY_CONTEXT, userDetails, RequestAttributes.SCOPE_SESSION);
}
}
23 changes: 23 additions & 0 deletions src/main/java/nextstep/security/filter/AuthorizationFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nextstep.security.filter;

import nextstep.security.context.UserContextHolder;
import nextstep.security.userdetails.UserDetails;
import org.springframework.http.HttpStatus;

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

public class AuthorizationFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
UserDetails userDetails = UserContextHolder.getUser();
if (userDetails == null) {
((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}

chain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package nextstep.security.filter;

import nextstep.security.authentication.Authentication;
import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
import nextstep.security.context.UserContextHolder;
import nextstep.security.manager.AuthenticationManager;
import nextstep.security.userdetails.UserDetails;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class BasicAuthenticationFilter implements Filter {

public static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final String BASIC = "Basic";
private static final String BLANK = " ";
private static final String COLON = ":";

private final AuthenticationManager authenticationManager;

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

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String[] credentials = parseBasicHeader((HttpServletRequest) request);
if (credentials == null) {
chain.doFilter(request, response);
return;
}

Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(credentials[0], credentials[1]));
if (authentication != null) {
UserContextHolder.setUser((UserDetails) authentication.getPrincipal());
}
chain.doFilter(request, response);
}

@Nullable
private String[] parseBasicHeader(HttpServletRequest request) {
// authorization 헤더를 조회한다.
String authorizationValue = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationValue == null || authorizationValue.isBlank()) {
return null;
}

// Basic authentication 방식인지 확인한다.
String[] strs = authorizationValue.split(BLANK);
if (strs.length != 2) {
return null;
}

String authType = strs[0];
String authValue = strs[1];

// Basic authentication 방식이 아닌 경우 null을 반환한다.
if (!BASIC.equalsIgnoreCase(authType)) {
return null;
}

String decodedBasicValue = new String(Base64.getDecoder().decode(authValue), StandardCharsets.UTF_8);
if (decodedBasicValue.isBlank()) {
return null;
}

strs = decodedBasicValue.split(COLON);
if (strs.length != 2) {
return null;
}

return new String[]{strs[0], strs[1]};
}
}
Loading