[SPRING] WIL - 8

진규빈·2024년 12월 21일
0

스프링 WIL

목록 보기
8/8

1. 스터디 복습

Spring Security

Spring 애플리케이션의 인증과 인가를 담당하는 강력한 보안 프레임워크

인증(Authentication)
식별 가능한 정보 이용하여 본인 확인
<Credential 방식>
principal => 아이디 username
credential => 비밀번호 password
인가(Authorization)
사용자가 특정 리소스에 접근할 권한 있는지 확인

Spring Security 특징

◦ Filter를 기반으로 동작
◦ OAuth 2.0 또는 JWT(JSON Web Tokens) 활용 시 Spring MVC와 분리되어 관리 & 동작
◦ 모든 보안 설정을 Java Config에서 Bean으로 설정 가능
(주요 설정은 SecurityFilterChain 및 AuthenticationManager로 관리)
◦ Spring Security 3.2부터 XML 설정할 필요 없이 어노테이션 활용
◦ 기본적으로 세션 & 쿠키 방식으로 인증
(REST API에서는 OAuth 2.0 또는 JWT 같은 토큰 기반 인증 방식 사용)
◦ 인증관리자(Authentication Manager) & 접근 결정 관리자(Access Decision Manager) 통해 사용자의 리소스 접근 관리

Spring Security 주요 기능

Spring Security 아키텍처

1) Http Request 수신
사용자가 로그인 정보와 함께 인증 요청

2) 유저 자격 기반으로 인증토큰 생성
AuthenticationFilter가 요청 가로채고 가로챈 정보 통해 인증용 객체 생성

3) Filter 통해 AuthenticationToken을 AuthenticationManager로 위임

4) AuthenticationProvider의 목록으로 인증 시도
AuthenticationManager는 등록된 AuthenticationProvider들 조회하며 인증 요구

5) UserDetailsService의 요구
실제 DB에서 사용자 인증정보 가져오는 UserDetailsService에 사용자 정보 전달

6) UserDetails 이용해 User 객체에 대한 정보 탐색
넘겨받은 사용자 정보 통해 DB에서 찾아낸 사용자 정보인 UserDetails 객체 생성

7) User 객체의 정보들을 UserDetails가 UserDetailsService로 전달, AuthenticationProvider들은 UserDetails 넘겨받고 사용자 정보 비교

8) AuthenticationException 인증 완료 되면 권한 등 사용자 정보 담은 Authentication 객체 반환

9) 인증 끝! 다시 최초의 AuthenticationFilter에 Authentication 객체 반환

10) SecurityContext에 인증 객체 설정
Authentication 객체를 Security Context에 저장

최종적으로,
SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체 저장
** 사용자 정보를 저장한다는 것은 Spring Security가 전통적인 세션-쿠키 기반의 인증 방식 사용한다는 것 의미

SecurityContextHolder & SecurityContext & Authentication
: 스프링 시큐리티의 주요 컴포넌트

인증 성공 시 사용자의 principal과 credential 정보를 Authentication 안에 담음
=> 방금 담은 Authentication을 SecurityContext에 보관
=> 이 SecurityContext를 SecurityContextHolder에 담아 보관


RBAC(Role Based Access Control) 역할 기반 접근 제어

정보에 대한 접근 권한을 역할에 따라 결정

<장점>
• 보안 강화 & 데이터 보호
• 그룹화 통해 권한 관리 간소화
• 다수의 사람에 대해 유연하고 직관적이며 효율적인 통제 가능

<기본 규칙>
▶ Role Assignment 역할 할당
▶ Role Authorization 역할 기반 권한 부여
▶ Permission Authorization 권한 승인

Spring Security에서 RBAC 구현

1) 사용자(User) 및 역할(Role) 데이터 모델 설계
<테이블 구조>
users 테이블 - 사용자 정보 저장

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(255) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    enabled BOOLEAN NOT NULL
);

roles 테이블 - 역할 정보 저장

CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) UNIQUE NOT NULL
);

user_roles 테이블 - 사용자와 역할 간의 관계 저장

CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

