[GO] Implementieren Sie OAuth ohne Client-Bibliothek (Java)

Ich weiß nicht, wie viele Biere es sind, aber dies ist ein Artikel über die Verwendung von OAuth. Es gibt in Java so viele OAuth-Client-Bibliotheken wie Sterne, einschließlich der von Google, aber hier werde ich OAuth berühren, ohne die Client-Bibliothek zum Lernen zu verwenden. Ich denke, es gibt viele gute Artikel über den Mechanismus von OAuth selbst, deshalb werde ich es hier nicht erklären.

Was ist in diesem Artikel zu tun?

Verweise

Using OAuth 2.0 for Web Server Applications OpenID Connect

OAuth

Vorbereitung

Sie müssen Ihre OAuth-Anmeldeinformationen bei Google registrieren.

Developers Console

Erstellen Sie Anmeldeinformationen -> OAuth-Client-ID -> Webanwendung

Der Name ist angemessen. Http: // localhost: 8080 / auth wurde zum genehmigten Umleitungs-URI hinzugefügt. Der hier hinzugefügte Wert ist der Wert, der als Umleitungsziel der Anforderung verwendet werden kann. Dieses Mal werden wir es nur für die Entwicklung verwenden, geben Sie also localhost an. Es sollte nicht in der Produktion verwendet werden.

Abhängigkeiten

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.3.RELEASE")
    }
}

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

dependencies {
    compile(
            'org.springframework.boot:spring-boot-devtools',
            'org.springframework.boot:spring-boot-starter-web',
            'org.springframework.boot:spring-boot-starter-thymeleaf',

            'com.google.guava:guava:25.1-jre',
            'com.google.http-client:google-http-client:1.23.0',
            'com.google.http-client:google-http-client-gson:1.23.0',
            'com.google.code.gson:gson:2.8.5',
    )
}

Alles ist in Ordnung, aber dieses Mal werde ich Spring verwenden. ~~ Guava wird für Base64 verwendet und ~~ HttpClient wird zur Vereinfachung von HTTP-Anforderungen verwendet. Verwenden Sie Gson, um JSON zu analysieren.

Nachtrag: Base64 wird von Java 8 unterstützt Es ist völlig unscharf.

Anmeldebildschirm

Erstellen Sie einen Anmeldebildschirm.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>

<a th:href="${endpoint}">login</a>

</body>
</html>
private static final String clientId = checkNotNull( System.getenv( "CLIENT_ID" ) );

@GetMapping( "/login" )
public String login( Model model ) {

    var endpoint = "https://accounts.google.com/o/oauth2/v2/auth?"
                   + "client_id=" + clientId + "&"
                   + "redirect_uri="
                   + UrlEscapers.urlFormParameterEscaper().escape( "http://localhost:8080/auth" )
                   + "&"
                   + "access_type=" + "online" + "&"
                   + "state=" + "test_state" + "&"
                   + "response_type=" + "code" + "&"
                   + "scope="
                   + UrlEscapers.urlFormParameterEscaper().escape( "email profile" );
    model.addAttribute( "endpoint", endpoint );
    return "login";
}

"https: // accounts.google.com / o / oauth2 / v2 / auth" ist der Endpunkt für die Google OAuth-API. Legen Sie die erforderlichen Informationen im URL-Abfrageparameter fest.

Parameter
client_id Eine ID, die den Client identifiziert. Verwenden Sie den zuvor in der Entwicklerkonsole erhaltenen Wert. Hier wird es aus der Umgebungsvariablen erhalten.
redirect_uri Das Umleitungsziel. Verwenden Sie den in der Entwicklerkonsole festgelegten Wert.
access_type Dieses Mal werde ich also einen Browser verwendenonlineAngegeben.
state stateDer Wert selbst kann frei eingestellt werden. MeistensnonceEs wird für solche verwendet. Diesmal ist es einfach ein fester Wert.
response_type So erhalten Sie eine Antwort. Auf der ServerseitecodeEs ist repariert.
scope Geben Sie an, welche Informationen Sie mit dieser Anfrage erhalten können. HieremailundprofileIst eingestellt. Für verfügbare WerteDokumentSehen.

