Extension de services Authentication (existante)
Les extensions de services permettent une flexibilité totale dans le processus d’établissement de l’identité d’un utilisateur et de sa gestion au cours d’une session.
Comment les passerelles d’applications gèrent le trafic
Un peu de contexte est nécessaire à la compréhension du fonctionnement de l’authentification et de sa place dans le flux d’identité d’un utilisateur. Voici comment le trafic circule dans une AppGateway (en pseudocode).
func Serve(appGateway *app.AppGateway, rw http.ResponseWriter, req *http.Request) {
if !IsAuthenticated(){
Authenticate(resp, req)
}
LoadAttrs()
if IsAuthorized() {
SetHeaders()
Proxy()
}
}
Il s’agit d’une simplification. La mise en œuvre effective met en cache les informations relatives à la session par souci d’efficacité, y compris les résultats de la vérification de l’authentification et de l’autorisation. Ainsi, ces méthodes ne sont généralement pas sollicitées pour chaque requête, mais uniquement lors du premier passage de l’utilisateur.
Authentification
Comme le montre le pseudocode ci-dessus, l’authentification se compose en réalité de deux éléments. L’extension
de services isAuthenticatedSE
en langage yaml
permet de vérifier si une session est
actuellement authentifiée de manière appropriée pour une ressource requise. Si ce n’est pas le cas, l’extension de services
authenticateSE
en langage yaml
prend en charge l’interaction réelle avec l’utilisateur pour
authentifier la session. Veuillez noter que les deux extensions dépendent de la
clé d’authentification
.
AuthN ou AuthZ
L’authentification (AuthN) sert simplement à confirmer l’identité de l’utilisateur, et non à déterminer s’il dispose d’un accès. Un utilisateur peut être authentifié, mais se voir refuser l’accès au stade de l’autorisation (AuthZ).
Extension de services IsAuthenticated
Cette extension de services est relativement peu complexe : elle se contente généralement de vérifier l’authentification de la session d’un utilisateur.
Bien que cette extension de services utilise http.ResponseWriter
, elle n’envoie généralement
pas de réponse directement à l’utilisateur. D’autres extensions, telles que
authenticateSE
, gèrent l’interaction avec l’utilisateur.
Extension de services Authenticate
L’extension de services AuthenticateSE
permet de déterminer l’identité de l’utilisateur. Elle peut
accéder aux champs et aux méthodes de la structure AppGateway
qui sont destinés à faciliter
l’authentification. Par exemple, la liste des fournisseurs d’identité disponibles peut être utilisée pour
transférer le flux d’authentification à un fournisseur d’authentification externe spécifique.
Interaction avec l’utilisateur
L’extension de service authenticateSE
permet d’interagir avec l’utilisateur
afin d’établir son identité. Par exemple, l’utilisateur peut être redirigé vers un
fournisseur d’authentification externe, ou un formulaire lui demandant de saisir son
nom d’utilisateur et son mot de passe peut lui être soumis. Cela signifie que l’extension de services peut parfois être amenée à
traiter différentes requêtes, en présentant d’abord la page de connexion puis en traitant
le nom d’utilisateur et le mot de passe postés, ou en redirigeant vers Azure puis en traitant un jeton OIDC
lorsque l’utilisateur est renvoyé vers la page de connexion.
Exemples
Authentification contre le fournisseur d’identité
Dans cet exemple très simple, l’extension de services demande simplement à un fournisseur d’identité défini dans
la configuration de gérer la connexion
de l’utilisateur.
Dans ce cas, le connecteur Azure IDP se chargera de l’authentification nécessaire
à la session de l’utilisateur. Une fois l’identité de l’utilisateur établie, celui-ci est
redirigé vers l’URL initialement demandée. L’extension de services*
isAuthenticatedSE
ci-dessus renvoie alors une valeur « true », indiquant que l’utilisateur a
été authentifié, et le flux de l’utilisateur à travers l’AppGateway se poursuit.
appgateways:
- name: alpha
# ...
policies:
- location: /
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/auth.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/auth.go
authorization:
allowAll: true
/etc/maverics/extensions/auth.go
package main
import (
"errors"
"net/http"
"maverics/app"
"maverics/log"
"maverics/session"
)
// IsAuthenticated determines if the user has been authenticated by Azure.
func IsAuthenticated(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) bool {
log.Debug("msg", "determining if user is authenticated")
if session.GetString(req, "azure.authenticated") == "true" {
return true
}
return false
}
// Authenticate authenticates the user against Azure.
func Authenticate(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) error {
log.Debug("msg", "authenticating user")
azure, ok := ag.IDPs["azure"]
if !ok {
return errors.New("failed to find Azure IDP")
}
azure.CreateRequest().Login(rw, req)
return nil
}
Interaction avec l’utilisateur
Dans cet exemple, l’utilisateur se voit présenter un formulaire lui permettant de se connecter s’il n’est pas déjà authentifié. Les informations d’identification de l’utilisateur sont collectées puis validées par rapport à un cache d’informations d’identification local.
appgateways:
- name: alpha
# ...
policies:
- location: /
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/auth.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/auth.go
authorization:
allowAll: true
package main
import (
"errors"
"fmt"
"net/http"
"maverics/app"
"maverics/log"
"maverics/session"
)
// IsAuthenticated determines if the user has been authenticated by Azure.
func IsAuthenticated(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) bool {
log.Debug("msg", "determining if user is authenticated")
if session.GetString(req, "se.authenticated") == "true" {
return true
}
return false
}
// Authenticate authenticates the user by challenging the user for credentials.
func Authenticate(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) error {
log.Debug("msg", "authenticating user")
if req.Method != http.MethodPost && req.Method != http.MethodGet {
return errors.New("received unexpected request")
}
if req.Method == http.MethodPost {
err := req.ParseForm()
if err != nil {
return fmt.Errorf("failed to parse form: %w", err)
}
username := req.Form.Get("username")
password := req.Form.Get("password")
if username == "username" && password == "password" {
log.Debug("msg", "successfully logged in user")
session.Set(req, "se.authenticated", "true")
http.Redirect(rw, req, "/", http.StatusFound)
return nil
}
log.Debug(
"msg", "user entered invalid credentials",
"username", username,
)
}
_, _ = fmt.Fprintf(rw, htmlForm)
return nil
}
const htmlForm = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/sonar/reports" method="POST">
<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="password">Password:</label><br>
<input type="password" id="password" name="password"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
`