2) Spring Security 설정
build.gradle에 Spring Security 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-core'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'

사용자 정보와 역할 매핑
사용자 인증 및 권한 확인 위해 UserDetails & GrantedAuthority 구현

User 엔티티

import jakarta.persistence.*;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.HashSet;
import java.util.Set;

@Entity
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private boolean enabled;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();

    // Getters and setters

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
            .map(role -> new SimpleGrantedAuthority(role.getName()))
            .collect(Collectors.toSet());
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
}

Role 엔티티

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getters and setters
}

UserDetailsService 구현
Spring Security가 사용자 정보 로드

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

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

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
    }
}

SecurityConfig 설정 - 인증 및 권한 관리 구성

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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 {

    private final CustomUserDetailsService userDetailsService;

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin()
            .and()
            .logout();
        return http.build();
    }

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder())
            .and()
            .build();
    }

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

Method Level Security 메소드 수준 보안

RBAC를 서비스 계층으로 확장한 방식으로, 서비스 계층에서 세밀한 권한 관리

<주요 어노테이션>
@PreAuthorize 메소드 실행 전 권한 체크
@PostAuthorize 메소드 실행 후 반환값에 따라 접근 제어
@Secured 특정 역할 기반으로 접근 제어
@RolesAllowed JSR-250 표준 기반 접근 제어

<예시 코드>

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
	// 메소드 레벨 보안 설정
}
@Service
public class UserService {

    // ADMIN 역할을 가진 사용자만 접근 가능
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    // 특정 사용자 ID만 접근 가능
    @PreAuthorize("#userId == authentication.principal.id")
    public User getUserById(Long userId) {
        return userRepository.findById(userId).orElseThrow();
    }
}
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // ADMIN 역할을 가진 사용자만 접근 가능
    @Secured("ROLE_ADMIN")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    // 여러 역할 허용
    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void updateUser(User user) {
        userRepository.save(user);
    }
}
import jakarta.annotation.security.RolesAllowed;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // ADMIN 역할을 가진 사용자만 접근 가능
    @RolesAllowed("ROLE_ADMIN")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    // 여러 역할 허용
    @RolesAllowed({"ROLE_ADMIN", "ROLE_USER"})
    public User getUserDetails(Long userId) {
        return userRepository.findById(userId).orElseThrow();
    }
}

ACL(Access Control List) 액세스 제어 목록

개별 리소스에 대한 접근 권한 세분화하여 정의 및 관리

<장점>
• 리소스 단위의 권한 관리
=> 리소스(도메인 객체)에 사용자/그룹별 권한 부여
• hasPermission() 통해 접근성 관리 DB화
(Spring Security는 spring-security-acl 라이브러리 제공)

<단점>
• Domain 객체가 갖는 접근성에 대한 정보 활용 X => ACL 테이블 별도로 관리
• 사용자/도메인 객체 많아지면 접근 권한에 대한 경우의 수가 기하급수적으로 증가
• 기술 난이도 높아 유지보수 어려움

기본 구조

▶ Sid(Security Identity) – 사용자/그룹
▶ Permission – 객체에 대한 권한
▶ Domain Object – 보호할 객체

ACL 도메인 모델


ACL_CLASS 도메인 객체의 종류
ACL_SID 권한의 주체가 되는 사용자 정보 & Role 정보
ACL_OBJECT_IDENTITY 보안의 대상이 되는 정보 객체
ACL_ENTRY 접근 권한 정보


OAuth 2.0

인터넷 사용자가 애플리케이션에 직접 비밀번호 제공하지 않고 권한 위임하여 리소스에 접근하도록 하는 프로토콜
◦ 개방형 표준 프로토콜로, third-party 프로그램에게 리소스 소유자를 대신해 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식으로 작동
◦ third-party 프로그램(구글, 카카오 등)에게 로그인 및 개인정보 관리에 대한 권한 위임하여 third-party 프로그램이 가지고 있는 사용자에 대한 리소스 조회 가능

OAuth 2.0 주요 구성 요소

OAuth 2.0 인증 방식

<Authorization Code Grant 권한 부여 승인 코드 방식>

