1. 기본 환경 설정

 1) https://start.spring.io/ 에서 spring initializr 를 사용하여 프로젝트를 구성하고 GENERATE로 생성한다

프로젝트 설정

 2) IntelliJ를 열고 프로젝트 파일을 오픈한다

  • 생성된 파일을 지정 폴더에 압축풀기를 한다
  • 압축을 푼 폴더에서 build 파일을 intelliJ에서 오픈한다

 3) 의존성을 설정한다

  • build.gradle 파일을 열고 아래와 같이 코드를 작성한다
plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-mustache'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
	useJUnitPlatform()
}

  • gradle 에서 코드를 추가하거나 수정한 후에는 Gradle 탭에서 Reload를 해 준다

 

 3) db.jpa 설정을 추가한다

  • resource 폴더 아래에 application.yml 파일을 추가한다
spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

 

 4) 애플리케이션 출력 화면을 구성하기 위해 2개의 파일을 추가한다

  • src/resources/templates 아래에 추가한다
  • loginForm.html
    - 로그인 화면에 출력되는 HTML 코드이다
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr />
<form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" /><br />
    <input type="password" name="password" placeholder="Password" /><br />
    <button>로그인</button>
</form>
<a href="join">회원가입</a>
</body>
</html>
  • joinForm.html
    - 회원가입 화면에 출력되는 HTML 코드이다
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr />
<form action="/join" method="post">
  <input type="text" name="username" placeholder="Username" /><br />
  <input type="password" name="password" placeholder="Password" /><br />
  <input type="email" name="email" placeholder="Email" /><br />
  <button>회원가입</button>
</form>
</body>
</html>

 

2. 기본 실행

 1) 애플리케이션을 실행한다

  • intelliJ에서 프로젝트를 실행한다
  • 콘솔 화면에 아래와 같이 password가 부여되고 설명이 출력되면 기본 설정이 적용된 것이다

  • http://localhost:8080 으로 접속한다

  • Username : user / password : 콘솔창에 표시된 password 를 작성한다
  • 현재 login 화면 구성이 없기 때문에 Error 메시지가 출력될 것이다

 

3. Spring Security Configuration 적용

 1) src > main > java > com.memberlogin.loginjoin 아래에 controller 패키지와 IndexController.java를 생성한다

  • 코드 생성 후 반드시 애플리케이션을 재실행 한다
package com.memberlogin.loginjoin.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {

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

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

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

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

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

    @GetMapping("/join")
    public @ResponseBody String join() {
        return "join";
    }
}
  • 애플리케이션을 재실행 후 web 접속하여 로그인을 하면 아래와 같이 출력된다
    - url을 지정하지 않은 경우에 출력은 'index'로 설정되어 있다

  • 총 5개의 url을 설정하였고 5개 중 1개의 url을 제외하고는 모두 로그인 시 정상적으로 작동하는 것을 확인할 수 있다
    - /login의 경우에는 Spring Security가 처리하고 있기 때문에 작동하지 않는다
localhost:8080/login localhost:8080/user localhost:8080/admin localhost:8080/manager localhost:8080/join

 

 2) src > main > java > com.memberlogin.loginjoin 아래에 config 패키지와 SecurityConfig.java를 생성한다

  • @Configuration과 @EnableWebSecurity를 추가한다
    - @EnableWebSecurity 추가 시 스프링 시큐리티 필터가 스프링 필터체인에 등록 된다
  • filterChain 메서드를 @Bean으로 등록한 후 스프링 컨테이너에서 관리할 수 있도록 한다
  • http.csrf().disable(); 의 경우에는 form 태그로만 요청이 가능해지고 postman등의 요청이 불가능하게 된다
    - csrf를 disable 한다
  • http.headers().frameOptions().disable(); 은 h2 연결할 때 필요하다
  • Config 설정이 되면  /login에 접속이 가능하게 된다
