Session
- 세션(session)
- 웹 사이트의 여러 페이지에 걸쳐 사용되는 사용자 정보를 저장하는 방법
- 사용자가 브라우저를 닫아 서버와의 연결을 끝내는 시점까지를 의미
- 쿠키와의 차이점
- 쿠키는 클라이언트 측의 컴퓨터에 모든 데이터 저장
- 세션은 서비스가 돌아가는 서버 측에 데이터 저장
- 세션의 키 값만을 클라이언트 측에 저장
- 브라우저는 필요할 때마다 이 키 값을 이용하여 서버에 저장된 데이터 사용
- 보안에 취약한 쿠키를 보완하는 역할
- 상태 생성
- 고유 식별자를 쿠키에 저장하거나 URL에 심는 것
- 이때, 서버는 각 사용자를 고유 식별자와 연관시킨다
- ID의 고유성과 HTTP 전송의 암호화
- 쿠키와 고유 식별자를 통해 생성된 세션의 보안에 기여하는 두 가지 요인
- 서버와 클라이언트 사이의 연결은 일회성이기 때문에 연속성을 위한 상태 생성이 중요하다
- 고유 식별자
- 사용자가 서버에 요청을 보내면 고유 식별자를 전송해준다
- 이를 통해 서버와 통신하는 대상에 대한 정보를 알 수 있다
- 특정 개인에 대한 정보를 서버에 유지할 수 있게 한다
- 서버에 접근하는 대상을 구분하여 그 대상에 따라 정보 접근 권한이 달라진다
- 정보를 열기 위한 열쇠
- 고유 식별자 저장 방법
- 쿠키
- 지정 도메인에 따라 다름
- 클라이언트의 컴퓨터에 도메인으로부터의 쿠키가 있다면 클라이언트가 도메인으로 보내는 모든 요청의 쿠키가 서버에 간다
- 이를 통해 사용자 식별이 가능해진다
- URL
- 다른 페이지로 가기 위해 링크를 누를 때마다 URL의 매개 변수 혹은 고유 식별자를 갖춘 URL의 변수 등이 식별자로 사용된다
- 쿠키가 없더라도 고유 식별자를 URL에 동적으로 연결하여 요청을 보낼 때마다 확인 가능
- HTTPS를 사용하면 TLS와 보안 연결에 의해 안전하게 보호된다
- 데이터가 필드로 전송되는 동안 전부 암호화되기 때문
- UUID
- 범용 고유 식별자
- 수가 매우 많아 앞으로 백 년 동안은 거뜬히 만들어낼 수 있다
- HTTPS를 사용하는 전송을 중단할 수 없다
- 데이터베이스 키
- 고유 식별자로 사용 가능
- 사용자 키가 아니라 사용자를 더 안전하게 만들어주는 세션용 테이블 키
- 세션 원리
- 클라이언트가 서버로 고유 식별자(세션 ID, SID) 전송
- 세션 DB
- 고유 식별자가 있는 사용자 ID를 알면, 사용자에 관한 정보를 모두 확인 가능
- 사용자 ID를 통해 사용자 정보에 접근 가능
- 게시물, 신용카드, 로그인 이메일, 비밀번호, ...
- 세션 ID는 사용자 ID와 연결됨
- 세션 DB와 사용자 DB는 분리된 공간
- 사용자가 새 세션을 만들고 새로운 세션 ID를 만들면 그 세션 ID를 사용자 ID와 연결한다
- 쿠키와 세션 ID를 사용해 세션 생성의 바탕이 되는 사용자 ID를 가져오는 것
UUID
- UUID
- 범용 고유 식별자
- 네트워크 상에서 고유성이 보장되는 ID를 만들기 위한 표준 규약
- 128비트의 숫자이며, 32자리의 16진수로 표현된다
- 8자리-4자리-4자리-4자리-12자리 패턴
- UUID 생성하기
- Cookie를 요청했을 때 Cookie가 없으면 새로운 UUID 생성
- goolge package의 uuid 모듈로 대체(satori 지원 안 함)
import error 발생 -> build는 됨
- sumdb 파일 삭제 후, src에 모듈 설치
uuid.New()
- 생성된 UUID는 쿠키의 Value 필드에 넣어 전송한다
- Cookie 옵션 - HttpOnly
- 브라우저가 서버에 http request를 요청할 때만 쿠키 전송 허용
- Cookie 옵션 - Secure
- 브라우저와 서버가 HTTPS로 통신할 때만 쿠키 전송 허용
회원 가입 예제
- index 페이지
- 사용자 정보 가져오기
- 가져온 정보 화면에 띄우기
- bar 페이지
- 사용자 정보 가져오기
- 로그인 여부 확인하기
- 로그인이 안되어 있으면 index 페이지로 redirect
- 로그인 후, 접속할 최종 목표 페이지
- signup 페이지
- 로그인 여부 확인하기
- 로그인이 되어 있으면 index로 redirect
- 로그인이 이미 되어 있으면 가입 할 필요가 없다
- POST 메서드로 제출된 Form data 가져오기
- 가져온 데이터는 user 객체에 저장
- username이 이미 존재하면 에러 띄우기
- 새로운 세션 생성하기
- uuid 생성하기
- cookie 생성하기(uuid, 세션 id)
- 세션 DB에 사용자 이름으로 접근하여 user 객체 할당하기
- 등록이 완료되면 index 페이지로 redirect
- 동일 패키지 내, go 파일이 여러 개일 때
go run .
으로 실행
go run *.go
로 실행
go build
로 실행
- 실행 가능한 바이너리로 빌드하여 최종 결과물을 작업 폴더에 넣는다
로그인/로그아웃 예제
- bcrypt로 비밀번호 암호화 하기
- 블로피시 암호에 기반을 둔 암호화 해시 함수
.GenerateFromPassword(비밀번호, 암호화 수준)
- 사용자로부터 받은 비밀번호를 byte slice 형태로 암호화한다
- 암호화 수준을 결정할 수 있다
- 사용자가 설정한 비밀번호는 사용자만 안다
- ex:) [36 50 97 36 48 52 36 76 53 ** 67 69 50 78 80 52 48 122 101 47 56 114 49 73 83 86 72 80 ** 121 53 66 98 47 121 116 100 76 106 77 56 ** 74 90 113 119 * 97 77 109 86 67 88 115 99 52 72 ** 76 87]
.CompareHashAndPassword(db에 저장된 해시, 사용자 비밀번호 해시)
- 두 해시 값을 비교하여 사용자가 입력한 비밀번호가 db에 저장된 비밀번호화 같은지 확인
- 엄밀히 따지면 사용자가 입력한 비밀번호가 아닌 해당 비밀번호에 대한 해시 값을 비교하는 것
- login 페이지
- 로그인 여부 확인
- 로그인 되어 있으면, index 페이지로 redirect
- 사용자가 입력한 이메일과 비밀번호 가져오기
- DB에 해당 정보가 있는지 확인
- 없으면 user name이 틀렸다는 문구 출력
- DB에 저장된 해시와 사용자가 입력한 비밀번호의 해시 비교하기
- 맞지 않으면 password가 틀렸다는 문구 출력
- 세션 생성하기
- logout 페이지
- 로그인 여부 확인
- 로그인 되어 있지 않으면, index 페이지로 redirect
- session에 대한 쿠키 가져오기
- dbSession에서 session ID 삭제하기
- 쿠키 삭제하기
- login 페이지로 redirect
- 권한 부여하기
- user struct에 role 추가하기
- Form으로부터 role 입력 받기
- 최종 접속 페이지(bar)에서 권한에 따라 접속 허용하기
- 세션 만료하기
- 절차
- 회원 가입하기
- 30초 전에 bar 페이지를 가면 정상적으로 접속 된다
- 30초 뒤에 bar 페이지를 가면 안 들어가진다(redirect)
- DB session에 사용자 이름과 마지막 활동 시간을 포함하는 session struct를 포함한다
- session ID를 통해 사용자 이름과 마지막 활동 시간에 접근 가능
- session 길이를 설정한다
ex:) const sessionLength int = 30
- init 함수에서 정리된 db Session 변수에 현재 시간을 넣어준다
- session이 정리될 때마다 현재 시간으로 초기화 된다
- session을 정리하는 함수를 정의하고 로그아웃 시에 호출한다
- 현재 시간에서 앞서 정의한 정리된 dbSession 변수에 담긴 시간을 뺀 수를 확인한다
- 현재 session을 보여주고 dbSession을 순회하며 session 길이를 초과한 user를 삭제한다
- 정리된 db session 변수를 현재 시간으로 초기화 한다
- 정리 후의 session을 보여준다
- session 만료 후, 동일한 user name으로 접근하면 새로운 session이 부여된다
- 쿠키를 생성할 때마다 쿠키의 만료 시간을 session 길이로 설정한다
- Map과 동시성
- map에 없는 값을 지워도 에러가 나지 않는다
- map에 없는 값에 접근(read)해도 에러가 나지 않는다
- 오직 map에 새로운 값을 추가하거나 변경(write)할 때 에러가 난다
- 동시성 이슈
- 하나의 goroutine에서 map에 값을 쓸 경우, 다른 goroutine에서는 해당 map에 값을 읽거나 쓸 수 없다
- 위와 같이 동시성을 잘못 구현할 경우, 런타임이 문제를 인식하고 프로그램을 종료시킨다
package main
import (
"fmt"
)
func main() {
m := map[int]int{}
m[4] = 3
delete(m, 7)
fmt.Println(m)
fmt.Println(m[5])
}
- time.Time은 다수의 goroutine에서 동시에 사용 가능하다