Imports System Imports System.IO Imports System.Net.Http Imports System.Text Imports System.Web Imports Newtonsoft.Json ' NuGet: Newtonsoft.Json (>= 13.x) Public Class cRelayHubToken ' ======= KONFIG ======= Private Shared ReadOnly TOKEN_ENDPOINT As String = "https://dev-kc.singlewindow.io/auth/realms/agsw/protocol/openid-connect/token" Private Shared ReadOnly CLIENT_ID As String = "agsw-admin" ' Gewünscht: Zugangsdaten in der Klasse definieren Private Shared ReadOnly USERNAME As String = "andreas.test@test.com" Private Shared ReadOnly PASSWORD As String = "Password.123" ' Token-File pro Benutzer unter %AppData% Private Shared ReadOnly StorePath As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RelayHub", "token.json") ' Sicherheitspuffer, bevor wir erneuern (Sekunden) Private Const ExpirySkewSeconds As Integer = 60 ' ======= ÖFFENTLICHE API ======= ''' ''' Liefert einen gültigen Access Token (nie Leerstring). ''' Public Shared Function GetAccessToken() As String Dim store = LoadStore() ' 1) Wenn wir einen (noch) gültigen Token haben If store IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(store.AccessToken) Then If store.ExpiresAtUtc > DateTimeOffset.UtcNow.AddSeconds(ExpirySkewSeconds) Then Return store.AccessToken End If End If ' 2) Versuche Refresh, falls vorhanden If store IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(store.RefreshToken) Then store = TryRefresh(store.RefreshToken) End If ' 3) Fallback: Password-Grant Login If store Is Nothing OrElse String.IsNullOrWhiteSpace(store.AccessToken) Then store = PasswordLogin() End If ' Validierung If store Is Nothing OrElse String.IsNullOrWhiteSpace(store.AccessToken) Then Throw New ApplicationException("Konnte keinen gültigen Access Token erhalten (leer).") End If ' Persistieren & zurück SaveStore(store) Return store.AccessToken End Function ' ======= INTERNES ======= Private Shared Function PasswordLogin() As TokenStore Dim form = New Dictionary(Of String, String) From { {"grant_type", "password"}, {"username", USERNAME}, {"password", PASSWORD}, {"client_id", CLIENT_ID} } Dim resp = PostForm(form) Dim token = ParseTokenResponse(resp) Return token End Function Private Shared Function TryRefresh(refreshToken As String) As TokenStore Try Dim form = New Dictionary(Of String, String) From { {"grant_type", "refresh_token"}, {"refresh_token", refreshToken}, {"client_id", CLIENT_ID} } Dim resp = PostForm(form) Dim token = ParseTokenResponse(resp) ' Nur speichern, wenn ein Access Token vorhanden ist If Not String.IsNullOrWhiteSpace(token.AccessToken) Then SaveStore(token) Return token End If Catch ex As Exception ' Ignorieren -> fällt auf PasswordLogin zurück End Try Return Nothing End Function Private Shared Function PostForm(formFields As Dictionary(Of String, String)) As String Using client As New HttpClient() Using content As New FormUrlEncodedContent(formFields) Dim response = client.PostAsync(TOKEN_ENDPOINT, content).Result Dim body = response.Content.ReadAsStringAsync().Result If Not response.IsSuccessStatusCode Then Throw New ApplicationException( $"Token-Endpoint Fehler ({CInt(response.StatusCode)}): {body}") End If Return body End Using End Using End Function Private Shared Function ParseTokenResponse(json As String) As TokenStore Dim r = JsonConvert.DeserializeObject(Of TokenResponse)(json) If r Is Nothing OrElse String.IsNullOrWhiteSpace(r.access_token) Then Throw New ApplicationException("Token-Antwort ungültig oder ohne access_token.") End If Dim now = DateTimeOffset.UtcNow Dim expiresIn = If(r.expires_in <= 0, 3600, r.expires_in) ' Fallback 1h Dim store = New TokenStore With { .AccessToken = r.access_token.Trim(), .RefreshToken = If(r.refresh_token, String.Empty), .ExpiresAtUtc = now.AddSeconds(expiresIn) } ' === Konsolen-Ausgabe === Console.WriteLine("== Neuer Token erhalten ==") Console.WriteLine("Access Token: " & store.AccessToken) Console.WriteLine("Refresh Token: " & store.RefreshToken) Console.WriteLine("Gültig bis UTC: " & store.ExpiresAtUtc.ToString("yyyy-MM-dd HH:mm:ss")) Return store End Function ' ======= PERSISTENZ ======= Private Shared Function LoadStore() As TokenStore Try If File.Exists(StorePath) Then Dim json = File.ReadAllText(StorePath, Encoding.UTF8) Dim s = JsonConvert.DeserializeObject(Of TokenStore)(json) ' Ausgabe in Konsole If s IsNot Nothing Then Console.WriteLine("== Token aus Datei geladen ==") Console.WriteLine("Access Token: " & (If(String.IsNullOrWhiteSpace(s.AccessToken), "", s.AccessToken))) Console.WriteLine("Refresh Token: " & (If(String.IsNullOrWhiteSpace(s.RefreshToken), "", s.RefreshToken))) Console.WriteLine("Gültig bis UTC: " & s.ExpiresAtUtc.ToString("yyyy-MM-dd HH:mm:ss")) End If Return s Else ' Datei existiert nicht -> Info ausgeben Console.WriteLine("Keine Token-Datei vorhanden, neuer Login erforderlich.") End If Catch ex As Exception Console.WriteLine("Fehler beim Laden der Token-Datei: " & ex.Message) ' Datei defekt? -> Ignorieren, neu holen End Try Return Nothing End Function Private Shared Sub SaveStore(store As TokenStore) Try Dim dir = Path.GetDirectoryName(StorePath) If Not Directory.Exists(dir) Then Directory.CreateDirectory(dir) ' Datei immer neu schreiben/überschreiben Dim json = JsonConvert.SerializeObject(store, Formatting.Indented) File.WriteAllText(StorePath, json, Encoding.UTF8) Console.WriteLine("Token-Datei gespeichert: " & StorePath) Catch ex As Exception Console.WriteLine("Fehler beim Speichern der Token-Datei: " & ex.Message) End Try End Sub ' ======= DTOs ======= Private Class TokenResponse Public Property access_token As String Public Property expires_in As Integer Public Property refresh_expires_in As Integer Public Property refresh_token As String Public Property token_type As String Public Property scope As String ' … weitere Felder bei Bedarf End Class Private Class TokenStore Public Property AccessToken As String Public Property RefreshToken As String Public Property ExpiresAtUtc As DateTimeOffset End Class End Class