package com.memberlogin.loginjoin.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
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
				http.headers().frameOptions().disable();

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
        return http.build();
    }

}
  • 애플리케이션을 재실행 후 web 접속하여 로그인을 하면 아래와 같이 출력된다
    - http://localhost:8080 으로 접속하면 index 가 표시되고
    - http://localhost:8080/login 으로 접속하면 login 이 표시된다
  • /, /login, /join 3개의 url은 로그인 없이도 접속이 가능하다

  • /admin, /manager url의 경우에는 권한이 없기 때문에 403 에러가 출력된다

admin, manager 접속 시 콘솔창에 session ID가 생성되는 것을 볼 수 있다

  • 아래 코드를 추가한 후 재실행하고 접속하면 로그인이 가능해 진다
    - admin 과 manager 로 접속 시 login이 출력된다
                .and()
                .formLogin()
                .loginPage("/login");

 

 3) config 패키지 아래에 WebMvcConfig.java를 생성한다

  • mustache → html 사용할 수 있도록 설정한다
package com.memberlogin.loginjoin.config;

import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        MustacheViewResolver resolver = new MustacheViewResolver();
        resolver.setCharset("UTF-8");
        resolver.setContentType("text/html; charset=UTF-8");
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");

        registry.viewResolver(resolver);
    }
}
  • ViewResolver 구현 클래스 종류

 4) src > main > java > com.memberlogin.loginjoin 아래에 model 패키지와 Member.java를 생성한다

package com.memberlogin.loginjoin.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Entity
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String role;
    private LocalDateTime createdAt = LocalDateTime.now();
}

 

 5) src > main > java > com.memberlogin.loginjoin 아래에 repository 패키지와 MemberRepository.java를 생성한다

package com.memberlogin.loginjoin.repository;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
}

 

4. 회원 가입

 1) 회원가입에 필요한 코드를 수정한다

  • controller 패키지의 IndexController 클래스를 수정한다
  • 코드를 수정하고 실행하면 정상적으로 db에 저장되게 된다
@Controller
public class IndexController {

    @Autowired
    MemberRepository memberRepository;

		...

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

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

    @PostMapping("/join")
    public @ResponseBody String join(Member member) {

        member.setRole("ROLE_USER");
        memberRepository.save(member);
        return "join";
    }
}

  • 애플리케이션을 재실행하면 아래와 같이 로그인 페이지가 정상적으로 출력되고 로그인 시 index 가 출력된다

 

 2) 패스워드 암호화를 위한 코드 수정

  • config 패키지의 SecurityConfig 클래스를 수정한다
    - 아래 이미지에 @Bean 이 한 곳 빠져 있다...이 오류 때문에 고생 많이 했다...그래서 이미지를 수정하지 않고 둔다..^^
    - @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;
        -> IndexController 클래스의 Bean 충돌로 인하여 서버 실행에 에러가 발생한다.
        -> 주석 처리해 주면 해결된다
public class SecurityConfig {
		
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    
    ...

    @Bean // 추가
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
		
    ...
}

  • controller 패키지의 IndexController 클래스를 수정한다
    @PostMapping("/join")
    public String join(Member member) {
        member.setRole("ROLE_USER");
        String rawPassword = member.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        member.setPassword(encPassword);

        memberRepository.save(member);

        return "redirect:/login";
    }
  • 애플리케이션 재실행 후 웹에 접속하여 확인한다
  • http://localhost:8080/h2 에 접속하여 입력된 데이터를 확인할 수 있다

 

5. 로그인

 1) config > auth 패키지를 만들고 PrincipalDetails 클래스를 생성한다

  • PrincipalDetails 클래스에 implements UserDetails와 메서드를 오버라이드 한다
  •  org.springframework.security.core.userdetails
    - 보안 목적으로 Spring Security에서 직접 사용되지 않는다
    - 단순히 나중에 객체로 캡슐화되는 사용자 정보를 저장 Authentication 한다
    - 보안과 관련되지 않은 사용자 정보(예: 이메일 주소, 전화번호 등)를 편리한 위치에 저장할 수 있다
 

