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

[김세영] 프리코스 미션 제출합니다. #14

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# spring-security-authentication
## ID-PW 기반 로그인 구현
## Basic 인증 구현
## 인터셉터 분리
## 인증 로직 - 서비스 로직 간 패키지 분리:wq
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"nextstep.app", "nextstep.security"})
public class SecurityAuthenticationApplication {

public static void main(String[] args) {
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/nextstep/app/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package nextstep.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() // CSRF 비활성화 (테스트 용도)
.authorizeRequests()
.antMatchers("/login", "/members").permitAll() // /login 경로는 인증 없이 접근 허용
.anyRequest().authenticated(); // 그 외의 요청은 인증 필요
return http.build();
}
}
30 changes: 30 additions & 0 deletions src/main/java/nextstep/app/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.app.config;

import nextstep.security.interceptor.BasicAuthInterceptor;
import nextstep.security.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class WebConfig implements WebMvcConfigurer {

private final LoginInterceptor loginInterceptor;
private final BasicAuthInterceptor basicAuthInterceptor;

@Autowired
public WebConfig(LoginInterceptor loginInterceptor, BasicAuthInterceptor basicAuthInterceptor) {
this.loginInterceptor = loginInterceptor;
this.basicAuthInterceptor = basicAuthInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/login"); // ID/비밀번호 인증에 대한 경로
registry.addInterceptor(basicAuthInterceptor)
.addPathPatterns("/members"); // Basic 인증에 대한 경로
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nextstep.app.infrastructure;

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

import java.util.HashMap;
Expand Down
10 changes: 3 additions & 7 deletions src/main/java/nextstep/app/ui/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
package nextstep.app.ui;

import nextstep.app.domain.MemberRepository;
import nextstep.security.service.MemberRepository;
import nextstep.security.exception.AuthenticationException;
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() throws AuthenticationException {
return ResponseEntity.ok().build();
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/nextstep/app/ui/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nextstep.app.ui;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.security.service.MemberRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/nextstep/security/SecurityConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nextstep.security;

public class SecurityConstants {
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nextstep.app.ui;
package nextstep.security.exception;

public class AuthenticationException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nextstep.security.interceptor;

import nextstep.app.domain.Member;
import nextstep.security.service.MemberRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Optional;

import static nextstep.security.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY;

@Component
public class BasicAuthInterceptor implements HandlerInterceptor {

private final MemberRepository memberRepository;

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

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("베이직 인터셉터");
String authHeader = request.getHeader("Authorization");
if (!(authHeader != null && authHeader.startsWith("Basic "))) { // invalid header
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
String base64Credentials = authHeader.substring("Basic ".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), StandardCharsets.UTF_8);
if (!credentials.contains(":")) { // invalid header
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

final String[] values = credentials.split(":", 2);
String username = values[0];
String password = values[1];
Optional<Member> memberOptional = memberRepository.findByEmail(username);
if (memberOptional.isPresent()) {
Member member = memberOptional.get();

// 1-2. 비밀번호 확인
if (password.equals(member.getPassword())) {
// 1-3. 세션에 인증 정보 저장
HttpSession session = request.getSession();
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member);
return true;
}
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 다음으로 요청을 진행
}
}
49 changes: 49 additions & 0 deletions src/main/java/nextstep/security/interceptor/LoginInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package nextstep.security.interceptor;

import nextstep.app.domain.Member;
import nextstep.security.service.MemberRepository;
import nextstep.security.exception.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

import static nextstep.security.SecurityConstants.SPRING_SECURITY_CONTEXT_KEY;

@Component
public class LoginInterceptor implements HandlerInterceptor {

private final MemberRepository memberRepository;

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

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("로그인 인터셉터");
String username = request.getParameter("username");
String password = request.getParameter("password");

if (username == null || password == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

Member member = memberRepository.findByEmail(username)
.orElseThrow(AuthenticationException::new);

if (!password.equals(member.getPassword())) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

// 세션에 인증 정보 저장
HttpSession session = request.getSession();
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, member);

return true; // 다음으로 요청을 진행
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package nextstep.app.domain;
package nextstep.security.service;

import nextstep.app.domain.Member;

import java.util.List;
import java.util.Optional;
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/nextstep/app/MemberTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nextstep.app;

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.security.service.MemberRepository;
import nextstep.app.infrastructure.InmemoryMemberRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down