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