- oauth2는 많은 업계에서 사용중인 인증과 인가를 위한 프로토콜이다.
- 트위터 앱을 사용중인 유저들이 자신들의 트위터 사용 기록을 분석하고 싶어한다.
- 트위터 Analizer라는 웹 어플리케이션을 개발하려한다.
- 트위터 Analizer는 트위치 내 유저의 모든 정보가 필요하다.
- 트위터 Analizer는 트위치에 유저 정보를 요구하기위해 유저의 인증관련 정보가 필요하다.
- 유저는 트위치 Analizer에 자신의 인증 정보를 모두 공유하고 트위치 Analizer는 그 정보를 통해 트위치에서 유저 관련 정보를 받아왔다.
- 하지만 본인의 인증 정보를 제 3어플리케이션에 공유하는 것이기에, 만약 해당 웹이 불법 사이트였을 경우 자신의 정보가 악용될 가능성이 있었다.
- 그렇기에 인증 정보는 공유하기 않고 제한적인 접근을 허용하기 위해 나온 것이 OAUTH2이다.
- 인증 토큰을 트위치 Analizer에게 공유하고 토큰을 통해 트위터 정보에 접근할 수 있는것이다.

- 위의 예시와 같이 한 개의 회사에서 여러가지 어플리케이션을 서비스하고 있는 상황을 생각해보자
- 구글은 별개의 인증 서버를 운용하며 모든 인증은 해당 인증서버를 통해서만 이뤄진다.
- 구글의 모든 어플리케이션들은 동일한 토큰을 공유하며, 어느 어플리케이션에 접근하던 동일한 토큰을 동일한 인증서버에 보내면 되니 재로그인이 필요없어진다.
- OAUTH2는 프레임워크나 api 같은 것이 아닌 단순 프로토콜이다.
- OAUTH2는 특정 애플리케이션 정보에 접근하고 싶을 때 따라야하는 표준 규격이다.
- EX) 우리가 만들 사진 어플리케이션이 구글 Photo 내의 사진에 접근해야 하는 경우
- 오직 read에 대한 권한만 부여함으로 사진을 보는 것 이외에 아무런 동작을 할 수 없게 만들면 된다.
- 액세스 토큰을 통하여 위의 기능을 구현한다.
- Resource owner: OAUTH2에서 엔드 유저는 Resource owner라고 불린다.
- Client: 위의 예시에서 트위터 Analizer 애플리케이션을 Client라고 부른다.
- Authorization Server: 유저에게 아이디와 비밀번호 권한등의 정보를 묻고 리소스 오너인지 판단하는 서버다.
- Resource Serve: 리소스들을 저장하고 있는 서버를 의미한다.위의 예시에선 트위터에 해당한다.
- Scopes: 허가해줄 권한의 범위를 의미한다. 위의 예시에서는 트위터 정보를 읽기만 가능하게 할 것이니 트윗 읽기, 사진 보기 등 권한이 scope에 해당한다.
- 트위터 Analizer를 구현한다고 생각해보자
-
- 우선 트위터 어플리케이션에 트위터 Analizer를 클라이언트로 등록해야한다. (웹 이름, 로고 등등)
- 트위터에서 클라이언트 ID와 클라이언트 시크릿 이라는 비밀번호를 발급해준다.
- 유저는 (트위터를 통해 회원가입하기) 버튼을 통해 트위터 계정으로 회원가입을 시도한다.
- 인증서버에서 유저에게 리소스 접근 권한을 허용하냐는 질문을 하고 승인했다면, 인증서버에서 클라이언트에게 액세스 토큰과 리프레쉬 토큰을 발급한다. (해당 토큰을 애플리케이션에 잘 보관해둬야 반복적으로 트위터를 통해 로그인할 필요가 없어진다.)
- 이제 트위터 Analizer는 리소스 소유자에게 /getTweets라는 api를 통해 리소스를 요청한다. 요청 시마다 액세스 토큰을 함께 보내줘야한다.
- 리소스 서버 즉 트위터는 액세스 토큰을 확인한다.
- 그럼 정보 읽기가 가능하다.
- OUATH2에는 여러가지 인증유형이 있고 우리가 웹 어플리케이션을 만들 때 그러한 여러가지 인증 유형중 하나를 선택해야한다.
- Authorization code: 엔드유저가 연관되어 있거나 두 개의 다른 어플리케이션이 서로 교류하려할 때 이 방법을 사용해야한다.
-
- 엔드유저가 리소스에 접근하고 싶다는 의사를 밝힌다.
- 클라이언트 어플리케이션은 유저의 권한이 필요하다는 정보를 보낸다.
- 유저는 인증 서버 로그인 페이지로 이동하게 된다.
- 로그인을 하면 인증서버는 액세스 토큰이 아닌 일시적인 Authorization code를 클라이언트 어플리케이션에 제공한다.
- 클라이언트 어플리케이션은 Authorization code를 인증 서버에 보내면서 유저의 인증 정보들도 함께 보낸다. 해당 정보들을 바탕으로 액세스 토큰을 발금해달라고 한다.
- 인증 서버는 정보들을 바탕으로 액세스 토큰을 클라이언트 어플리케이션에 반환해준다.
- 클라이언트 어플리케이션은 액세스 토큰을 리소스 서버에 보내면서 리소스요청을 한다.
- 액세스 토큰을 확인하고 리소스를 반환한다.
- 위의 2,3단계에서는 client_id, redirect_url, scope, state(csrf 토큰값), return_type등의 정보가 인증 서버에 보내진다.
- 인증서버는 Authorization code를 반환한다.
- 위의 5단계에서는 액세스 토큰을 발급받기 위해 인증서버에 Authorization code, client_id. client_secret, grant_type(Authorization_code의 유형이다를 명시), redirect_url의 정보가 인증 서버에 보내진다.
- 왜 두 번의 인증이 존재할까?
- 첫 번째 Authorization code 발급을 위한 인증은 유저가 인증서버에 유효한 유저임을 인증받는 과정이다.
- 두 번째 액세스 토큰 발급을 위한 인증은 해당 웹 어플리케이션이 Client로 등록된 웹이다 라는 것을 인증하기 위한 과정이다. 따라서 Client_secret이 두 번째 단계에서 전송된다.
- 위의 두 단계를 하나로 합친 implicit grant type이란 인증 타입이 존재하긴 하지만 보안성이 떨어지기 때문에 사용하지 않는다.
- 엔드유저가 회원가입 등의 요청을 클라이언트 어플리케이션으로 보냄
- 클라이언트 어플리케이션에서 엔드유저를 인증서버로 보냄
- 같이 보내는 정보는 response type과 client id, redirect url, scope, state의 정보를 함께 보냄
- 유저는 인증서버로 이동한 후 자신의 인증 정보를 입력함
- 유저에게 클라이언트 어플리케이션이 당신의 사진에 접근하려 한다 허용하겠냐? 등의 메세지가 표시된다.
- 인증서버는 Authorization code와 함께 state에 담겨있던 csrf 토큰을 같이 클라이언트 어플리케이션으로 보낸다.
- csrf 토큰에 오류가 없다면 무사히 수신된다.
- 클라이언트 어플리케이션은 post 유형으로 인증서버에 액세스 토큰을 요청한다.
- 같이 보내는 정보는 grant type, client id, client secret, redirect url, authorization code다.
- 인증서버에서 해당 정보들을 확인후 액세스 토큰을 반환한다.
- 같이 반환되는 정보에는 token 타입, 만료 시간, 액세스 토큰, scope, refresh 토큰이 있다.
- 엔드유저가 자신의 실제 인증 정보를 클라이언트 어플리케이션에게 보낸다.
- 클라이언트 어플리케이션이 post 요청으로 유저의 인증정보와 client id, client secret 정보를 인증 서버에 넘긴다.
- 인증 서버는 액세스 토큰을 바로 클라이언트 어플리케이션에 반환해준다.
- 클라이언트 어플리케이션은 액세스 토큰을 리소스 서버에 보내면서 리소스를 요청한다.
- 액세스 토큰이 적절하다면 리소스를 반환한다.
- 단점: 엔드유저가 실제 인증 정보를 애플리케이션과 공유한다. (OUATH2를 이용할 이유가 없다.)
- 클라이언트 어플리케이션이 인증 서버에 보내는 정보: client 아이디 및 시크릿, scope, username&password, grant type
- 클라이언트 어플리케이션, 리소스 서버, 인증 서버가 모두 같은 단체에 속해있을 경우 위와 같은 인증방식을 사용한다.
- 엔드유저가 연결돼있지 않은 두 백엔드 어플리케이션에 관한 상황에서 사용한다.
- 클라이언트 어플리케이션은 A회사에 소속, 인증 서버와 리소스 서버는 B회사에 소속된 상황이라 생각해보자. 클라이언트 어플리케이션이 B회사의 리소스 서버에서 자료를 가져오고 싶다면 인증 서버에서 인증을 성공한 후에 가져올 수 있을것이다.
- 클라이언트 어플리케이션에서 유저 세부사항을 인증 서버로 보낸다.(엔드유저는 관련돼있지 않다.)
- 인증과 관련된 정보들을 인증서버에서 확인한 후 클라이언트 어플리케이션에 액세스 토큰을 반환한다.
- 클라이언트 어플리케이션에서 액세스 토큰과 함께 리소스 요청을 리소스 서버에 보낸다.
- 액세스 토큰이 적절하다면 리소스를 반환해준다.
- 클라이언트 어플리케이션에서 인증 서버에 보내야하는 정보
- client id, client secret, scope, grant type
- grant type에 client_credentials를 적어서 보내면 엔드유저가 관련되지 않다는 것을 인증 서버는 이해할것이다.
- 해당 방식에서 state에 csrf 토큰을 넣어서 보내지 않는 이유는 브라우저와 통신이 없기 때문이다.(세션, 쿠키가 없으므로 사용자 정보를 바탕으로 csrf 공격을 할 수 없기 때문)
- 액세스 토큰의 기간이 만료됐을 때 리프레쉬 토큰을 다시 인증서버에 보낸다. 인증 서버는 리프레쉬 토큰을 확인한 후 새로운 액세스 토큰과 리프레쉬 토큰을 발급해준다.
- refresh token grant flow는 엔드유저가 관련되지 않는다. 액세스 토큰의 기간이 만료됐다면, 자동으로 서버에서 인증 서버로 refresh 토큰을 보낼것이기 때문이다.
- 리소스 서버에 액세스 토큰을 보내며 자원을 요청함
- 리소스 서버에서 토큰 만료된 것을 보고 403 에러를 반환함
- 클라이언트 어플리케이션은 인증서버에 리프레쉬 토큰을 보내며 새 액세스 토큰을 요청함
- 리프레쉬 토큰이 적절하다면 인증서버는 새 액세스 토큰과 리프레쉬 토큰을 반환한다.
- 새 리프레쉬 토큰은 브라우저나 데이터베이스에 저장한다.
- 새 액세스 토큰을 활용하여 리소스 서버에 리소스를 요청한다.
- 클라이언트 어플리케이션에서 인증서버에 보내야하는 정보
- client id, client secret, refresh token, scope, grant type
- grnat type에는 refresh_token 이라고 적어서 보내야 인증 서버에서 액세스 토큰과 리프레쉬 토큰을 새로 발급해준다.
- 인증 서버와 리소스 서버가 api 소통을 하는 것이다. 액세스 토큰이 들어오면 해당 토큰을 인증 서버에 보내며 유효성을 검사한다. (액세스 토큰이 들어올 때마다 인증 서버와 소통해야 하므로 효율이 약간 떨어진다.)
- 인증 서버와 리소스 서버가 동일한 데이터베이스를 공유하는 것이다. 인증서버에서 액세스 토큰을 발급해주면 데이터베이스에 활성화 표시를 해주고, 액세스 토큰이 리소스 서버에 들어오면 리소스 서버는 쿼리를 날려 활성화 상태인지 아닌지를 판단하면 된다.
- 어플리케이션이 시작할 때 리소스 서버는 인증 서버에게 공개 인증서를 발급받아둔다. 나중에 액세스 토큰이 들어올때 마다 리소스 서버는 공개 인증서를 통하여 액세스 토큰의 진위 여부를 판단한다. (현재 가장 많이 사용되는 방법이며 맨 처음 인증 서버와 리소스 서버의 연결이 된 이후 지속적인 소통이 필요 없기에 효율적이다.)
- Oauth2는 유저의 정보가 어떤지 유저의 이메일은 무엇인지에 관한 정보는 파악할 수 없다.
- Oauth2의 구조물을 그대로 사용하면서 유저의 세부 정보를 받고 싶었던 개발자들은 Ouath2 기반에서 사용 가능한 방법인 OpenID Connect를 개발했다.
- 인증 서버와 OICD로 연결을 시도하면 인증서버에선 액세스 토큰과 ID 토큰을 반환한다. (리프레쉬 토큰 제외)
- 여기서 ID 토큰이 유저의 세부 정보에 관한 권한을 관리한다.
- 우리가 반환값으로 ID 토큰을 받으려면 scope에 OpenID 라는 값을 보내야한다.
- 액세스 토큰과 ID 토큰의 조합을 통해 세부적인 유저 권한 관리가 가능해졌고, 그에따라 나타난 개념이 IAM 이다.
- OIDC를 활용하면 scope에 profile, email, address 같은 값을 넣을 수 있고, jwt 토큰에 해당 정보들을 담아 교환할 수 있으며, /userinfo라는 공개 엔드 포인트를 통해 유저 정보를 쉽게 파악할 수도 있다.
- 인증서버에서 반환된 openid 토큰을 확인해보면 바디 부분에 base64로 우리가 scope에 담아 요청한 유저의 세부 정보가 인코딩 된 것을 확인할 수 있다.
- 우선 spring-boot-starter-oauth2-client 다음과 같은 의존성을 추가해야 한다. 이를 통해 클라이언트 어플리케이션이라는 사실을 알려주는것이다.
- 그리고 config의 defalutSecurityFilterChain에서 http.authorizeRequests().anyRequest().authenticated().and().oauth2Login();
- 을 통해서 ouath2 방식의 로그인을 지원한다. 모든 url에 대하여 인증 서버로 이동 시켜준다.
/*@Bean
public ClientRegistrationRepository clientRepository() {
ClientRegistration clientReg = clientRegistration();
return new InMemoryClientRegistrationRepository(clientReg);
}
private ClientRegistration clientRegistration() {
return CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("8cf67ab304dc500092e3")
.clientSecret("6e6f91851c864684af2f91eaa08fb5041162768e").build();
}*/
- 다음과 같이 clientRegistration을 통해 client 정보를 리턴해주고 리턴된 정보는 ClientRegistrationRepository에 저장시켜 클라이언트 정보를 인증 서버에 전달할 수 있게 만들어준다.
- CommonOAuth2Provider은 열거형 클래스로 내부의 getBuilder를 통해 scope,autorizationUrl, tokenUrl등등의 정보를 우리가 직접 입력하지않고 활용할 수 있다.
- 우리는 단순히 getBuolder를 호출하면서 어떤 인증 서버인지와 클라이언트 정보만 넘겨주면 된다.
- 위의 방법 말고 더 간단한 방법도 있다. application.properties에
spring.security.oauth2.client.registration.github.client-id=8cf67ab304dc500092e3
spring.security.oauth2.client.registration.github.client-secret=6e6f91851c864684af2f91eaa08fb5041162768e
- 위와 같이 클라이언트 정보를 넣어두면 자동으로 클라이언트 정보를 파악해서 인증 서버에 넘겨준다.
- 깃허브 말고 페이스북이나 구글을 활용하고 싶다면, 위 코드의 github 부분만 바꿔주면 된다.
'spring > spring security' 카테고리의 다른 글
[Spring Security] Oauth 2.0 카카오 소셜 로그인 구현하기 1. (소셜 로그인 흐름 확인하기) (0) | 2024.07.28 |
---|---|
스프링 시큐리티 기본 개념공부 11. (Oauth2 실습) (0) | 2024.07.18 |
스프링 시큐리티 기본 개념공부 9. (메소드 레벨 보안이란?) (0) | 2024.07.18 |
스프링 시큐리티 기본 개념공부 8. (JWT 토큰의 개념과 사용 방법) (1) | 2024.06.16 |
Spring Security 기본 개념 공부 7. (사용자 커스텀 필터 작성 방법) (0) | 2024.06.01 |