org.springframework.security.core.userdetails (spring-security-docs 5.7.2 API)

 

docs.spring.io

  • 시큐리티는 /login 주소에 요청이 오면 대신 로그인을 진행한다
  • Authentication 타입 객체이며 안에 Member 정보가 있어야 한다
  • 로그인 진행이 완료되면 security session을 만들어 준다 (Security ContextHolder)
package com.memberlogin.loginjoin.config.auth;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class PrincipalDetails implements UserDetails {

    private Member member;

    public PrincipalDetails(Member member) {
        this.member = member;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return member.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getUsername();
    }

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

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

    @Override
    public boolean isCredentialsNonExpired() {
        //isCredentialsNonExpired()는 암호 사용 기간이 지났는지 여부를 확인한다
        return true;
    }

    @Override
    public boolean isEnabled() {
        //isEnabled()은 특정 사이트 규칙에 따라 return false로 설정한다
        // ex) 1년 동안 로그인을 하지 않았을 경우
        return true;
    }
}
//현재 따로 규칙이 없기 때문에 isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled 메서드들을
//모두 return true로 설정한다

 

 2) config > auth 패키지에 PrincipalDetailsService 클래스를 생성한다

  • PrincipalDetailsService에 implement UserDetailsService와 메서드를 오버라이드 한다
  • Security 설정에서 loginProcessingUrl(”/login”);으로 요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어 있는 loadUserByUsername 함수가 실행된다
  • 메서드 파라미터에 username이라고 되어있으면 form을 통해 username을 가져올 때 name이 반드시 매치되야 한다
    - 이름을 똑같이 변경해주거나
    - SecurityConfig에 .loginPage() 아래에 .usernameParameter(”다른 이름")으로 추가해야 한다
  • loadUserByUsername 함수가 Authentication으로 값이 return 된다
package com.memberlogin.loginjoin.config.auth;

import com.memberlogin.loginjoin.model.Member;
import com.memberlogin.loginjoin.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
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 PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member memberEntity = memberRepository.findByUsername(username);
        System.out.println("username : " + username);
        if(memberEntity != null) {
            return new PrincipalDetails(memberEntity);
        }
        return null;
    }
}

 

 3) MemberRepository에 findByUsername 메서드를 추가해줍니다.

  • PrincipalDetailsService에 있는 loadUserByUsername에서 사용하기 때문에 해당 메서드를 추가한다
  • UserDetailService에서 findByUsername을 구현처리 할 때 자동적으로 생성되지만 한번 더 확인한다

package com.memberlogin.loginjoin.repository;

import com.memberlogin.loginjoin.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
    public Member findByUsername(String username);
}
  • 애플리케이션을 재실행 한 후 웹에 http://local:8080/uesr 접속하여 회원 가입 후 로그인을 진행한다
    - 이상없이 회원가입과 로그인이 진행된다
    - 친절하게 비밀번호에 대한 확인 창도 표시된다
  • localhost:8080/login 과 localhost:8080/join, localhost:8080/user 은 원활하게 접속이 된다
로그인을 실행한 후 화면이다
로그아웃은 하지 않았다



 


  • localhost:8080/admin 과 localhost:8080/manager는 다시 403 에러가 뜨는 것을 확인할 수 있다

 

6. 권한 처리

 1) manager, admin 처리

  • 회원가입 후 h2 database에서 ROLE을 ROLE_ADMIN으로 변경해줍니다.
  • localhost:8080/admin 과 localhost:8080/manager 에 정상적으로 접근이 가능하고 그 외 모든 url 또한 접근이 가능한 것을 확인할 수 있다
  • SecurityConfig 클래스에 코드를 추가한다
@Configuration
@EnableWebSecurity

//권한 처리 추가
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

/*@EnableGlobalMethodSecurity(securedEnabled = true)
  - Secured 애너테이션이 활성화 된다
  - SecurityConfig에서 접근 권한 설정이 아닌 Controller에서 애너테이션으로 관리할 수 있게 된다*/
  
