197 lines
3.1 KiB
Go
197 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
const (
|
|
keycloakURL = "https://auth.cloud.osnawiki.de/realms/myrealm"
|
|
|
|
clientID = "go-demo"
|
|
clientSecret = "FIyBmiHK5R3Yt2LG6ljgN8eO97eMiglXyLCjEfciFGI1Om6Wif0G0Ym2J9Yu17rMaOlYcLtXN1cM0KJbkpSmSG"
|
|
|
|
redirectURL = "https://oidc-demo.cloud.osnawiki.de/callback"
|
|
)
|
|
|
|
var (
|
|
oauthConfig *oauth2.Config
|
|
verifier *oidc.IDTokenVerifier
|
|
)
|
|
|
|
func main() {
|
|
|
|
ctx := context.Background()
|
|
|
|
// OIDC Discovery (.well-known/openid-configuration)
|
|
provider, err := oidc.NewProvider(ctx, keycloakURL)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
verifier = provider.Verifier(
|
|
&oidc.Config{
|
|
ClientID: clientID,
|
|
},
|
|
)
|
|
|
|
oauthConfig = &oauth2.Config{
|
|
ClientID: clientID,
|
|
ClientSecret: clientSecret,
|
|
Endpoint: provider.Endpoint(),
|
|
RedirectURL: redirectURL,
|
|
Scopes: []string{
|
|
"openid",
|
|
"profile",
|
|
"email",
|
|
},
|
|
}
|
|
|
|
http.HandleFunc("/", home)
|
|
http.HandleFunc("/login", login)
|
|
http.HandleFunc("/callback", callback)
|
|
http.HandleFunc("/logout", logout)
|
|
|
|
log.Println("running :8080")
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
}
|
|
|
|
|
|
// Startet OIDC Login
|
|
func login(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// CSRF-Schutz: State erzeugen
|
|
state := randomString()
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: "oidc_state",
|
|
Value: state,
|
|
Path: "/",
|
|
})
|
|
|
|
url := oauthConfig.AuthCodeURL(
|
|
state,
|
|
)
|
|
|
|
http.Redirect(w, r, url, http.StatusFound)
|
|
}
|
|
|
|
|
|
// Callback von Keycloak
|
|
func callback(w http.ResponseWriter, r *http.Request) {
|
|
|
|
stateCookie, _ := r.Cookie("oidc_state")
|
|
|
|
if stateCookie == nil ||
|
|
stateCookie.Value != r.URL.Query().Get("state") {
|
|
http.Error(w, "invalid state", 400)
|
|
return
|
|
}
|
|
|
|
|
|
token, err := oauthConfig.Exchange(
|
|
r.Context(),
|
|
r.URL.Query().Get("code"),
|
|
)
|
|
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
|
|
|
|
rawIDToken, ok := token.Extra("id_token").(string)
|
|
|
|
if !ok {
|
|
http.Error(w, "missing id_token", 500)
|
|
return
|
|
}
|
|
|
|
|
|
idToken, err := verifier.Verify(
|
|
r.Context(),
|
|
rawIDToken,
|
|
)
|
|
|
|
if err != nil {
|
|
http.Error(w, "invalid token", 500)
|
|
return
|
|
}
|
|
|
|
|
|
// Userinformationen aus Token lesen
|
|
var claims struct {
|
|
Username string `json:"preferred_username"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
idToken.Claims(&claims)
|
|
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: "user",
|
|
Value: claims.Username,
|
|
Path: "/",
|
|
})
|
|
|
|
|
|
http.Redirect(w, r, "/", 302)
|
|
}
|
|
|
|
|
|
func home(w http.ResponseWriter, r *http.Request) {
|
|
|
|
cookie, err := r.Cookie("user")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprint(w, `
|
|
<h1>OIDC Demo</h1>
|
|
<a href="/login">
|
|
<button>Anmeldung über OIDC</button>
|
|
</a>
|
|
`)
|
|
|
|
return
|
|
}
|
|
|
|
|
|
fmt.Fprintf(w, `
|
|
<h1>Hallo %s</h1>
|
|
|
|
<form action="/logout" method="post">
|
|
<button>Abmelden</button>
|
|
</form>
|
|
`, cookie.Value)
|
|
}
|
|
|
|
|
|
func logout(w http.ResponseWriter, r *http.Request) {
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: "user",
|
|
Value: "",
|
|
Path: "/",
|
|
MaxAge: -1,
|
|
})
|
|
|
|
http.Redirect(w, r, "/", 302)
|
|
}
|
|
|
|
|
|
// kleiner State Generator
|
|
func randomString() string {
|
|
|
|
b := make([]byte, 16)
|
|
rand.Read(b)
|
|
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
}
|