1. 학습일지
1) OAuth2.0
- 다양한 플랫폼 환경에서 권한 부여를 위한 산업 표준 프로토콜이다.
- 쉽게 말해 인증과 권한을 획득하는 것인데, 이러한 방식은 third party Application에 아이디와 비밀번호를 제공하고 싶지 않은 요구에 의해 탄생되었다.
- 다시 말해 인터넷 사용자들이 비밀번호를 제공하지 않고
다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의
접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는,
접근 위임을 위한 개방형 표준이다.
- 사용자가 애플리케이션에게 모든 권한을 넘기지 않고 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜이다.
2) Kakao 계정 로그인 구현하기 (OAuth2.0)
- 카카오 developers에 앱(웹) 정보 등록하기
- 카카오 인가 코드 받아오기
https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code
- 카카오 developers에 등록한 콜백 URI대로 API 작성하기
-
Controller는 콜백의 query에 담긴 code @RequestParam으로 받고 서비스로 넘겨주며, 루트로 리다이렉트한다.
-
Service는 크게 두가지 기능을 순차적으로 실행한다.(절차적 프로그래밍)
- 1)
인가 코드
로 액세스 토큰
요청
- 서버 대 서버 간 요청은 RestTemplate을 사용한다.
- header와 body에 필요한 값을 담아주고 요청을보낸다.
- 보낸 요청은 JSON 형태의 ResponseEntity로 돌아온다.
- 이를 ObjectMapper를 사용하여 JsonNode로 파싱하고
- 파싱된 JSON에서 "access_token"을 추출한다.
*ObjectMapper provides functionality for reading and writing JSON,
either to and from basic POJOs (Plain Old Java Objects),
or to and from a general-purpose JSON Tree Model (JsonNode),
as well as related functionality for performing conversions.
- 2) 받아온 액세스 토큰으로 카카오 API 호출
- header에 토큰을 담아주고 HTTP 요청을 보낸다.
- 역시 JSON 타입의 Response가 돌아오는데, body에 id, nickname, email 등 원하는 정보가 담겨있다.
- 하나씩 추출하여 변수에 담아주고 사용한다.
- 리팩토링
- service에 절차적 프로그래밍 방식으로 작성한 코드들 함수화하기.
- 계속해서 new연산을 통해 생성하는 객체들 @Bean으로 등록하여 재사용하기
- HttpHeaders , RestTemplate, ObjectMapper를 WebSecurityConfig에서 빈으로 등록한다.
- 이 후 service의 멤버변수로 넣어주고, @Autowired 붙은 생성자에 넣어줘 DI한다.
- 복습해보자면, 생성자에 new를 넣어 생성할 때마다 new를 해주는 것은 강한결합의 형태다.
- 따라서 등록된 빈을 가져와 사용하며(@Autowired를 통한 DI) 느슨한 결합의 형태로 바꾸는 것.
- 멤버 변수를 갖다 쓰며 재사용성을 높이고, 메모리 낭비를 줄인다.
- ❗❗❗ 단, 계속해서 재사용하기 때문에 headers의 경우 보내는 로직에 clear()를 추가해줘야 한다.
@Bean
public HttpHeaders httpHeaders() {
return new HttpHeaders();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
- 카카오 사용자 정보로 회원가입 설계하기
-
테이블 설계 옵션
- 1) 카카오 User 를 위한 테이블 (ex. KakaoUser) 을 하나 더 만든다
- 장점: 결합도가 낮아짐: 성격이 다른 유저 별로 분리하여 각 테이블의 변화가 서로 영향을 주지 않는다.
- 단점: 구현 난이도가 올라감: 기능 구현 시 회원 별로 다른 테이블을 참조해야 한다.
- 2) 기존 회원 테이블에 카카오 User 추가하기
- 장점: 구현이 단순해짐
- 단점: 결합도가 높아진다: 폼 로그인을 통해 카카오 로그인 사용자의 username, password로 입력해서 로그인한다면?
-
2번 째 방식을 택하여 구현했다.
-
-
문제는 username부분에 닉네임을 넣어서 카카오에서 같은 닉네임을 사용 시 중복된다.
- 즉, 중복을 허용하지 않는 username의 특성 상 올바르지 않은 타입이었다.
- 나는 이를 이메일의 아이디와 도메인 값을 섞어서 대체하였다.
- makeUserNameForKakao(email); 구현부.
private String makeUserNameForKakao(String email) {
String[] emailArr = email.split("@");
String emailId = emailArr[0];
String[] domainArr = emailArr[1].split(".");
String domainVal = Character.toString(domainArr[0].charAt(0))
+ Character.toString(domainArr[0].charAt(domainArr[0].length()-1));
return emailId + domainVal;
}
-
DB에서 해당 kakaoId를 가진 회원 정보가 없다면 카카오에서 받은 정보를 기반으로 회원가입.
- 카카오 로그인 시 강제 로그인 구현
- 로그인 성공 시 사용자 정보(UserDetails)는 SecurityContext에 저장된다.
- (User ,UserDetails , UsernamePasswordAuthenticationToken , SecurityContext 작은 순서대로 나열)
UserDetails userDetails = new UserDetailsImpl(kakaoUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
- 처리 과정
- 1) UserDetails 생성하여 파라미터에 User 넣어주기
- 2) UsernamePasswordAuthenticationToken 생성하여 파라미터에 UserDetails, null(credentials), userDetails.getAuthorities() 넣기
- 3) SecurityContextHolder에서 getContext()로 컨텍스트 가져와서 UsernamePasswordAuthenticationToken set해주기
3) JWT 로그인 구현하기
-
배경 지식
-
JWT 사용!
-
로그인 정보를 Server 에 저장하지 않고, Client 에 로그인정보를 JWT 로 암호화하여 저장 → JWT 통해 인증/인가
-
모든 서버에서 동일한 Secret Key 소유
-
Secret Key를 통한 암호화 / 위조 검증 (복호화 시)
-
장점
- 동시 접속자가 많을 경우 서버 측 부하를 낮춘다.
- Client, Server가 다른 도메인을 사용할 때 ( 카카오 OAuth2 로그인도 JWT를 사용한다.)
-> 도메인이 다르면 CORS 문제가 생길 수도 있는데 JWT는 이를 해결할 수 있다.
-
단점
- 구현의 복잡도 증가
- JWT에 담은 내용이 커진다면 네트워크 비용이 증가한다. (클라이언트 -> 서버)
- 기생성된 JWT를 일부만 만료시킬 방법이 없다.
- Secret Key 유출 시 JWT 조작 가능
- JWT 로그인 이해
- 1) JWTAuthFilter: API 요청 Header에 전달되는 JWT 유효성 인증
- 모든 API에 대해 JWTAuthFilter가 JWT 확인
- 로그인 전 허용이 필요한 API는 예외처리 필요 -> FilterSkipMatcher
- 2) FormLoginFilter: 회원 폼 로그인 요청 시 username / password 인증
2. 코멘트
- 오늘은 스프링 심화 강의 2주차까지 수강했다. OAuth2.0 방식으로 카카오 계정으로 내가 만든 애플리케이션에 로그인하는 것을 구현해봤는데, 신선한 경험이었다. 로우코드 플랫폼으로 SSO를 구현할 때는 SAML 방식으로 구현했었는데, OAuth 방식으로도 구현해보며 조금 더 공부한 것 같다.
- 아직 이해만 한 수준이지 자유 자재로 스프링 시큐리티 부터 SSO까지 구현 가능한 수준은 아닌 것 같다. 복습하고 응용해보며 익숙해지자
- 추가적으로 마지막 강의에서 JWT에 대한 설명과 더불어 코드를 주셨는데, 논리 흐름에 따라 해독해나가는건 과제인 것 같다. 한참을 여러 파일을 읽어보며 해석해보려 했지만, 애초에 어떠한 논리 흐름인지 파악 못해서 불가능했다.
- 내일은 3주차 강의를 시작해보며, JWT에 대한 공부를 마저 할 예정이다.
- 목표는 나만의 셀렉샵에 스프링 시큐리티 + JWT 적용 시키기! 화이팅!