/*@EnableGlobalMethodSecurity(prePostEnabled=true)
  - PreAuthorize, PostAuthorize 애너테이션이 활성화 된다.*/

public class SecurityConfig {

 

 2) IndexController.java에 새로운 메서드와 @Secured와 @preAuthorize를 추가한다

  //권한 추가
   @Secured("ROLE_ADMIN")
    @GetMapping("/info")
    public @ResponseBody String info() {
        return "info";
    }

    @PostMapping("/join")
    public String join(Member member) {
        member.setRole("ROLE_USER");
        String rawPassword = member.getPassword
  • SecurityConfig에 .antMatchers("/info/**").access("hasRole('ROLE_ADMIN')") 코드를 추가하는 것과 같은 동작이 된다
    - @Secured는 1개의 권한을 주고 싶을 때 사용한다
    - @PreAuthorize는 1개 이상의 권한을 주고 싶을 때 사용한다
      : #을 사용하면 파라미터에 접근할 수 있다
      : ex) @PreAuthorize("isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))")
    - @PostAuthorize는 메서드가 실행되고 응답하기 직전에 권한을 검사하는데 사용된다
    - 클라이언트에 응답하기 전에 로그인 상태 또는 반환되는 사용자 이름과 현재 사용자 이름에 대한 검사, 현재 사용자가 관리자 권한을 가지고 있는지 등의 권한 후처리를 할 수 있다
	@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    @GetMapping("/data")
    public @ResponseBody String data() {
        return "data";
    }

 

 

※ 참조 링크

BCryptPasswordEncoder :

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html

 

BCryptPasswordEncoder (spring-security-docs 5.7.2 API)

All Implemented Interfaces: PasswordEncoder public class BCryptPasswordEncoder extends java.lang.Object implements PasswordEncoder Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients can optionally supply a "version" ($2

docs.spring.io

 

'인증 & 보안' 카테고리의 다른 글

웹 보안 공격  (0) 2022.07.23
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. SQL Injection

 1) 공격 방식

  • 데이터베이스에서 임의의 SQL문을 실행할 수 있도록 명령어를 삽입하는 공격 유형이다
  • 응용 프로그램의 보안상의 허점을 이용해 데이터베이스를 비정상적으로 조작하여 기록을 삭제하거나 데이터를 유출한다

 

2) SQL Injection 대응

  • 입력(요청) 값을 검증한다
    - SQL문은 사람이 사용하는 자연어와 비슷하기 때문에 키워드를 막기에는 한계가 있다
    - 블랙리스트가 아닌 화이트리스트 방식으로 해당 키워드가 들어오면 다른 값으로 치환하여 SQL Injection에 대응할 수 있다
      : 화이트리스트란 기본 정책이 모두 차단인 상황에서 예외적으로 접근이 가능한 대상을 지정하는 방식 또는 그 지정된 대상들을 말한다
  • Prepared Statement 구문을 사용한다
    - Prepared Statement 구문을 사용하면 사용자의 입력이 SQL문으로부터 분리되어 SQL Injection을 방어할 수 있다
    - 사용자의 입력 값이 전달 되기 전에 데이터베이스가 미리 컴파일하여 SQL을 바로 실행하지 않고 대기한다
    - 사용자의 입력 값을 단순 텍스트로 인식하여 입력 값이 SQL문이 아닌 단순 텍스트로 적용되어 공격에 실패한다
  • Error Message 노출을 금지한다
    - 공격자는 데이터베이스의 Error Message를 통해 테이블이나 컬럼 등 데이터베이스의 정보를 얻을 수 있다
    - 에러가 발생한 SQL문과 에러 내용이 클라이언트에 노출되지 않도록 별도의 에러핸들링이 필요하다

 

