first commit
This commit is contained in:
commit
4d46891323
1 changed files with 382 additions and 0 deletions
382
README.md
Normal file
382
README.md
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
# ✅ Parte 1: Guida completa per installare e configurare Keycloak come server OAuth2/OIDC locale
|
||||||
|
|
||||||
|
## Step 1: Scarica e avvia Keycloak
|
||||||
|
|
||||||
|
Vai su (https://www.keycloak.org/downloads)
|
||||||
|
|
||||||
|
Scarica la versione Quarkus distribution.
|
||||||
|
|
||||||
|
Avvia in modalità sviluppo:
|
||||||
|
|
||||||
|
./bin/kc.sh start-dev
|
||||||
|
|
||||||
|
Keycloak sarà disponibile su:
|
||||||
|
|
||||||
|
http://localhost:8080
|
||||||
|
|
||||||
|
## Step 2: Configura Realm e Client
|
||||||
|
|
||||||
|
Accedi alla console admin:
|
||||||
|
|
||||||
|
http://localhost:8080/admin.
|
||||||
|
|
||||||
|
Crea un Realm (es. flutter-realm).
|
||||||
|
|
||||||
|
Crea un Client:
|
||||||
|
|
||||||
|
Tipo: Public
|
||||||
|
|
||||||
|
Nome: flutter-app
|
||||||
|
|
||||||
|
Redirect URI:
|
||||||
|
|
||||||
|
com.tuaapp:/oauthredirect
|
||||||
|
|
||||||
|
|
||||||
|
Abilita Standard Flow (Authorization Code).
|
||||||
|
|
||||||
|
Salva il Client ID.
|
||||||
|
|
||||||
|
## Step 3: Configura utenti
|
||||||
|
|
||||||
|
Vai su Users → Aggiungi utente → Imposta credenziali.
|
||||||
|
|
||||||
|
Ora hai un utente locale per il login.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 4: Sicurezza
|
||||||
|
|
||||||
|
In locale puoi usare HTTP, ma in produzione serve HTTPS.
|
||||||
|
|
||||||
|
Abilita PKCE per sicurezza (Keycloak lo supporta di default).
|
||||||
|
|
||||||
|
# ✅ Parte 2: Esempio Flutter che si collega a Keycloak locale
|
||||||
|
|
||||||
|
Dipendenze
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dependencies:
|
||||||
|
flutter_appauth: ^6.0.0
|
||||||
|
flutter_secure_storage: ^9.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Codice
|
||||||
|
|
||||||
|
```sh
|
||||||
|
import 'package:flutter_appauth/flutter_appauth.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
final FlutterAppAuth appAuth = FlutterAppAuth();
|
||||||
|
final storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
Future<void> login() async {
|
||||||
|
final AuthorizationTokenResponse? result = await appAuth.authorizeAndExchangeCode(
|
||||||
|
AuthorizationTokenRequest(
|
||||||
|
'flutter-app', // Client ID
|
||||||
|
'com.tuaapp:/oauthredirect', // Redirect URI
|
||||||
|
issuer: 'http://localhost:8080/realms/flutter-realm',
|
||||||
|
scopes: ['openid', 'profile', 'email'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
await storage.write(key: 'access_token', value: result.accessToken);
|
||||||
|
await storage.write(key: 'id_token', value: result.idToken);
|
||||||
|
print('Login OK: ${result.accessToken}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Refresh Token
|
||||||
|
```sh
|
||||||
|
Future<void> refreshToken() async {
|
||||||
|
final refreshToken = await storage.read(key: 'refresh_token');
|
||||||
|
if (refreshToken != null) {
|
||||||
|
final TokenResponse? response = await appAuth.token(
|
||||||
|
TokenRequest(
|
||||||
|
'flutter-app',
|
||||||
|
'com.tuaapp:/oauthredirect',
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
issuer: 'http://localhost:8080/realms/flutter-realm',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response != null) {
|
||||||
|
await storage.write(key: 'access_token', value: response.accessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# ✅ Dockerfile per Keycloak
|
||||||
|
Questo crea un container con Keycloak in modalità sviluppo:
|
||||||
|
|
||||||
|
Dockerfile
|
||||||
|
```sh
|
||||||
|
FROM quay.io/keycloak/keycloak:24.0.4
|
||||||
|
|
||||||
|
# Imposta la modalità sviluppo
|
||||||
|
ENV KC_DB=dev-mem
|
||||||
|
ENV KC_HTTP_PORT=8080
|
||||||
|
ENV KC_HOSTNAME=localhost
|
||||||
|
|
||||||
|
# Avvia Keycloak in modalità sviluppo
|
||||||
|
```
|
||||||
|
Build e Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build -t keycloak-local .
|
||||||
|
docker run -p 8080:8080 keycloak-local
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ docker-compose.yml
|
||||||
|
Se vuoi aggiungere persistenza e database (PostgreSQL):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:24.0.4
|
||||||
|
container_name: keycloak
|
||||||
|
environment:
|
||||||
|
KC_DB: postgres
|
||||||
|
KC_DB_URL_HOST: postgres
|
||||||
|
KC_DB_URL_DATABASE: keycloak
|
||||||
|
KC_DB_USERNAME: keycloak
|
||||||
|
KC_DB_PASSWORD: keycloak
|
||||||
|
KC_HOSTNAME: localhost
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command:
|
||||||
|
- start-dev
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: keycloak
|
||||||
|
POSTGRES_USER: keycloak
|
||||||
|
POSTGRES_PASSWORD: keycloak
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Come usarlo
|
||||||
|
|
||||||
|
Salva il file come docker-compose.yml.
|
||||||
|
|
||||||
|
e avvia con
|
||||||
|
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
Accedi a Keycloak su http://localhost:8080.
|
||||||
|
|
||||||
|
Crea Realm, Client, e imposta il Redirect URI per la tua app Flutter.
|
||||||
|
|
||||||
|
|
||||||
|
## ✅ Script Bash: init-keycloak.sh
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Variabili
|
||||||
|
KEYCLOAK_URL="http://localhost:8080"
|
||||||
|
REALM_NAME="mio-realm"
|
||||||
|
CLIENT_ID="flutter-app"
|
||||||
|
REDIRECT_URI="com.tuaapp:/oauthredirect"
|
||||||
|
ADMIN_USER="admin"
|
||||||
|
ADMIN_PASS="admin"
|
||||||
|
|
||||||
|
# Login come admin
|
||||||
|
/opt/keycloak/bin/kcadm.sh config credentials \
|
||||||
|
--server $KEYCLOAK_URL \
|
||||||
|
--realm master \
|
||||||
|
--user $ADMIN_USER \
|
||||||
|
--password $ADMIN_PASS
|
||||||
|
|
||||||
|
# Crea Realm
|
||||||
|
/opt/keycloak/bin/kcadm.sh create realms \
|
||||||
|
-s realm=$REALM_NAME \
|
||||||
|
-s enabled=true
|
||||||
|
|
||||||
|
# Crea Client
|
||||||
|
/opt/keycloak/bin/kcadm.sh create clients -r $REALM_NAME \
|
||||||
|
-s clientId=$CLIENT_ID \
|
||||||
|
-s enabled=true \
|
||||||
|
-s publicClient=true \
|
||||||
|
-s redirectUris="[\"$REDIRECT_URI\"]" \
|
||||||
|
-s protocol=openid-connect \
|
||||||
|
-s standardFlowEnabled=true \
|
||||||
|
-s directAccessGrantsEnabled=true
|
||||||
|
|
||||||
|
echo "✅ Realm '$REALM_NAME' e Client '$CLIENT_ID' creati con successo!"
|
||||||
|
```
|
||||||
|
### ✅ Come usarlo con Docker
|
||||||
|
|
||||||
|
Copia lo script in una cartella.
|
||||||
|
|
||||||
|
Aggiorna docker-compose.yml aggiungendo un volume per montare lo script:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
volumes:
|
||||||
|
- ./init-keycloak.sh:/opt/keycloak/init-keycloak.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Dopo che il container è avviato, esegui:
|
||||||
|
|
||||||
|
docker exec -it keycloak bash /opt/keycloak/init-keycloak.sh
|
||||||
|
|
||||||
|
## Nginx config
|
||||||
|
|
||||||
|
```sh
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name keycloak.local;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/ssl/keycloak.crt;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/keycloak.key;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://keycloak:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name keycloak.local;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flutter
|
||||||
|
|
||||||
|
1. Dipendenze
|
||||||
|
|
||||||
|
Nel pubspec.yaml:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dependencies:
|
||||||
|
flutter_appauth: ^6.0.0
|
||||||
|
flutter_secure_storage: ^9.0.0
|
||||||
|
http: ^1.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configurazione Keycloak
|
||||||
|
|
||||||
|
Realm: mio-realm
|
||||||
|
|
||||||
|
Client: flutter-app
|
||||||
|
|
||||||
|
Redirect URI: com.tuaapp:/oauthredirect
|
||||||
|
|
||||||
|
Abilita:
|
||||||
|
|
||||||
|
standardFlowEnabled=true
|
||||||
|
|
||||||
|
directAccessGrantsEnabled=true (per registrazione via API)
|
||||||
|
|
||||||
|
3. Codice Flutter
|
||||||
|
|
||||||
|
Ecco il file auth_service.dart:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
import 'package:flutter_appauth/flutter_appauth.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
final FlutterAppAuth _appAuth = FlutterAppAuth();
|
||||||
|
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
|
||||||
|
|
||||||
|
final String clientId = 'flutter-app';
|
||||||
|
final String redirectUri = 'com.tuaapp:/oauthredirect';
|
||||||
|
final String issuer = 'https://keycloak.local/realms/mio-realm';
|
||||||
|
final List<String> scopes = ['openid', 'profile', 'email'];
|
||||||
|
|
||||||
|
Future<void> login() async {
|
||||||
|
final result = await _appAuth.authorizeAndExchangeCode(
|
||||||
|
AuthorizationTokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUri,
|
||||||
|
issuer: issuer,
|
||||||
|
scopes: scopes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
await _secureStorage.write(key: 'access_token', value: result.accessToken);
|
||||||
|
await _secureStorage.write(key: 'refresh_token', value: result.refreshToken);
|
||||||
|
await _secureStorage.write(key: 'id_token', value: result.idToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
await _secureStorage.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> register(String username, String password, String email) async {
|
||||||
|
final url = '$issuer/protocol/openid-connect/token';
|
||||||
|
// Usa le API Admin di Keycloak o un endpoint custom
|
||||||
|
// Qui esempio con Direct Access Grant (non consigliato in produzione)
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('$issuer/protocol/openid-connect/token'),
|
||||||
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
|
body: {
|
||||||
|
'client_id': clientId,
|
||||||
|
'grant_type': 'password',
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
await _secureStorage.write(key: 'access_token', value: data['access_token']);
|
||||||
|
await _secureStorage.write(key: 'refresh_token', value: data['refresh_token']);
|
||||||
|
} else {
|
||||||
|
throw Exception('Errore registrazione/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. UI di esempio
|
||||||
|
|
||||||
|
Nel main.dart:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'auth_service.dart';
|
||||||
|
|
||||||
|
void main() => runApp(MyApp());
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
final AuthService authService = AuthService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: AppBar(title: Text('Login & Registrazione')),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(onPressed: authService.login, child: Text('Login')),
|
||||||
|
ElevatedButton(onPressed: () => authService.register('user', 'pass', 'email@test.com'), child: Text('Registrati')),
|
||||||
|
ElevatedButton(onPressed: authService.logout, child: Text('Logout')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Loading…
Reference in a new issue