데브코스 백엔드 3기 71일차
목금!
JWT를 이용한 로그인 API와 인증된 사용자가 본인의 정보를 확인하는 API를 구현했다
마지막으로 카카오 로그인하기를 구현했다
일기(회고)
- Spring Security 강의가 끝이 났고,
기대가 되었던 kakao oauth도 해보았다
전반적인 Spring Security 후기는 쉽지 않았다..!는 결론
그렇지만 강의 안밀리고, 포기 안하고 끝까지 따라간 나 칭찬해
- 객사오책을 다 읽었다!
JPA 게시판 과제도 잘 제출했다
이번주도 하루에 하나씩 테코톡 잘 보았고,
CS 스터디도 잘했고(다음주에 벌써 끝..!)
알고리즘도 잘풀었고(알고리즘은 얼른 잘하고싶다)
나와의 약속을 잘 지켜서 너무 뿌듯하다!
실력도 늘었나에 대한건 현재 단계에서 스스로 판단하긴 어렵다고 생각한다
의심이 들 땐 내가 투자한 시간을 믿는중
- 다음주는 데브코스 방학!(복습과 휴식)겸 새해!
모처럼 힐링을 하려 가족과 칭9들과도 약속을 잡아보았다
그 외의 시간에는 정리한 내용들 복습하기 & 알고리즘은 계속 하루에 하나씩 이어나가기
계속해서 새로운걸 공부해 나가는것도 중요하지만
배운걸 유지해야 아는게 늘어날테니
정리기간을 가지려 한다!
-
22년이 일주일 남았다
싱숭생숭하다
한 해가 지나가는게 아쉬워, 이번해가 아쉬움이 많았던 해인가?하고 생각해보면 그건 또 아니다
열심히 살았어 이번해도 ㅋㄷㅋㄷ
그래도 지나가는 시간들에 대한 아쉬움은 어쩔수 없나보다
시간이 역시 젤 중요해! 시간은 한번 지나가면 돌아오지 않으니까!(청춘 드라마!)
내년도 행복하자 태햐
JWT를 이용한 API 만들기
JWT를 이용한 로그인 API
(인증 API 추가하기)
- JWT 인증처리를 명확히 하기위해
JWT 전용 Authentication 구현체
로 JwtAuthenticationToken 만들기 - 인증된 사용자의
Principal 타입
으로 JwtAuthentication 만들기(userDetails.User 같은) - 위의 2개의 클래스를 추가함으로써 Spring Security의 인증처리기능 일부 커스터마이징 필요
JwtAuthenticationFilter 만들기
- 역할
- HttpRequest의 Http Header에서 token 꺼내고
- JWT 디코딩해서 JwtAuthenticationToken 만들어서
- SecurityContext에 참조를 넘겨줌
- 새로만든 필터를 SpringSecurityFilterChain에 몇번째에 위치시켜야할까 고민 -> SecurityContextPersistenceFilter 뒤에
JWT 전용 Authentication 구현체 만들기
- JwtAuthenticationToken.java
- extends AbstractAuthenticationToken
- 인증 완료 전 호출되는 생성자
- 인증 완료 시 호출되는 생성자
- 이 생성자 안에서 setAuthenticated(true) -> 생성자 통해서만 true
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private String credentials;
public JwtAuthenticationToken(String principal, String credentials) {
super(null);
super.setAuthenticated(false);
this.principal = principal;
this.credentials = credentials;
}
JwtAuthenticationToken(Object principal, String credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
super.setAuthenticated(true);
this.principal = principal;
this.credentials = credentials;
}
JWT로 인증된 사용의 Principal 타입 만들기
- JwtAuthentication.java
- token 과 username 가짐
UserDetailsService 역할 만들기
- UserDetailsService : UserDetails(안에 User객체) 객체 가져와주는 애인데, 이젠 내가 만든 User 객체 사용
- UserService.java 구현 : 로그인할 수 있게 UserRepository에서 find해서 User객체 찾음, 그 객체안에서 password 체크하는 로직 가짐
JWT인증 전용 AuthenticationProvider 구현체 만들기
JwtAuthenticationToken type의 인증요청을 처리할 수 있는 Provider
- supports() : 주어진 Authentication 객체가 JwtAuthenticationToken으로 casting 가능한지 확인하게끔 override
- authenticate() : 주어진 Authentication을 JwtAuthenticationToken으로 casting
- UserService의 login(principal, credentials) 통해 로그인해서 User객체 얻어옴
- Filter에서 User 정보로(사용자아이디, 권한목록으로) Claims 만들어서 jwt.sign(claims) 통해 token 만들어서 JwtAuthenticationToken 만듬
JwtAuthenticationToken authenticated = new JwtAuthenticationToken(new JwtAuthentication(token, user.getLoginId()), null, authorities);
Controller
- LoginRequest(DTO) 이용해서
- 인증요청 위한 JwtAuthenticationToken 만들어서
- AuthenticationManager 통해 인증요청
- 인증완료된 JwtAuthenticationToken에서 getPrincipal()에서 getDetails() 해서 내가만든 JwtAuthentication객체 얻음
Spring Security configuration class에 설정
- JwtAuthenticationFilter 빈 등록 후 SecurityContextPersistenceFilter 뒤에 끼워넣어주기
.addFilterAfter(jwtAuthenticationFilter(), SecurityContextPersistenceFilter.class)
- JwtAuthenticationProvider 빈 등록
- AuthenticationManager 빈 등록
JWT를 통해 인증된 사용자가 본인의 정보를 확인하는 API
@AuthenticationPrincipal
: 인증된 사용자의 정보 가져오기 위해 사용- HttpRequest의 Header에 입력되어 있는 JWT 가져와서
- 디코드 후 위변조 검증 후 제대로 들어있다면
- SecurityContextLoader에서 Context 가져와서 Authentication 객체(구현체)에서 getPrincipal() 통해 Principal 객체(내가만든 JwtAuthentication)가 Controller에게 전달됨
- 여기까지 커스텀하게 구현한 JwtAuthenticationFilter 사용한 방법
- Spring Security에서 JwtAuthenticationFilter와 비슷한 처리하는 필터 :
SecurityContextPersistenceFilter
- SecurityRepository를 loadContext()통해서 SecurityContext 가져옴
SecurityRepository
를 커스텀 구현하면 JWT 사용할때 가능- 그러나, SecurityRepository를 사용하는게 SessionManagementFilter도 있어 변경한다면 이부분 또한 설정 필요 -> 고려할게 많음
- 따라서,
JwtAuthenticationFilter 구현하기 추천
Spring Security OAuth2.0
사용자가 가입된 서비스(google, naver, kakao 등)에서 제공하는 API 이용해 사용자 데이터 접근할 때
비밀번호 없이 사용자로의 권한 위임받아야 함
=> OAuth2.0(Open Authorization, Open Authentication)이라는 표준 인증 프로토콜 통해 처리
- Resource Owner : 리소스 주인, 서비스를 사용하는 사용자
- Client : Resource Owner 대신해서 리소스에 접근하려는 응용 프로그램
- Authorization Server : Client에 access token 발급하는 서버(naver, kakao 인증서버)
- Resource Server : 리소스 호스팅하고 access token 받으면 응답하는 서버(naver, kakao 서버)
OAuth2.0에서 사용하는 토큰 요청법(Client -> Authorization Server)
1. Authorization Code Grant
- 가장 잘 알고 있어야 함
- Server to Server 인증방식 -> Back-end server 필요
- 보안상 우수 : Client-secret, access token 같은 민감정보가 서버 밖으로 노출되지 않음
- flow
- 1)Authorization Request
application(Client) -> 사용자(Resource Owner)
: Authorization Server로 redirection- response-type(“code” 고정값), client_id(클라이언트 고유 식별키), redirect-uri(인증 완료 후 백엔드 서버 호출시 사용할 uri), scope(Client가 요구하는 리소스), state(CSRF 공격방지 위한 임의의 문자열) 등을 가지고
- 2)Authorization Response
Authorization Server -> application
: 사용자가 로그인하고 나면 위에서 보낸 redirect-uri로 redirection- code(access token 교환을 위한 1회성 승인코드), state(위에서 전달한 임의의 문자열) 가지고
- code는 URL에 노출됨 -> 한번 쓰면 바로 폐기함
- 3)Token Request
application -> Authorization Server
: 위에서 받은 code를 가지고 access token으로 교환요청- grant-type(“authorization” 고정값), code(위에서 받은 1회성 승인코드), client_id, client_secret(클라이언트 비밀키)을 가지고
- 4)Token Response
Authorization Server -> application
: access token, refresh token 응답
- 1)Authorization Request
2. Implicit Grant
- Authorization Code Grant에서 Token Request 단계 생략
- Authorization Response 시에 code 대신 바로 access token 전달
-> access token이 URL에 노출
-> 보안상 리스크(백엔드 서버 없는 제한적인 환경에서만 사용권장)
- Authorization Response 시에 code 대신 바로 access token 전달
3. Client Credentials Grant
- 사용자(Resource Owner)는 전혀 관여x, 백그라운드에서 실행되는 서버간 통신에 적용할 수 있는
- flow
application(Client) -> Authorization Server
: client_id, client_secret 보냄- access token 받음
4. Resource Owner Password Credentials Grant
- 일반 로그인 인증과 같음
- 인증정보가 Client를 거쳐가므로 Client를 완전히 신뢰할 수 있을 때 사용
- third party application에서는 잘 못쓰고, 공식 service application 정도 되어야 적용해 볼 수 있음
- flow
사용자(Resource Owner) -> application(Client) -> Authorization Server
: 사용자의 (naver, kakao의) id, password가 application 거쳐서 인증서버까지- access token 받음
Kakao OAuth2.0 Login 구현하기
- 카카오 로그인하기 연동을 위해 kakao developers site에 application 등록
- kakao developers에서 application 추가
- 로그인 활성화 on
- redirect url 등록
- 동의항목(닉네임, 프로필사진) 필수동의로
- 보안 - Client Secret 생성 + 활성화
spring-boot-starter-oauth2-client
dependency 추가- application.yml에 설정
- registration
client-id
: Rest API키client-secret
: 보안파트에서 만든 Client Secretredirect-uri
: kakao 부분{registrationId}
로 변수처리
- provider : kakao Rest API 주소들
authorization-uri
:인가코드(1회성 인증코드) 받기
API pathtoken-uri
: 1회성 인증코드를 이용해access token 받기
API pathuser-info-uri
: access token으로사용자 정보 가져오기
위한 APIuser-name-attribute
: 사용자 정보 가져오기 API에서 정보 가져왔을 때사용자 고유 식별키
추출위한 고유 식별키 필드명- 사용자 정보 가져오기의 응답모델을 살펴보면 id가 회원번호라고 되어있음
= id라는 필드가 카카오에서 사용자를 식별하기 위한 고유키라는 뜻
- 사용자 정보 가져오기의 응답모델을 살펴보면 id가 회원번호라고 되어있음
- registration
spring:
security:
oauth2:
client:
registration:
kakao:
client-name: kakao
client-id:
client-secret:
scope: profile_nickname, profile_image
redirect-uri: "http://localhost:8080/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-authentication-method: POST
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
인증완료 후 호출되는 handler 만들기
- handler 빈등록
- oauthlogin의 successHandler로 등록
- 기능
- 카카오 인증후의 사용자가 application의 신규 사용자라면 가입시키기(만들었던 UserService 수정해서 이용)
- 카카오 인증후의 사용자에게 JWT를 생성해서 보내주기
users table
새로 만들기- password 없애고
- provider : oauth 인증 provider의 식별자(kakao)
- provider_id : oauth 인증된 사용자의 고유 식별키
- profile_image : 사용자 프로필 이미지 url 저장
- OAuth2.0으로 인증된 사용자 :
OAuth2AuthenticationToken
type- OAuth2AuthenticationToken에서 principal :
OAuth2User
- OAuth2User에서 getName() -> providerId(String)
- OAuth2User에서 getAttributes() -> Map<String, Object> 형태로 attributes
- attributes에서 get(“properties”) -> Map<String, Object> 형태로 properties
- properties에서 get(“nickname”), get(“profile_image”) -> 사용자정보(닉네임, 프로필이미지) 얻음
- attributes에서 get(“properties”) -> Map<String, Object> 형태로 properties
- OAuth2AuthenticationToken에서 principal :
내부적으로 일어나는 일
- Spring Security의 Filter Chain에 추가된 필터들
- OAuth관련 2개
OAuth2AuthorizationRequestRedirectFilter
: redirection 시켜주는OAuth2LoginAuthneticationFilter
: 카카오에서 인증 후 백엔드 서버 호출(1회용 코드 줄때)시, 호출되는 필터
DefaultLoginPageGeneratingFilter
: 인증방식 별로 적절한 id, pwd 기반의 폼로그인 페이지 생성해줌
- OAuth관련 2개
쿠키기반인 AuthorizationRequestRepository의 커스텀 구현체 만들기
- OAuth관련 필터 2개가 서로 연결되는 부분 있음 - CSRF 공격 방지 위한 임의의 문자열(state) 확인하는 부분
- OAuth2AuthorizationRequestRedirectFilter에서
AuthorizationRequestRepository
를 통해 authorizationRequest저장
- OAuth2LoginAuthneticationFilter에서
AuthorizationRequestRepository
를 통해 authorizationRequest조회
- OAuth2AuthorizationRequestRedirectFilter에서
- AuthorizationRequestRepository의 기본 구현체 :
HttpSessionOAuth2AuthorizationRequestRepository
-> session 기반
=> API 서버에 맞추어 cookie 기반으로 변경해보기(커스텀) :HttpCookieOAuth2AuthorizationRequestRepository
만들기- HttpServletRequest를 넘겨서 cookie 조회하는 메서드
- cookie clear 하는 메서드
- cookie로부터 OAuth2AuthorizationRequest 만드는 메서드(deserialize)
- <-> Object를 cookie에 저장하는 메서드(serialize, save) : OAuth2AuthorizationRequest를 String으로 변환하고 그 String을 cookie에 저장
Spring Security OAuth2 JDBC방식으로 바꾸기
- AuthorizedClientRepository에서 authorizedClient를 save함
- AuthorizedClientRepository의 구현체 OAuth2AuthorizedClientRepository의 구현체 AuthenticatedPrincipalOauth2AuthorizedClientRepository에서 OAuth2AuthorizedClientService의 메서드 사용
- OAuth2AuthorizedClientService의 기본 구현체 : InMemoryOAuth2AuthorizedClientService
- JDBC기반 구현체로 변경해주기(제공되는) :
JdbcOAuth2AuthorizedClientService
- 얘가 사용하는 테이블도 이미 정의되어 있음(oauth2-client-schema.sql)
이번주 영상(테코톡 등)
12/19 월, 완태의 전략패턴
전략패턴
: 디자인패턴 중 하나객체가 할수있는 행위
를 동일계열의 알고리즘군으로 정의해서캡슐화
함으로써 전략으로 만들어 놓고 사용setter 사용
해서 상호교환을 통해동적으로 전략수정
이 가능
- 예시) 로봇
- 기능1 로봇/ 기능2 로봇/ 기능3 로봇.class 를 각각 만드는게 아니라
- 기능1/ 기능2 / 기능3.class(strategy) 를 각각 만들고, 기능1, 2, 3을 가진(composition) 로봇.class(context)를 만들기
- 기능1/ 기능2 / 기능3.class 를 구현하는 클래스들을 만들기
Java Comparator
가 대표적 예시 : 유저들마다 커스텀하게 정의한 Comparator 구현해서 sort라는 컨텍스트에서 사용
12/20 화, 프로그래밍 초식 - DB 트랜잭션 조금 이해하기 01
트랜잭션
의 범위는하나의 커넥션
- 여러 메서드 호출 시 묶고 싶은데 그러려면 커넥션객체 전달해야 함
커넥션객체 전달 안하고도
한 트랜잭션에 묶이도록 하기 위해서트랜잭션 전파
필요- 외부 API 연동이 섞여있으면 롤백처리 주의
- 외부 API는 록백이 안됨 -> 외부 시스템의 상태를 돌리는 방법 고려해야함
12/21 수, 프로그래밍 초식 - DB 트랜잭션 조금 이해하기 02
- 경쟁상태 : 여러클라이언트가 같은 데이터에 접근하려고 할 때 문제 발생
- -> 트랜잭션 순서대로 실행 : 한번에 한개 트랜잭션만 처리 -> 성능저하
- -> 트랜잭션 격리
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
Read Committed
: dirty read/write(커밋되지 않은 데이터 읽기, 변경하기) 문제 방지- 커밋된 데이터만 읽기
커밋된 값(언두로그)
,트랜잭션 진행중인 값(InnoDB 버퍼풀)
따로 보관
- 커밋된 데이터만 변경하기
- 행단위로 잠금 사용 -> 같은 데이터 수정하려고 하는 트랜잭션은 수정 중인 트랜잭션 끝날때까지 대기
- 커밋된 데이터만 읽기
Repeatable Read
: read skew(읽는 시점에 따라 데이터가 바뀜) 문제 방지- 트랜잭션 동안 같은 데이터를 읽게함
- ex)
MVCC(Multi-Version Concurrency Control)
: 읽을 때 읽는동안 다른 트랜잭션에 값이 변경 or 삭제되더라고 특정버전에 해당하는 데이터만 읽음
- Lost update : 같은 데이터를 update 하다가 변경한 내용 유실될 수 있음
- 해결
- DB가 지원하는 원자적(atomic) 연산 사용
- 명시적 잠금 : 수정위해 조회해오면서 수정할 행 미리 잠금(Select … for update)
- CAS : 수정할 때 값이 같은지 비교 후 같을때만 수정
- 해결
Serializable
: 같은 데이터를 쓰지 않지만 트랜잭션의 결과가 다른 트랜잭션에 영향미쳐 논리적으로는 경쟁상태일 때(물리적으론 아닌데)를 방지- 인덱스 기반 잠금, where 절의 조건기반 잠금
- 잠금시간은 최소화해야, 길어지면 성능(처리량)저하
12/22 목, 로키의 상속보다는 composition
- 조합 : 새로운 클래스를 만들고 private 필드로
기존 클래스의 인스턴스를 참조
하게 하는 설계- 장점
- 캡슐화를 깨뜨리지 않음
- 변화에 유연
- 새로운 클래스는 기존클래스의 내부구현에 영향에서 벗어남(심지어 메서드 하나가 추가되어도 영향없음)
- 장점
has a
관계
12/23 금, 조조그린의 Thread Pool
요청마다 thread 생성하면 안좋은점
Thread 생성비용 큼
-> 매번 만들기는 응답시간 늘어남- 요청 많아지면, 스레드 많아짐 -> 하드웨어쪽 무리, 오버헤드 때문에 프로그램 멈출 수도
- => 메모리 차치 많음 ->
메모리 문제
- => Context Switching 늘어남 ->
CPU 오버헤드
- => 메모리 차치 많음 ->
- Java : One-to-One Threading Model로 thread 생성
- User thread(process의 thread) 생길 때 그에 해당하는 OS thread 하나 꼭 연결해줘야함
- User thread : OS thread에 대한 유저프로그램 계층에서의 추상화
=> thread 하나 만들 때마다 OS kernel의 작업 필요
=> 생성비용 많이듬 -> 요청처리 시간 늘어남
=> 해결 : Thread Pool
Thread Pool : thread를 허용된 개수 안에서 사용 -> 동시요청을 안정적으로 처리
- 자바에서의 Thread Pool :
Thread Pool Executor
- max pool size : 최대 스레드 수
- core pool size : 최소 스레드 수
- keep alive time : 이 시간 후에 요청 없으면 스레드 없어짐, 최소 core pool size만큼은 유지
- Tomcat의 Thread Pool
- max connections : Tomcat이 최대로 동시에 처리할 수 있는 커넥션(요청)의 개수
- accept count : max connections이상의 요청이 들어왔을 때 사용하는 대기큐의 사이즈
- max connections + accept count 이상의 요청이 들어오면 거절될 수 있음