2. Cross-Site Request Forgery (CSRF)

  • 다른 오리진(cross-site)에서 유저가 보내는 요청을 조작(forgery)하는 것이다
  • 다른 오리진이기 때문에 공격자가 직접 데이터를 접근할 수 없다

 1) 공격 조건

  • 클라이언트가 로그인 했을 경우에 쿠키로 클라이언트를 확인할 수 있어야 한다
  • 클라이언트가 요청한 parameter에 예측할 수 있는 정보가 담겨있어야 한다
  • GET 요청으로 CSRF 공격
    - http://bank.com/transfer?account_number=username&amount=1000$
    → http://bank.com/transfer?account_number=user계좌번호&amount=1000$
     http://bank.com/transfer?account_number=해커계좌번호&amount=1000$

 

 

 2) Cross-Site Request Forgery 대응

  • CSRF 토큰을 사용한다
    - 서버측에서 CSRF 공격에 보호하기 위한 문자열을 클라이언트의 브라우저와 웹 앱에만 제공한다 
  • SAME-site coolie를 사용하여 같은 도메인에서만 세션과 쿠키를 사용할 수 있도록 한다

 

 

※ 참조 링크

▶ "동일 사이트" 및 "동일 출처" 이해하기 : https://web.dev/same-site-same-origin/#same-site-cross-site

 

"동일 사이트" 및 "동일 출처" 이해하기

"동일 사이트"와 "동일 출처"는 자주 인용되지만 종종 오해됩니다. 이 기사는 이 둘이 무엇이며 어떻게 다른지 이해하는 데 도움이 됩니다.

web.dev

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. 해싱(Hashing)

  • 인증을 거치지 않고 단순하게 정보를 요청하고 응답한다면 누구나 접근이 가능하다

  • 간단한 password를 이용하여 인증을 진행하는 방식은 위험성이 매우 높다
    - DB가 타인으로부터 공격을 받아서 password가 노출된다면 클라이언트가 이용하고 있는 다른 애플리케이션까지 피해를 줄 수 있다

  • 암호화(encryption)는 일련의 정보를 임의의 방식을 사용하여 다른 형태로 변환하여, 해당 방식에 대한 정보를 소유한 사람을 제외하고 이해할 수 없도록 '알고리즘'을 이용해 정보를 관리하는 과정이다
  • 예를 들어 입력받은 문자열을 입력받은 숫자의 값 만큼 알파벳 순서를 건너뛴 문자로 변환하여 새로운 문자열을 반환하도록 한다
    - 단순하고 간단하지만 암호화에 속한다
    - 아래는 예제 코드이다
package CA.example;

public class Hashing {
    public static String shiftBy(String content, int offset) {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < content.length(); i++) {
            char shiftByOffset = (char)(content.charAt(i) + offset);
            result.append(shiftByOffset);
        }
        return result.toString();
    }

    public static void main(String[ ] args) {
        shiftBy(content:"apple", offset: 2);   //crrng 문자열 반환
        shiftBy(content:"crrng", offset: -2);  //apple 문자열 반환
    }
}

 

2. 쿠키(cookie)

  • 서버에서 클라이언트에 데이터를 저장하는 방법이다
  • 서버는 클라이언트에게서 쿠키를 이용하여 데이터를 가져올 수 있다
  • 서버가 클라이언트로부터 데이터를 가져오기 위해서는 특정 조건들을 만족해야 한다
  • 쿠키의 특성을 이용하여 서버는 클라이언트에 인증정보를 담은 쿠키를 전송한다
  • 클라이언트는 전송받은 쿠키를 이용하여 무상태성인 인터넷 연결을 안전하게 유지할 수 있다
  • 쿠키는 오랜 시간 유지될 수 있고 자바스크립트에서 접근이 가능하므로 보안에 취약하다

 1) 데이터를 가져오기 위한 쿠키 옵션