Wenn der Nutzer dem hier generierten Link folgt, wird er zum Google-Anmeldebildschirm weitergeleitet. Wenn Sie der Anwendung zum ersten Mal erlauben, auf Ihre Benutzerinformationen zuzugreifen, wird ein Bestätigungsdialogfeld angezeigt. Wenn der Benutzer den Anmeldevorgang abgeschlossen hat, wird er zu "redirect_url" umgeleitet. Sie können ein Zugriffstoken mit dem im Abfrageparameter der URL enthaltenen "Code" anfordern.

Holen Sie sich Token

@GetMapping( "/auth" )
public String auth( HttpServletRequest request, Model model ) throws IOException {

    var params = parseQueryParams( request );
    checkState( Objects.equals( params.get( "state" ), "test_state" ) );
    var code = URLDecoder.decode( checkNotNull( params.get( "code" ) ), StandardCharsets.UTF_8 );

    // ...

Holen Sie sich den Code und den Status aus den Abfrageparametern. state überprüft, ob der in der vorherigen Anforderung verwendete Wert unverändert zurückgegeben wird. Beachten Sie, dass Sie möglicherweise die URL dekodieren müssen.

var response = httpPost(
        "https://www.googleapis.com/oauth2/v4/token",
        Map.of(
                "code", code,
                "client_id", clientId,
                "client_secret", clientSecret,
                "redirect_uri", "http://localhost:8080/auth",
                "grant_type", "authorization_code"
        )
).parseAsString();
var responseAsMap = parseJson( response );

Senden Sie eine POST-Anfrage an die URL zur Token-Erfassung. Es war vor einiger Zeit v2, aber aus irgendeinem Grund ist dies v4. Es ist mir egal, weil es dem offiziellen Dokument entspricht.

Parameter
code Oben erhaltencodeWird gesendet wie es ist.
client_id Kunden ID.
client_secret Kundengeheimnis. Legen Sie wie die ID die in der Entwicklerkonsole angegebene fest.
redirect_uri Dies ist kein Weiterleitungsziel, sondern dient zur Überprüfung. In der Konsole festlegen und das in der vorherigen Anforderung verwendete Umleitungsziel erneut festlegen.
grant_type Fester Wertauthorization_code

Wenn die Anforderung als erfolgreich erkannt wird, wird ein JWT-formatiertes Token zurückgegeben.

ID-Token-Analyse

JSON Web Token ist in drei Teile unterteilt: Header, Body und Signatur, die durch "." Getrennt sind. Holen Sie sich also jeden Teil.

var idTokenList = Splitter.on( "." ).splitToList( idToken );

var header = parseJson( base64ToStr( idTokenList.get( 0 ) ) );
var body = parseJson( base64ToStr( idTokenList.get( 1 ) ) );
var sign = idTokenList.get( 2 );

Der Header und die Körperteile sind Base64-codiertes JSON. Wenn Sie das Ergebnis nicht überprüfen, können Sie den Wert damit erhalten. Da dies jedoch eine große Sache ist, überprüfen wir auch das Token.

Hol den Schlüssel

Holen Sie sich den öffentlichen Google-Schlüssel zur Überprüfung. Den Schlüsselspeicherort erhalten Sie unter "https: // accounts.google.com / .well-unknown / openid-configuration".

var response = httpGet( "https://accounts.google.com/.well-known/openid-configuration" ).parseAsString();
var responseAsMap = parseNestedJson( response );
var jwks = responseAsMap.get( "jwks_uri" ).toString();

Senden Sie eine GET-Anforderung an diesen URI, um den JSON abzurufen.

var keyResponse = httpGet( jwks ).parseAsString();
var keysResponseObj = parseJsonAs( keyResponse, Keys.class );
return keysResponseObj.keys.stream()
                           .filter( k -> k.kid.equals( kid ) )
                           .findAny()
                           .orElseThrow();

Mehrere Verschlüsselungsschlüssel werden im Array "Schlüssel" gespeichert. Suchen Sie in der Kopfzeile des ID-Tokens nach einem "Kind", das mit dem "Kind" übereinstimmt.

var signature = Signature.getInstance( "SHA256withRSA" );
signature.initVerify( KeyFactory.getInstance( "RSA" ).generatePublic( new RSAPublicKeySpec(
        new BigInteger( 1, base64ToByte( n ) ),
        new BigInteger( 1, base64ToByte( e ) )
) ) );
signature.update( contentToVerify.getBytes( StandardCharsets.UTF_8 ) );
return signature.verify( base64ToByte( sign ) );

Konvertieren Sie den Schlüssel JSON in das Java-Format und stellen Sie sicher, dass er bekannt ist. Eigentlich sollte das Schlüsselformat auch von JSON bezogen werden, aber hier ist es ein fester Wert von "RSA".

Nach Bestätigung der Signatur besteht der nächste Schritt darin, die Parameter zu überprüfen.

checkState( Set.of( "https://accounts.google.com", "accounts.google.com" ).contains( body.get( "iss" ) ) );

Emittent, stellen Sie sicher, dass "iss" legitim ist. Für Google ist es entweder "https: // accounts.google.com" oder "accounts.google.com".

checkState( Objects.equals( body.get( "aud" ), clientId ) );

Stellen Sie sicher, dass die Zielgruppe "aud" mit der Client-ID übereinstimmt.

var now = System.currentTimeMillis();
checkState( now <= Long.parseLong( body.get( "exp" ) ) * 1000 + 1000 );
checkState( now >= Long.parseLong( body.get( "iat" ) ) * 1000 - 1000 );

Stellen Sie schließlich sicher, dass der Schlüssel innerhalb des Ablaufdatums liegt und die Signaturzeit gültig ist. Der Wert ist in Sekunden. Der Grund für das Addieren und Subtrahieren von 1000 besteht darin, die Zeitverzögerung mit dem Server zu absorbieren.

Parameter abrufen

Nachdem wir überprüft haben, ob das ID-Token gültig ist, erhalten wir den Wert. Hier werden die E-Mail-Adresse, der Name und die URL des Gesichtsbildes erfasst und im Modell festgelegt.

model.addAttribute( "email", id.get( "email" ) )
     .addAttribute( "name", id.get( "name" ) )
     .addAttribute( "picture", id.get( "picture" ) );
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Result</title>
</head>
<body>

<img th:src="${picture}">
<p th:text="'Email: ' + ${email}"></p>
<p th:text="'Name:  ' + ${name}"></p>

<a th:href="${back}">back</a>

</body>
</html>

Um die verschiedenen APIs von Google zu verwenden, verwenden Sie die in JWT enthaltenen "access_token" und "refresh_token", um jede Anfrage zu senden.

Funktionsprüfung

Gehen Sie zu localhost: 8080 / login.

1.PNG

2.PNG

Gehen Sie zu Google und authentifizieren Sie sich.

3.PNG

Ich habe das Gesichtsbild, die E-Mail-Adresse und den Namen erhalten.

Recommended Posts

Implementieren Sie OAuth ohne Client-Bibliothek (Java)
SELECT-Daten mithilfe der Client-Bibliothek mit BigQuery
[Python] Deep Learning: Ich habe versucht, Deep Learning (DBN, SDA) ohne Verwendung einer Bibliothek zu implementieren.
[GoogleCloudPlatform] Verwenden Sie die Google Cloud-API mit der API-Clientbibliothek
Überschussberechnung ohne Verwendung von%
Lesen Sie Dateien auf GCS mithilfe der Cloud Storage Client Library
[Django] Implementieren Sie eine Funktion zum Hochladen von Bilddateien, die kein Modell verwendet
Blasensortierung ohne Sortierung
Schreiben Sie FizzBuzz ohne "="
Schnelle Sortierung ohne Sortierung
Versuchen Sie, ein neuronales Netzwerk in Python aufzubauen, ohne eine Bibliothek zu verwenden