Sparta/What I Learned

23.1.15

코딩하는 또롱이 2023. 1. 15. 22:11

 

SQL

 

❓인덱스란?

  • 데이터를 빠르게 찾을 수 있도록 도와주는 도구. 각 열마다 붙일 수 있다.
  • 인덱스를 붙이면 균형 트리(balanced tree) 노드가 생성되어 빠른 탐색이 가능해진다. 열번 데이터를 읽어서 찾던 것을 2, 3번만에 찾는다.
  • 인덱스가 없다면 순차적으로 읽어서 조건에 맞는 데이터를 가져오므로 뒷쪽에 위치한 데이터를 찾게 될 때 시간이 많이 걸리게 된다.

 

✅  인덱스의 장점⭕과 단점✖️

  • ⭕ SELECT 문으로 검색하는 속도가 매우 빨라집니다.
  • ✖️ 인덱스도 공간을 차지해서 데이터베이스 안에 추가적인 공간이 필요합니다.
  • ✖️ 데이터 변경 작업(INSERT, UPDATE, DELETE) 가 자주 일어나면 오히려 성능이 나빠질 수 있습니다.

 

✅인덱스의 종류(💟자동 & 💫수동)

 

💟 자동으로 생성되는 인덱스

  • 클러스터형 인덱스: 어떤 열을 기본 키(Primary key)로 설정하면 자동으로 클러스터형 인덱스가 생성된다. 데이터가 정렬 되어 있음. 영어사전
  • 보조 인덱스: 어떤 열을 고유키(Unique)로 설정하면 자동으로 보조 인덱스가 생성다. 데이터 정렬은 하지 않음. 책 뒷부분의 색인

❣️ 실습

create database index_test_db;
use index_test_db;

create table table1(
        col1 int primary key,
        col2 int unique,
        col3 int
);

-- 인덱스 확인
show index from table1;
drop table if exists member;
create table member(
        mem_id char(8),
        mem_name varchar(10),
        mem_number int, addr char(2)
);

show index from member;

insert into member values('TWC', ' 트와이스', 9, '서울');
insert into member values('BLK', ' 블랙핑크', 5, '경남');
insert into member values('WMN', ' 여자친구', 6, '경기');
insert into member values('OMG', ' 오마이걸', 7, '서울');

-- 데이터 순서 확인
select * from member;

-- 클러스터
alter table member
        add constraint
        primary key (mem_id);

Key에 PRI로 지정되어 있음을 확인 할 수 있다.

 

인덱스 확인 완료!
분명 우리는 12~15 순으로 인서트해서 보였었는데  index를 지정해주고 나니까 mem_id가 알파벳순으로 바뀌었음을 확인 할 수 있다.


alter table member
        add constraint
        unique (mem_id);

👉 alter는 테이블의 속성을 바꾸고, 제약사항도 , 값의 속성도 전부 바꿀 수 있다.

 

💫 사용자가 직접 생성하는 인덱스

  • 사용자가 생성하는 인덱스도 보조 인덱스이다.
  • 인덱스 생성
CREATE [UNIQUE] INDEX 인덱스_이름
	ON 테이블_이름 (열_이름) [ASC | DESC];
  • 인덱스 확인
SHOW INDEX 테이블_이름;
  • 인덱스의 적용
ANALYZE TABLE 테이블_이름;
  • 인덱스 제거
DROP INDEX 인덱스_이름 ON 테이블_이름;

 

❣️ 실습

use market_db;

select * from member;

show index from member;

5번 실행



 -- 인덱스 직접 생성
create index idx_member_addr on member(addr);
create unique index idx_member_mem_name on member (mem_name);

analyze table member;

8~11 실행
정상적으로 인덱스가 들어가있음을 확인할 수 있다.



 -- excution plan 확인
select * from member;

이 부분을 확인해 보면 이렇게 나오는데 우리가 인덱스를 쓰질 않았기 때문에 그냥 출력이 된 것이다.


select mem_id, mem_name, addr from member;

.
여전히 풀테이블 스캔이 나온다. 인덱스가 있는 열을 선택하는 게 인덱스를 쓰는게 아님을 알 수 있다.


