AAA

  • Аутентификация – идентификация пользователя.
  • Авторизация – определение прав пользователя для ресурса.
  • Аудит (accounting) – регистрация действий пользователя.

Basic аутентификация

Проверка пользователя

private fun validator(): 
	suspend ApplicationCall.(UserPasswordCredential) -> Principal? =
    { credentials ->
        if ( userList.find {
                it.username == credentials.name
            }?.password == credentials.password 
			){ 
				UserIdPrincipal(credentials.name) } 
		else { null }
    }

Подключение плагина

install(Authentication) {
	basic("auth-basic") {
		realm = "Access to the '/basic' path"
		validate(validator())
	}
}

Контроль доступа

routing {
	authenticate("auth-basic") {
		get("/basic") {
			call.respondText(
				"Hello, ${call.principal<UserIdPrincipal>()?.name}!"
			)
		}
	}

Вход с браузера

Вход с браузера

Curl

curl -i http://localhost:8000/basic 
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Access to the '/basic' path", 
	charset=UTF-8
Content-Length: 0
curl -i -H "Authorization: Basic dHV0b3I6dHV0b3I=" 
	http://localhost:8000/basic
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8

Digest аутентификация

Настройка плагина

fun getMd5Digest(str: String): ByteArray = 
	MessageDigest.getInstance("MD5").digest(str.toByteArray())
const val digestRealm = "Access to the '/digest' path"
val usersHash: Map<String, ByteArray> = userList.associate {
    it.username to 
		getMd5Digest("${it.username}:$digestRealm:${it.password}")
}

digest("auth-digest") {
	realm = digestRealm
	digestProvider { userName, realm ->
		usersHash[userName]
	}
}

Контроль доступа

routing {
	authenticate("auth-digest") {
		get("/digest") {
			call.respondText(
				"Hello, ${call.principal<UserIdPrincipal>()?.name}!")
		}
	}

Вход с браузера

Curl

curl -i http://localhost:8000/digest
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="Access to the '/digest' path", 
	nonce="fb1bffee9d86d7d4", algorithm="MD5"
Content-Length: 0
curl -i -H @digest-header.txt http://localhost:8000/digest
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8

Form и session аутентификация

Настройка плагина

data class UserSession(
    val name: String, val count: Int
) : Principal
form("auth-form") {
	userParamName = "username"
	passwordParamName = "password"
	validate(validator()) }
session<UserSession>("auth-session") {
	validate { session ->
		session }
	challenge {
		call.respondRedirect("/form-login") } }

Контроль доступа

authenticate("auth-form") {
	post("/form-login") {
		val userName = call.principal<UserIdPrincipal>()
			?.name.toString()
		call.sessions.set(UserSession(userName, 1))
		call.respondText("Hello, $userName!") } }
authenticate("auth-session") {
	get("/session") {
		val userSession = call.principal<UserSession>()
		call.sessions.set(userSession
			?.copy(count = userSession.count + 1))
		call.respondText("Hello, ${userSession?.name}") } }

Curl form

curl -i 
	-H "Content-Type: application/x-www-form-urlencoded" 
	-d "username=tutor&password=tutor"  
	-X POST http://localhost:8000/form-login

HTTP/1.1 200 OK
user_session: count=%23i1&name=%23stutor
Content-Length: 13
Content-Type: text/plain; charset=UTF-8

Curl session

curl -v -H "user_session: count=%23i1&name=%23stutor"
	http://localhost:8000/session
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /session HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.55.1
> Accept: */*
> user_session: count=%23i1&name=%23stutor
< HTTP/1.1 200 OK
< user_session: count=%23i2&name=%23stutor
< Content-Length: 12
< Content-Type: text/plain; charset=UTF-8
Hello, tutor* Connection #0 to host localhost left intact

JSON Web Token (JWT)

Настройка плагина

jwt("auth-jwt") {
	realm = jwtRealm
	verifier(JWT
		.require(Algorithm.HMAC256(secret))
		.withAudience(audience)
		.withIssuer(issuer)
		.build() )
	validate { credential ->
		if (credential.payload.getClaim("username").asString() 
			!= "") {
			JWTPrincipal(credential.payload)
		} else { 
			null } } }

Выдача токена

post("jwt-login") {
	val user = call.receive<User>()
	val token = JWT.create()
		.withAudience(audience)
		.withIssuer(issuer)
		.withClaim("username", user.username)
		.withExpiresAt(
			Date(System.currentTimeMillis() + 600000))
		.sign(Algorithm.HMAC256(secret))
	call.respond(hashMapOf("token" to token))
}

Использование токена

post("jwt-login") {
authenticate("auth-jwt") {
	get("/jwt") {
		val principal = call.principal<JWTPrincipal>()
		val username = principal!!.payload
			.getClaim("username").asString()
		val expiresAt = principal.expiresAt
			?.time?.minus(System.currentTimeMillis())
		call.respondText(
			"Hello, $username! Token is expired at $expiresAt ms.")
	}
}

QAuth

Auth — открытый протокол (схема) авторизации, обеспечивающий предоставление третьей стороне ограниченный доступ к защищённым ресурсам пользователя без передачи ей (третьей стороне) логина и пароля