통합 코드 및 설정의 위치에 따라 두 가지 주요 통합 스타일이 존재한다.
embedded | proxied | |
---|---|---|
기술 스택 | 서드파티 라이브러리, 프레인워크, 웹 컨테이너, 어플리케이션 서버에서 제공 | nginx, apache 등 |
구현 방법 | 어플리케이션 내 특정 형태 코드 및 설정 구현 | 어플리케이션 앞단에 프록시 서버 구현 |
예시 | 특별히 외부 서비스를 관리하지 않고 간단하게 통합하는 경우 | 어플리케이션 코드를 제어할 수 없는 경우, 어플리케이션 앞단에 API 게이트웨이가 있는 경우 |
일반적으로 OIDC 관련 라이브러리가 다수 존재한다. 적절한 통합을 위해 다음과 같은 내용을 포함하고 있는지 확인한다.
https://openid.net/developers/certified-openid-connect-implementations/ 에 접속하면 인증된 라이브러리 목록을 확인할 수 있다.
ClientID: mywebapp
Root URL: 어플리케이션 루트 경로 (http://localhost:3000 등)
Client Authentication: Enabled
이후 Credentials 페이지에서 Client ID, Secret 을 복사하여 어플리케이션에 등록한다.
golang 어플리케이션은 PacktPublishing 레포에서 제공하는 해당 코드를 참고하였다. (필자는 포트 Keycloak 과 어플리케이션의 포트를 일부 변경함)
앞서 생성한 클라이언트의 Credential 을 아래 이미지와 같이 코드에 넣어준다.
...
func createConfig(provider oidc.Provider) (oidc.Config, oauth2.Config) {
oidcConfig := &oidc.Config{
ClientID: "mywebapp",
}
config := oauth2.Config{
ClientID: oidcConfig.ClientID,
ClientSecret: "CLIENT_SECRET", // change CLIENT_SECRET with the secret generated by Keycloak for the mywebapp client. This option is a string.
Endpoint: provider.Endpoint(),
RedirectURL: "http://localhost:3000/auth/callback",
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
return *oidcConfig, config
}
그리고 go run main.go
커맨드를 입력하면 golang 어플리케이션이 켜지고 http://localhost:3000
(포트 변경함)으로 접속하면 Keycloak 로그인 화면이 나온다. 로그인하면 어플리케이션에서 토큰을 토대로 렌더링한 페이지를 볼 수 있다.
이제 코드의 일부 메서드 분석해보자.
func createOidcProvider(ctx context.Context) *oidc.Provider {
provider, err := oidc.NewProvider(ctx, KEYCLOAK_REALM_URL)
if err != nil {
log.Fatal("Failed to fetch discovery document: ", err)
}
return provider
}
OIDC provider 를 생성하는 부분이다. Keycloak 의 주소를 담아 새로운 Provider 를 만들어준다
func createConfig(provider oidc.Provider) (oidc.Config, oauth2.Config) {
oidcConfig := &oidc.Config{
ClientID: "mywebapp",
}
config := oauth2.Config{
ClientID: oidcConfig.ClientID,
ClientSecret: "SECRET",
Endpoint: provider.Endpoint(),
RedirectURL: "http://localhost:3000/auth/callback",
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
return *oidcConfig, config
}
어플리케이션에서 Keycloak 에 접근하기 위해 사용할 클라이언트에 대한 정보(Client ID, Client Secret 등)를 제공한다.
func callbackHandler(resp http.ResponseWriter, req *http.Request) {
err := checkState(req, resp)
if err != nil {
redirectHandler(resp, req)
return
}
tokenResponse, err := exchangeCode(req)
if err != nil {
http.Error(resp, "Failed to exchange code", http.StatusBadRequest)
return
}
idToken, err := validateIDToken(tokenResponse, req)
if err != nil {
http.Error(resp, "Failed to validate id_token", http.StatusUnauthorized)
return
}
handleSuccessfulAuthentication(tokenResponse, *idToken, resp)
}
로그인을 한 뒤 호출되는 콜백이다. 사용자 로그인 후 Keycloak 에서 받은 인가코드로 토큰을 받고(exchangeCode
), ID Token 이 유효한지 확인한 뒤(validateIDToken
) 성공적으로 인증처리를 한다(handleSuccessfulAuthentication
).