select mem_id, mem_name, addr from member where mem_name = '에이핑크';

인덱스 적용이 잘 되었음을 알 수 있다.

✅ 인덱스는 where로 데이터를 찾아낼 때 쓰이는 것을 알 수 있다.

show index from member;

 -- 인덱스 삭제
drop index idx_member_addr on member;
drop index idx_member_mem_name on member;

1번 실행
2,3번 실행


자동생성된 클러스터형 인덱스를 제외하고 전부 인덱스가 제거 되었다.

💡 자동으로 생성된 인덱스는 위 명령으로 제거할 수 없다. 테이블 구조를 변경하는 ALTER TABLE 문으로 기본키나 고유키를 제거하면 자동으로 인덱스도 제거할 수 있다.

 

 

✅ 인덱스를 효과적으로 사용하는 방법

  • WHERE 절에서 사용되는 열에 인덱스를 만들어야한다.
  • 데이터 변경 작업보다 SELECT 문을 훨씬 자주 쓰는 열에 인덱스를 만들어야 한다.
  • 데이터의 중복이 높은 열은 인덱스를 만들어도 별 효과가 없다.
  • 사용하지 않는 인덱스는 제거한다.

 

 


 

스프링 시큐리티 심화

[build.gradle]에 추가하기

implementation 'org.springframework.boot:spring-boot-starter-security'

 

 ❓ CSRF 설정이란?

  • CSRF (Cross-site request forgery) : 사이트 간 요청 위조
    • 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것
    • CSRF 설정이 되어있는 경우 html 에서 CSRF 토큰 값을 넘겨주어야 요청을 수신 가능
    • 쿠키 기반의 취약점을 이용한 공격 이기 때문에 REST 방식의 API에서는 disable 가능
    • POST 요청마다 처리해 주는 대신 CSRF protection 을 disable

[WebSecurityConfig]CSRF  설정을 적용하기 전

더보기
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                // image 폴더를 login 없이 허용
                .antMatchers("/images/**").permitAll()
                // css 폴더를 login 없이 허용
                .antMatchers("/css/**").permitAll()
                // 어떤 요청이든 '인증'
                .anyRequest().authenticated()
                .and()
                // 로그인 기능 허용
                .formLogin()
                .loginPage("/user/login")
                .defaultSuccessUrl("/")
                .failureUrl("/user/login?error")
                .permitAll()
                .and()
                // 로그아웃 기능 허용
                .logout()
                .permitAll();
    }
}

 

[참고링크]

https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

 

Spring Security without the WebSecurityConfigurerAdapter

<p>In Spring Security 5.7.0-M2 we <a href="https://github.com/spring-projects/spring-security/issues/10822">deprecated</a> the <code>WebSecurityConfigurerAdapter</code>, as we encourage users to move towards a component-based security configuration.</p> <p

spring.io

[WebSecurityConfig] CSRF 적용하고 나서 코드

더보기

[Spring Boot 2.7 이상]

package com.sparta.myselectshop.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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();

        http.authorizeRequests().anyRequest().authenticated();

        // 로그인 사용
        http.formLogin();

        return http.build();
    }

}

[Sprng Boot 3.0 이상] 나는 여기에 해당!

2.7이랑 다른 점 : authorizeRequests() authorizeHttpRequests() 

package com.sparta.springsecurity.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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();

        http.authorizeHttpRequests().anyRequest().authenticated();

        // 로그인 사용
        http.formLogin();

        return http.build();
    }

}

 

👉 Spring Security 와 Filter

내가 이해하기로는 클라이언트가 요청을 넣게 되고 그 것들이 시큐리티 쪽을 거치게게 되면 필터 체인에 적용된 올바른 url이 아니면 걸러준다는 거 같다.

Spring Security는 요청이 들어오면 Servlet FilterChain을 자동으로 구성한 후 거치게 한다. FilterChain은 여러 Filter를 chain형태로 묶어놓은 것을 의미합니다.