◦ 기본이 되는 방식
◦ 간편 로그인 기능에서 사용
◦ 권한 부여 승인을 위해 자체 생성한 Authorization Code를 전달하는 방식으로 많이 쓰임

<Implicit Grant 암묵적 승인 방식>

◦ 자격증명 안전하게 저장하기 힘든 클라이언트(ex. 자바스크립트 등의 스크립트 언어 사용한 브라우저)에게 최적화된 방식
◦ 권한 부여 승인 코드 없이 바로 Access Token 발급
◦ 응답성과 효율성은 높아지지만 Access Token이 URL로 전달된다는 단점 존재

<Resource Owner Password Credentials Grant 자원 소유자 자격증명 승인 방식>

◦ username, password로 Access_Token 받는 방식
◦ 권한 서버, 리소스 서버, 클라이언트가 모두 같은 시스템에 속해 있을 때 사용

<Client Credentials Grant 클라이언트 자격증명 승인 방식>

◦ 클라이언트의 자격증명만으로 Access Token 획득하는 가장 간단한 방식
◦ 자신이 관리하는 리소스 혹은 권한 서버에 해당 클라이언트 위한 제한된 리소스 접근 권한이 설정되어 있는 경우 사용

Spring Security & OAuth 2.0 통합

1) application.yml / application.properties 파일에 Oauth 2.0 클라이언트 설정 추가
2) spring-boot-starter-oauth2-resource-server 의존성 프로젝트에 추가
3) SecurityConfig 클래스에서 리소스 서버의 보안 구성 정의

<장점>
• 복잡한 인증 및 인가 요구사항 효과적으로 관리 가능
• Oauth 2.0 => 사용자 데이터에 대한 안전한 접근 권한 부여 가능
• Spring Security => 이러한 프로세스 간소화하여 구현할 수 있는 풍부한 기능 제공
• 개발자는 보안 강화된 애플리케이션 쉽고 빠르게 구축 가능

2. 과제



구글 클라우드 가입해서 무료 체험 시작
머신 구성하고 VM 인스턴스 생성


방화벽 규칙 설정

# 시스템 업데이트
sudo apt update && sudo apt upgrade -y

# Java 설치
sudo apt install openjdk-17-jdk -y

# Git 설치
sudo apt install git -y

필수인지는 잘 모르겠지만 SSH 콘솔에서 요것들 설정해줌

intellij에서 Google Cloud code 플러그인 설치

구글 클라우드 계정 로그인하여 연결

윈도우 터미널 열어서 SSH 키 쌍 생성

Compute Engine의 메타데이터 들어가서 SSH 키 저장


intellij에서 프로젝트 연결 및 서버 추가


호스트, 사용자 이름, 키 쌍, 비밀번호까지 입력하고 연결 완료

jar 파일 생성하여 gcp/home/eileen0379 안으로 드래그

jar 파일 통해 자바 애플리케이션 빌드
.
.
.
성공한 줄 알았으나,

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.gdg.library.service.BookService required a bean named 'entityManagerFactory' that could not be found.

쭉 내려보니 이런 식의 오류 발생하면서 빌드 실패
코드 수정해보고 구글링 해보고 지피티한테 물어보고 고정 IP 주소 예약해보는 등 이것저것 엄청 바꿔보았는데도 같은 문제가 지속됨ㅜ
.
.
.
그러다가 언제부터인지 모르겠지만 gcp 외부 IP가 달라진 것 발견
변경된 외부 IP로 SSH 구성에서부터 다시 시도했더니 드디어

빌드 성공!!!

postman 들어가서 지난번에 저장해놨던 API 열고 url 수정
도서 POST 성공

방금 전송한 도서 배열 확인


멤버 POST 성공


도서 대출 및 반납도 성공

<간단 회고>
◦ 내가 잘 한 게 맞나 싶지만.. 그래도 배포 성공하고 문제도 해결된 것 같아서 뿌듯하다
◦ 시간 날 때 간단한 프론트 화면 만들어서 API 연결해보면 더 와닿고 재밌을 것 같다

+)
>>요 사이트 참고 많이 함<<

0개의 댓글