URL

  • 도메인(Domain)
    - 쿠키에서 도메인은 도메인 이름만 포함한다
    - 쿠키 옵션에서 도메인은 포트 및 서브 도메인 정보와 세부 경로를 포함하지 않는다
    - 클라이언트의 쿠키 옵션에 도메인 정보가 존재하고 도메인 옵션과 서버의 도메인이 일치하면 쿠키를 전송한다

 

  • 패스(Path)
    - Path는 세부경로이며 서버가 라우팅할 때 사용하느경로이다
    - 기본적으로 '/'으로 표시한다
    - path 옵션은 /file.html로 설정되어 있다면 file.html/user/username의 경로라도 /file.html을 만족하므로 쿠키 전송이 가능하다
  • MaxAge or Expires
    - 쿠키가 유효한 기간을 설정하는 옵션이다
    - 쿠키의 유효기간이 정해져 있지 않으면 탈취하기가 쉬워지므로 보안 측면에서 중요한 옵션이다
    - maxAge는 몇 초 동안 유효할 지를 설정하는 옵션이다
    - Expires는 클라이언트의 시간을 기준으로 유효한 날짜와 시간을 지정하는 옵션이다
      : 지정한 날자와 시간을 초과하면 쿠키는 자동으로 삭제된다
    - 세션 쿠키는 MaxAge나 Expires 옵션과 상관없이 크라우져가 실행 중에만 사용할 수 있는 쿠키이며, 브라우져가 종료되면 자동으로 삭제되는 쿠키이다
    - 영속성 쿠키는 브라우져의 종료 여부와 관계없이 MaxAge와 Expires 옵션에 지정된 내용만큼 사용 가능한 쿠키이다
  • Secure
    - 쿠키를 전송할 때 프로토콜 옵션에 따라 전송 여부를 결정한다
    - true로 옵션이 설정되어 있으면 HTTPS 포로토콜을 이용하는 경우에만 쿠키를 전송할 수 있다
    - 옵션 설정이 없다면 HTTP와 HTTPS 모두 쿠키를 전송할 수 있다
  • HttpOnly
    - 자바스크립트에서 브라우져의 쿠키에 접근을 허용할 것인지를 결정한다
    - true 옵션으로 설정되어 있으면 자바스크립트에서 쿠키에 접근이 불가하다
    - 옵션 설정이 없는 경우에 기본적으로 false 값으로 지정된다
    - false 값으로 지정되어 있으면 자바스크립트에서 쿠키에 접근이 가능하여 XSS 공격에 취약해 진다
  • SameSite
    -  Cross-Origin 요청을 받은 경우에는 사용한 메서드와 해당 옵션(get,post,put,patch,...)의 조합에 따라 서버의 쿠키 전송 여부를결정한다
    - 사용 가능한 옵션
      → Lax : Cross-Origin 요청이면 'GET'메서드에 대해서만 쿠키를 전송할 수 있다
       Strict : Cross-Origin 요청이 아닌 same-site 경우에만 쿠키를 전송할 수 있다
       None : 항상 쿠키를 전송할 수 있지만, Secure 옵션이 있는 경우에 한하여 전송한다
      
    - same-site는 요청을 보낸 Origin과 서버의 Domain, Protocol, Port가 같은 경우를 의미한다
    - 요청을 보낸 Origin과 서버의 Domain, Protocol, Port가 하나라도 다르면 Cross-Origin으로 구분한다

 2) 쿠키 전송

  • 서버에서 클라이언트로 쿠키를 처음 전송할 때에는 Header에 Set-Cookie 라는 프로퍼티에 쿠키를 담아 전송한다
  • 이후 쿠키를 전송할 경우에는 클라이언트는 Header에 Cookie라는 프로퍼티에 쿠키를 담아 서버에 전송한다

 

3. Session

  • 클라이언트가 인증에 성공한 상태를 Session이라고 한다
  • 서버는 in-memory, 세션스토어(redis 같은 트랜젝션이 빠른 DB)에 세션을 저장한다
  • 세션이 만들어지면 세션 id가 생성되고 인증된 클라이언트에게 전달된다
  • 클라이언트는 서버에서 발급한 세션 아이디를 쿠키에 저장하고 사용한다
  • 쿠키에 저장된 데이터는 보안에 취약하므로 사용이 종료되면 삭제해야 한다
  • 서버가 클라이언트의 쿠키를 임의로 삭제할 수는 없지만, set-cookie로 클라이언트에게 쿠키를 전송할 때 세션 아이디의 키값을 무효한 값으로 갱신할 수 있다

 

 

 

 