여기서 Filter 란❓
톰캣과 같은 웹 컨테이너에서 관리되는 서블릿의 기술이다. Filter는 Client 요청이 전달되기 전후의 URL 패턴에 맞는 모든 요청에 필터링을 해준다. CSRF, XSS 등의 보안 검사를 통해 올바른 요청이 아닐 경우 이를 차단해 준다. 따라서 Spring Security는 이런한 기능을 활용하기위해 Filter를 사용하여 인증/인가를 구현하고 있다.

 

👉 SecurityFilterChain

말 그대로 보안 방식을 사용하는데 필요한 설정을 분리한 것

Spring 의 보안 Filter를 결정하는데 사용되는 Filter session, jwt 등의 인증 방식들을 사용하는데에 필요한 설정을 완전히 분리할 수 있는 환경을 제공한다.

 

👉 여러가지 필터들 중에서 내가 사용할 것은 AbstractAuthenticationProcessingFilter

 : 사용자의 credential을 인증하기 위한 베이스 Filter

 

👉 위의 Abstract어쩌고를 상속한 UsernamePasswordAuthenticationFilter

: 기본적으로 아래와 같은 Form Login 기반을 사용할 때 username 과 password 확인하여 인증.

 

Form Login 기반 ❓

인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인페이지를 반환하는 형태.

 

 

👉  SecurityContextHolder

: 스프링 시큐리티로 인증을 한 사용자의 상세 정보를 저장.

  • SecurityContext 란? SecurityContextHolder 로 접근할 수 있으며 Authentication 객체를 가지고 있다.
// 예시코드
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);

 

👉 Authentication

  • 현재 인증된 사용자를 나타내며 SecurityContext 에서 가져올 수 있다.
  • principal : 사용자를 식별한다. Username/Password 방식으로 인증할 때 보통 UserDetails 인스턴스다.
  • credentials : 주로 비밀번호, 대부분 사용자 인증에 사용하고 다음 비운다.
  • authorities : 사용자에게 부여한 권한을 GrantedAuthority 로 추상화하여 사용한다
<UserDetails>
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRoleEnum role = user.getRole();
        String authority = role.getAuthority();
        System.out.println("authority = " + authority);

        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(simpleGrantedAuthority);

        return authorities;
        }

        Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        // UsernamePasswordAuthenticationToken는 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, 인증객체를 만드는데 사용된다.
👉 UserDetailsService
username/password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환
Custom하여 Bean으로 등록 후 사용 가능하다.

👉 UserDetails
검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication를 만들 때 사용되며 해당 인증 객체는 SecurityContextHolder에 세팅된다. Custom하여 사용가능하다.

👉 WebSecurityConfig 
h2-console 사용 및 resources 접근 허용 설정하기spring secuity 지원CSRF 설정security 내의 로그인 사용
(기본적으로 session 방법으로 로그인 됨)

👉 UserDetailsImpl
UserDetailsServiceImpl을 통해 갖고온 user의 정보를 활용해서 만든 객체

👉 UserDetailsServiceImpl
사용자를 DB에서 조회하고 인증해서 검증하는 부분user 객체와, DB에서 가져온 이름, 비번을 담아서 UserDetailsImpl로 반환시켜서 인증 객체 만든다

 

🚨 비밀번호 암호화 이해하고 적용하기

: 회원 등록 시 '비밀번호' 는 사용자가 입력한 문자 그대로 DB 에 등록하면 안 된다. '정보통신망법, 개인정보보호법' 에 의해 비밀번호 암호화(Encryption)가 의무이다.

 

[👇 비밀번호 저장의 역사]

더보기

https://docs.spring.io/spring-security/reference/6.0.0-RC1/features/authentication/password-storage.html#authentication-password-storage-history

 

비밀번호 저장의 역사 요약

1. 암호화된 패스워드를 가져가도 복호화 할 수 없도록 단방향 해시를 적용하여 비밀번호를 암호화하는 것이 관행

2. 레인보우 테이블과 같은 룩업 테이블을 만들어서 비밀번호를 미리 계산해서 저장하여 사용하는 해킹 방식이 등장

