It's going to be one day 🍀

안녕하세요! 매일 매일 공부하려고 노력하는 백엔드 개발자 지망생의 공부 흔적입니다.

Back-End/Spring

[Spring] 스프링 시큐리티 블로그 만들기

2jin2 2024. 3. 19. 16:08

스프링 시큐리티 

스프링 시큐리티(Spring security)는 스프링 기반의 애플리케이션 보안(인증, 인가, 권한)을 담당하는 스프링의 하위 프레임워크임.

인증, 권한 부여, 접근 제어, 암호화, 세션관리 등 다양한 보안 기능을 제공함.

 

인증(Authentication) : 사용자의 신원을 확인하는 과정

인가(Authorization) : 사이트의 특정 부분에 접근할 수 있는지 권한을 확인하여 접근을 제어하는 과정

 

스프링 시큐리티를 사용하면 보안 관련 옵션을 제공받을 수 있고 어노테이션 설정만으로 CSRF 공격, 세션고정 공격 등 개발자가 보안 관련 개발을 해야 하는 부담을 크게 줄여준다.

 

- CSRF 공격 : 사용자의 권한을 가지고 특정 동작을 수행하도록 유도하는 공격

- 세션 고정 공격 : 사용자의 인증 정보를 탈취하거나 변조하는 공격

 

스프링 시큐리티는 서블릿 필터와 이들로 구성된 필터 체인을 사용하여 보안 기능을 구현함. 사용자의 요청이 서블릿에 도달하기 전에 스프링 시큐리티의 필터들이 요청을 검사하여 인증, 인가, 접근 제어 등의 보안 처리를 수행함.

 

ex) ID/PW 기반 폼 로그인 시도

스프링 시큐리티에서는 사용자가 입력한 ID/PW를 받아와서 인증을 진행함. 인증에 성공하면 인증된 세션을 받고, 인증에 실패하면 로그인 페이지로 리다이렉트되어 다시 시도하게 함.


회원 도메인 만들기

1. 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
  • 스프링 시큐리티를 사용하기 위한 스타터 spring-boot-starter-security를 추가
  • 타임리프에서 스프링 시큐리티를 사용하기 위한 thymeleaf-extras-springsecurity6 추가

2. 엔티티 생성

domain package에 User.java 파일을 생성하고 UserDetails 클래스를 상속하는 User 클래스를 만든다.

User가 상속 가능한 UserDetails 클래스는 스프링 시큐리티에서 사용자의 인증 정보를 담아두는 인터페이스임.


0319 오늘의 할일

<화면용 Controller>

GET /login -> login.html

GET /signup -> signup.html

로그아웃 (버튼 추가)

 

<비즈니스 로직 처리용 Controller>

POST /user -> 회원가입 정보 저장 후 로그인 페이지로 redirect

 

3. 레파지토리 만들기

스프링 데이터 JPA는 메소드 규칙에 맞춰 메소드를 선언하면 이름을 분석해 자동으로 쿼리를 생성해줌. 

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);  // email로 사용자 정보를 가져옴
}

- findByEmail 이라는 메소드이름으로 선언을 했기 때문에, By 뒤에 붙는 email 주소를 User 테이블에서 찾는 쿼리를 자동으로 생성해줌. 

- 여기서 findByEmail 메서드는 이메일에 해당하는 사용자를 찾아서 반환할 수 있고, 값이 없는 경우엔 Optional.empty() 를 반환하여 값이 없는 경우에 대비할 수 있게됨.

SELECT *
FROM users
WHERE email = #{email}

자주 사용하는 쿼리 메소드의 명명 규칙

 

4. 서비스 코드 구현

스프링 시큐리티에서 사용자의 정보를 가져오는(Service 로직) UserDetailService() 코드를 작성함.

필수로 구현해야 하는 loadUserByUsername() 메소드를 오버라이딩하여 사용자 정보를 가져오는 로직 작성.

@Service
public class UserDetailService implements UserDetailsService {
    private UserRepository userRepository;

    public UserDetailService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
                // email 정보가 없을 경우엔 IllegalArgumentException(입력값이 잘못됨) 예외 throws
                .orElseThrow(() -> new IllegalArgumentException(email));
    }
}

- 구현 이유 : spring-security에 로그인 인증을 맡기려면 우리가 UserDetails, UserDetailsSerive를 통해 User 정보를 알려줘야하기 때문이다.

 

5. 회원 가입 구현

Step 1. 사용자 정보 객체 생성

dto 패키지에 AddUserRequest 파일 작성

public class AddUserRequest {
    private String email;
    private String password;

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

Step 2. 회원 정보 추가 기능 작성

service 패키지에 UserService 파일 작성

@Service
@RequiredArgsConstructor
public class UserService {
    // controller "POST /user" -> service() 회원가입 비즈니스 로직
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder encoder;

//    public User save(AddUserRequest dto) {
//        return userRepository.save(
//                User.builder()
//                        .email(dto.getEmail())
//                        .password(encoder.encode(dto.getPassword())) // 패스워드 암호화
//                        .build()
//        );
//    }


 // 강사님 코드
    public void save(AddUserRequest request) {
        userRepository.save(new User(request.getEmail(), encoder.encode(request.getPassword())));
    }
}

-> 패스워드 저장할 때 암호화 후 데이터베이스에 저장 해야하기 때문에 BCryptPasswordEncoder를 사용해서 인코딩(암호화) 한 후에 저장함. 

 

회원 가입 컨트롤러 작성

controller 패키지에 UserController 파일 작성

@Controller
public class UserController {
    private UserService userService;

    public UserController(UserService userService)  {
        this.userService = userService;
    }
    @PostMapping("/user") // 회원가입 정보 저장 후 로그인 페이지로
    public String signup(AddUserRequest request) {
        userService.save(request); // 회원가입 저장
        return "redirect:/login"; // 회원가입 처리 후 로그인 페이지로 강제 리다이렉트
    }
}

-> 회원 가입 처리 후 로그인 페이지로 이동하기 위해 redirect 사용.

- @RequestBody는 Json 형태를 받을 때 써줌. 여기선 ㄴㄴ 

 

회원 가입, 로그인 뷰 작성

사용자가 회원 가입, 로그인 경로에 접근하면 회원 가입, 로그인 화면으로 연결해주는 컨트롤러를 작성하고 사용자가 실제로 볼 수 있는 화면 작성.

 

1. 뷰 컨트롤러 구현

@Controller
public class UserViewController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping("/signup")
    public String signup() {
        return "signup";
    }
}

- controller 패키지에 UserViewController 파일을 생성해서 뷰 파일을 연결하는 컨트롤러 생성

 

2. 뷰 작성

로그인 화면, 회원가입 화면 뷰 작성 (resources/templates 디렉토리에 작성하기)

 

로그아웃 구현

 

1. 로그아웃 기능 구현

UserController에 로그아웃 메소드 추가

@GetMapping("/logout")
// GET /logout 요청을 하면 로그아웃 담당 핸들러 SecurityContextLogoutHandler의 logout() 메소드를 호출해서 로그아웃 함.
public String logout(HttpServletRequest request, HttpServletResponse response) {
    new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
    return "redirect:/login";
}

 

2. 로그아웃 뷰 추가

블로그 글 목록 뷰 파일에 로그아웃을 위한 버튼 추가

블로그 만들기 완성!!!!


스프링 시큐리티 마무리

지금까지 구현한 내용은 스프링 시큐리티에서 가장 기본적으로 제공하는 폼 로그인 방식으로 구현한 것임.