※ 참조 링크

 교차 출처 리소스 공유 (CORS) : https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

Same-origin Policy : https://en.wikipedia.org/wiki/Same-origin_policy

 

Same-origin policy - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Security measure for client-side scripting In computing, the same-origin policy (sometimes abbreviated as SOP) is an important concept in the web application security model. Under the

en.wikipedia.org

사이트 간 요청 위조 : https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0

 

사이트 간 요청 위조 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

Set-Cookie Attributes : https://urclass.codestates.com/53317456-fd52-431d-802c-28a53dc3735a?playlist=2338

 

Set-Cookie - HTTP | MDN

The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent can send it back to the server later. To send multiple cookies, multiple Set-Cookie headers should be sent in the same response.

developer.mozilla.org

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
웹 보안 공격  (0) 2022.07.23
인증 - HTTPS  (0) 2022.07.21

1. HTTPS (Hyper Text Transfer Protocol Secure Socket layer)

  • HTTP + Secure 의 합성어이다
  • HTTP over SSL(TLS), HTTP over Secure 라고도 한다
  • HTTP 프로토콜에 보안성이 추가된 시스템이다
  • HTTP 요청을 SSL 또는 TLS 알고리즘을 이용하여 HTTP 통신을 하는 과정에서 데이터를 암호화하여 전송한다
  • 접속하는 웹사이트의 왼족 자물쇠를 클릭하면 보안 정보가 나타난다

 

2. HTTPS 특징

 1) 암호화

  • 클라이언트와 서버가 데이터를 암호화하여 주고받기 위해 비대칭키 방식과 대칭키 방식을 혼용하여 사용한다
  • 대칭키 방식은 양쪽이 공통의 비밀 키를 공유하여 데이터를 암호화 및 복호화한다
  • 비대칭키 방식은 각각 공개키(public key)와 비밀키(private key)를 가지고 있어서 상대가 공개키로 암호화한 데이터를 개인이 가진 비밀키로 복호화한다
    - 전자서명은 개인키로 암호화하고 공개키로 복호화 한다
    - 암호화는 공개키로 암호화하고 개인키로 복호화 한다

  • 클라이언트와 서버는 한 쌍의 비대칭 암호화 Key를 가지고 있다
  • 클라이언트와 서버 간의 암호화 key를 상호 검증하여 보안을 강화한다
  • 서버가 실행되면서 인증서를 등록하므로 클라이언트는 서버의 공개키를 알 수 있다

 

2) 인증서(Certificate)

  • 데이터 제공 서버가 실제로 데이터를 보내는 서버인지 확인하는 용도이다
  • 서버의 도메인 정보가 포함되어 있어서 데이터 제공자의 인증을 용이하게 한다
  • 서버에서 보내는 응답 정보와 인증서를 함께 확인하여 보안을 강화한다

 

 3) CA(Certificate Authority)

  • 공인 인증서 발급 기관을 CA(Certificate Authority)라고 한다
    - CA는 공인 인증 기관으로 엄격한 기준에 따라 자격이 유지되고 박탈 당할 수 도 있다
    - CA들은 서버의 공개키와 정보를 CA의 비밀키로 암호화하여 인증서를 발급한다
  • 브라우져는 인증서 발급 기관으로부터 발급받은 인증서를 보유하고 있다

Certificate 예시

  • 서버와 클라이언트 간의 CA를 통해 서버를 인증하고 데이터를 암호화하는 과정의 전체 프로토콜을 TLS 또는 SSL이라고 한다
    - SSL과 TLS는 사실상 동일한 규약을 뜻한다
    - SSL이 표준화되며 변경된 이름이 TLS이다

 