3. 개발자들은 해결책으로 솔티드 패스워드를 사용, 이는 단순하게 해시 함수를 입력하는 것이 아니라 모든 사용자의 비밀번호를 랜덤 바이트를 만들어 비밀번호와 함께 사용하여 암호화해서 유니크한 해시값을 생성하여 사용

4. 하지만 하드웨어의 발전으로 모든 비밀번호를 쉽게 해독할 수 있게 됨

5. 현재는 적응형 단방향 함수로 비밀번호를 저장하는 것을 추천

6. Spring Security 는 적응형 단방향 함수를 쉽게 사용할 수 있도록 미리 만들어 제공

 

결론!

우리는 Spring Security 가 제공하는 적응형 단방향 함수인 bCrypt를 사용하여 비밀번호를 암호화한다!

 

❗️적응형 단방향 함수는 내부적으로 리소스의 낭비가 매우 심하기 때문에 API 요청 마다 사용자의 이름과 비밀번호를 검증하면 애플리케이션 성능이 크게 떨어질 수 있다. 따라서 세션, 토큰 과 같은 인증방식을 사용하여 검증하는 것이 속도 및 보안 측면에 유리하다.

 

❗️우리가 앞으로 프로젝트에 적용할 사용자 검증 흐름 짚고 넘어가기!

1. 사용자는 회원가입을 진행한다.

2. 사용자의 정보를 저장할 때 비밀번호를 암호화하여 저장한다.

3. 사용자는 로그인을 진행한다.

4. 사용자가 입력한 정보를 통해 저장된 암호화된 비밀번호를 가져와 사용자가 입력한 암호와 비교한다.

5. 사용자 인증이 성공하면 사용자의 정보를 사용하여 JWT 토큰을 생성하여 Header에 추가하여 반환하고 Client 는 이를 쿠키저장소에 저장한다.

6. 사용자는 게시글 작성과 같은 요청을 진행할 때 발급받은 JWT 토큰을 같이 보내고 서버는 이를 빠르게 인증 하고 사용자의 요청을 수행한다.

 

👉 [양방향 ↔ 단방향]

더보기
  • 양방향 암호 알고리즘
    • 암호화: 평문 → (암호화 알고리즘) → 암호문
    • 복호화: 암호문 → (암호화 알고리즘) → 평문

 

  • 단방향 암호 알고리즘
    • 암호화: 평문 → (암호화 알고리즘) → 암호문
    • 복호화: 불가 (암호문 → (암호화 알고리즘) → 평문)

    ❓그럼 사용자가 로그인할 때는 암호화된 패스워드를 기억해야 할까요?

  • Password 확인절차
    • 사용자가 로그인을 위해 "아이디, 패스워드 (평문)" 입력 → 서버에 로그인 요청
      • 서버에서 패스워드 (평문) 을 암호화
        • 평문 → (암호화 알고리즘) → 암호문
    • DB 에 저장된 "아이디, 패스워드 (암호문)"와 일치 여부 확인

👉 [Password Matching]

더보기

Spring Security에서는 비밀번호를 암호화하는 함수를 제공할 뿐만 아니라 사용자가 입력한 비밀번호를 저장된 비밀번호와 비교하여 일치여부를 확인해주는 함수도 제공하고 있다.

 

// 사용예시

// 비밀번호 확인

if (!passwordEncoder.matches("사용자가 입력한 비밀번호", "저장된 비밀번호")) {

    throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");

}

 

  • boolean matches(CharSequence rawPassword, String encodedPassword);
    • rawPassword : 사용자가 입력한 비밀번호
    • encodedPassword : 암호화되어 DB 에 저장된 비밀번호

 

 

 

 

 

 

 


 

 

스터디

 

시큐리티 전반적인 흐름이 이렇게 되는거군 하고 느꼈다 ㅎㅅㅎ

옵셔널 폴더명이 중복되는지 ? 옵셔널이 아니면 isPresent() , isEmpty() 못쓴다. 리스트는 오래 걸림!

'Sparta > What I Learned' 카테고리의 다른 글

23.1.17  (1) 2023.01.17
23.1.16  (0) 2023.01.17
23.1.13  (0) 2023.01.15
23.1.12  (0) 2023.01.13
23.1.11  (0) 2023.01.11