3. 인증서 발급

 1) 인증서 지원 형식

  • 자바는 다음과 같은 두 가지의 인증서 형식을 지원한다
    - PKCS12 (Public Key Cryptographic Standards #12)
     : 여러 인증서와 키를 포함할 수 있으며, 암호로 보호된 형식으로 업계에서 널리 사용된다

    - JKS (Java KeyStore)
     : PKCS12와 유사하다
     : 독점 형식이며 Java 환경으로 제한된다

 2) HTTPS 사설 인증서 발급 

  • Spring Initializr에서 기본 웹 서버 프로젝트를 생성한다
  • mkcert 프로그램을 설치한다
    - mkcert 프로그램을 이용해서 로컬 환경(내 컴퓨터)에서 신뢰할 수 있는 인증서를 만들 수 있다
    - mkcert는 PKCS12 형식만 지원합니다.
  • mkcert 프로그램 설치 방법
    - 터미널에 아래 명령어를 입력 후 실행하여 설치한다
WSL 터미널에서 작성한다

$ sudo apt install libnss3-tools

      - 설치가 안되고 에러가 발생할 경우 update를 먼저 진행한다

$ sudo apt update

      - 설치가 되었으면 아래 명령을 입력하고 진행한다

$ wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64

      - 마지막으로 아래 명령을 입력해 준다

$ chmod +x mkcert
$ sudo cp mkcert /usr/local/bin/

  • 로컬을 인증된 발급기관으로 추가해 준다
    - 아래 명령어를 작성 후 실행하여 CA를 생성한다
$ mkcert -install

  •  PKCS12 인증서를 생성하기 위해 아래 명령을 작성 후 실행한다
$ mkcert -pkcs12 localhost

  • 옵션으로 추가한 localhost에서 사용할 수 있는 인증서가 완성되었다
  • 해당 커맨드를 입력한 위치에 localhost.p12라는 파일이 생성된 것을 확인할 수 있다
  • 저장 경로를 반드시 확인한다 

3) HTTPS 서버 작성

  • Spring Boot를 이용하여 HTTPS 서버를 간단하게 작성할 수 있다
    - 생성된 인증서를 resources 폴더로 이동한다

     - application.properties 에서 관련 설정을 추가한다
      → 비밀번호인 changeit은 비밀번호를 설정하지 않았을 때의 기본값이다
       인증서 비밀번호는 인증서를 생성할 때 설정하거나 생성 후 변경해 줄 수 있다
       비밀번호 설정 방법은 한번 찾아보시기 바랍니다 :)

server.ssl.key-store=classpath:localhost.p12    -> 인증서 경로를 적는다
server.ssl.key-store-type=PKCS12                -> 인증서 형식을 적는다
server.ssl.key-store-password=changeit          -> 인증서 비밀번호를 적는다
  • 이제 서버를 실행해 보면 HTTPS 서버가 작동되는 것을 확인할 수 있다

  • web애플리케이션에서도 확인해 본다
    - 페이지를 생성한 것이 없으므로 white error 가 발생한다

 

 

 

 

 

※ 참고 자료

▶ 중간자 공격 : https://en.wikipedia.org/wiki/Man-in-the-middle_attack

 

Man-in-the-middle attack - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Form of message tampering In cryptography and computer security, a man-in-the-middle, monster-in-the-middle,[1][2] machine-in-the-middle, monkey-in-the-middle,[3] meddler-in-the-middle

en.wikipedia.org

▶ 암호화 : (101) HTTPS가 뭐고 왜 쓰나요? (Feat. 대칭키 vs. 비대칭키) - YouTube

 교차 출처 리소스 공유 (CORS) : https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

'인증 & 보안' 카테고리의 다른 글

Spring Security - 환경 구성  (0) 2022.07.26
웹 보안 공격  (0) 2022.07.23
보안 - Hashing,Coolie,Seesion  (0) 2022.07.23

+ Recent posts