changed trapExit from Boolean to "trapExit = List(classOf[..], classOf[..])" + cleaned up security code

This commit is contained in:
jboner 2009-11-17 22:26:25 +01:00
parent eef81f85e3
commit ceeb7d8b46
7 changed files with 465 additions and 371 deletions

View file

@ -22,20 +22,20 @@
package se.scalablesolutions.akka.security
import _root_.se.scalablesolutions.akka.actor.{Scheduler,Actor,ActorRegistry}
import _root_.se.scalablesolutions.akka.state.{TransactionalState,PersistentStorageConfig}
import _root_.se.scalablesolutions.akka.util.{Logging}
import _root_.se.scalablesolutions.akka.actor.{Scheduler, Actor, ActorRegistry}
import _root_.se.scalablesolutions.akka.util.Logging
import _root_.se.scalablesolutions.akka.Config
import _root_.com.sun.jersey.api.model.AbstractMethod
import _root_.com.sun.jersey.spi.container.{ResourceFilterFactory,ContainerRequest,ContainerRequestFilter,ContainerResponse,ContainerResponseFilter,ResourceFilter}
import _root_.com.sun.jersey.spi.container.{ResourceFilterFactory, ContainerRequest, ContainerRequestFilter, ContainerResponse, ContainerResponseFilter, ResourceFilter}
import _root_.com.sun.jersey.core.util.Base64
import _root_.javax.ws.rs.core.{SecurityContext,Context,Response}
import _root_.javax.ws.rs.core.{SecurityContext, Context, Response}
import _root_.javax.ws.rs.WebApplicationException
import _root_.javax.annotation.security.{DenyAll,PermitAll,RolesAllowed}
import _root_.javax.annotation.security.{DenyAll, PermitAll, RolesAllowed}
import _root_.java.security.Principal
import _root_.java.util.concurrent.TimeUnit
import _root_.net.liftweb.util.{SecurityHelpers, StringHelpers,IoHelpers}
import _root_.net.liftweb.util.{SecurityHelpers, StringHelpers, IoHelpers}
object Enc extends SecurityHelpers with StringHelpers with IoHelpers
@ -44,18 +44,16 @@ case object OK
/**
* Authenticate represents a message to authenticate a request
*/
case class Authenticate(val req : ContainerRequest, val rolesAllowed : List[String])
case class Authenticate(val req: ContainerRequest, val rolesAllowed: List[String])
/**
* User info represents a sign-on with associated credentials/roles
*/
case class UserInfo(val username : String,val password : String,val roles : List[String])
case class UserInfo(val username: String, val password: String, val roles: List[String])
trait Credentials
case class BasicCredentials(username : String, password : String) extends Credentials
case class BasicCredentials(username: String, password: String) extends Credentials
case class DigestCredentials(method: String,
userName: String,
@ -68,151 +66,145 @@ case class DigestCredentials(method: String,
response: String,
opaque: String) extends Credentials
case class SpnegoCredentials(token : Array[Byte]) extends Credentials
case class SpnegoCredentials(token: Array[Byte]) extends Credentials
/**
* Jersey Filter for invocation intercept and authorization/authentication
*/
class AkkaSecurityFilterFactory extends ResourceFilterFactory with Logging {
class Filter(actor: Actor, rolesAllowed: Option[List[String]])
extends ResourceFilter with ContainerRequestFilter with Logging {
class Filter(actor : Actor,rolesAllowed : Option[List[String]]) extends ResourceFilter with ContainerRequestFilter with Logging {
override def getRequestFilter: ContainerRequestFilter = this
override def getRequestFilter : ContainerRequestFilter = this
override def getResponseFilter : ContainerResponseFilter = null
override def getResponseFilter: ContainerResponseFilter = null
/**
* Here's where the magic happens. The request is authenticated by
* sending a request for authentication to the configured authenticator actor
*/
override def filter(request : ContainerRequest) : ContainerRequest =
rolesAllowed match {
case Some(roles) => {
val result : AnyRef = (authenticator !? Authenticate(request,roles))
result match {
case OK => request
case r if r.isInstanceOf[Response] =>
throw new WebApplicationException(r.asInstanceOf[Response])
case x => {
log.error("Authenticator replied with unexpected result: ",x);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR)
}
}
}
case None => throw new WebApplicationException(Response.Status.FORBIDDEN)
/**
* Here's where the magic happens. The request is authenticated by
* sending a request for authentication to the configured authenticator actor
*/
override def filter(request: ContainerRequest): ContainerRequest =
rolesAllowed match {
case Some(roles) => {
(authenticator !? Authenticate(request, roles)).asInstanceOf[AnyRef] match {
case OK => request
case r if r.isInstanceOf[Response] =>
throw new WebApplicationException(r.asInstanceOf[Response])
case x => {
log.error("Authenticator replied with unexpected result: ", x);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR)
}
}
}
}
case None => throw new WebApplicationException(Response.Status.FORBIDDEN)
}
}
lazy val authenticatorFQN = akka.Config.config.getString("akka.rest.authenticator").getOrElse(throw new IllegalStateException("akka.rest.authenticator"))
lazy val authenticatorFQN = Config.config.getString("akka.rest.authenticator").getOrElse(throw new IllegalStateException("akka.rest.authenticator"))
/**
* Currently we always take the first, since there usually should be at most one authentication actor, but a round-robin
* strategy could be implemented in the future
*/
def authenticator : Actor = ActorRegistry.actorsFor(authenticatorFQN).head
/**
* Currently we always take the first, since there usually should be at most one authentication actor, but a round-robin
* strategy could be implemented in the future
*/
def authenticator: Actor = ActorRegistry.actorsFor(authenticatorFQN).head
def mkFilter(roles : Option[List[String]]) : java.util.List[ResourceFilter] = java.util.Collections.singletonList(new Filter(authenticator,roles))
def mkFilter(roles: Option[List[String]]): java.util.List[ResourceFilter] =
java.util.Collections.singletonList(new Filter(authenticator, roles))
/**
* The create method is invoked for each resource, and we look for javax.annotation.security annotations
* and create the appropriate Filter configurations for each.
*/
override def create(am : AbstractMethod) : java.util.List[ResourceFilter] = {
/**
* The create method is invoked for each resource, and we look for javax.annotation.security annotations
* and create the appropriate Filter configurations for each.
*/
override def create(am: AbstractMethod): java.util.List[ResourceFilter] = {
//DenyAll takes precedence
if (am.isAnnotationPresent(classOf[DenyAll]))
return mkFilter(None)
//DenyAll takes precedence
if (am.isAnnotationPresent(classOf[DenyAll]))
return mkFilter(None)
//Method-level RolesAllowed takes precedence
val ra = am.getAnnotation(classOf[RolesAllowed])
//Method-level RolesAllowed takes precedence
val ra = am.getAnnotation(classOf[RolesAllowed])
if (ra ne null)
return mkFilter(Some(ra.value.toList))
if (ra ne null)
return mkFilter(Some(ra.value.toList))
//PermitAll takes precedence over resource-level RolesAllowed annotation
if (am.isAnnotationPresent(classOf[PermitAll]))
return null;
//PermitAll takes precedence over resource-level RolesAllowed annotation
if (am.isAnnotationPresent(classOf[PermitAll]))
return null;
//Last but not least, the resource-level RolesAllowed
val cra = am.getResource.getAnnotation(classOf[RolesAllowed])
if (cra ne null)
return mkFilter(Some(cra.value.toList))
//Last but not least, the resource-level RolesAllowed
val cra = am.getResource.getAnnotation(classOf[RolesAllowed])
if (cra ne null)
return mkFilter(Some(cra.value.toList))
return null;
}
return null;
}
}
/**
* AuthenticationActor is the super-trait for actors doing Http authentication
* It defines the common ground and the flow of execution
*/
trait AuthenticationActor[C <: Credentials] extends Actor with Logging
{
type Req = ContainerRequest
trait AuthenticationActor[C <: Credentials] extends Actor {
type Req = ContainerRequest
//What realm does the authentication use?
def realm : String
//What realm does the authentication use?
def realm: String
//Creates a response to signal unauthorized
def unauthorized : Response
//Creates a response to signal unauthorized
def unauthorized: Response
//Used to extract information from the request, returns None if no credentials found
def extractCredentials(r : Req) : Option[C]
//Used to extract information from the request, returns None if no credentials found
def extractCredentials(r: Req): Option[C]
//returns None is unverified
def verify(c : Option[C]) : Option[UserInfo]
//returns None is unverified
def verify(c: Option[C]): Option[UserInfo]
//Contruct a new SecurityContext from the supplied parameters
def mkSecurityContext(r : Req, user : UserInfo) : SecurityContext
//Contruct a new SecurityContext from the supplied parameters
def mkSecurityContext(r: Req, user: UserInfo): SecurityContext
//This is the default security context factory
def mkDefaultSecurityContext(r : Req,u : UserInfo, scheme : String) : SecurityContext = {
val n = u.username
val p = new Principal { def getName = n }
//This is the default security context factory
def mkDefaultSecurityContext(r: Req, u: UserInfo, scheme: String): SecurityContext = {
val n = u.username
val p = new Principal {def getName = n}
new SecurityContext {
def getAuthenticationScheme = scheme
def getUserPrincipal = p
def isSecure = r.isSecure
def isUserInRole(role : String) = u.roles.exists(_ == role)
new SecurityContext {
def getAuthenticationScheme = scheme
def getUserPrincipal = p
def isSecure = r.isSecure
def isUserInRole(role: String) = u.roles.exists(_ == role)
}
}
/**
* Responsible for the execution flow of authentication
*
* Credentials are extracted and verified from the request,
* and a se3curity context is created for the ContainerRequest
* this should ensure good integration with current Jersey security
*/
protected val authenticate: PartialFunction[Any, Unit] = {
case Authenticate(req, roles) => {
verify(extractCredentials(req)) match {
case Some(u: UserInfo) => {
req.setSecurityContext(mkSecurityContext(req, u))
if (roles.exists(req.isUserInRole(_))) reply(OK)
else reply(Response.status(Response.Status.FORBIDDEN).build)
}
case _ => reply(unauthorized)
}
}
}
/**
* Responsible for the execution flow of authentication
*
* Credentials are extracted and verified from the request,
* and a se3curity context is created for the ContainerRequest
* this should ensure good integration with current Jersey security
*/
protected val authenticate: PartialFunction[Any,Unit] = {
case Authenticate(req,roles) => {
verify(extractCredentials(req)) match {
case Some(u : UserInfo) => {
def receive = authenticate
req.setSecurityContext(mkSecurityContext(req,u))
//returns the string value of the "Authorization"-header of the request
def auth(r: Req) = r.getHeaderValue("Authorization")
if(roles.exists(req.isUserInRole(_)))
reply(OK)
else
reply(Response.status(Response.Status.FORBIDDEN).build)
}
case _ => reply(unauthorized)
}
}
}
override def receive: PartialFunction[Any, Unit] = authenticate
//returns the string value of the "Authorization"-header of the request
def auth(r : Req) = r.getHeaderValue("Authorization")
//Turns the aforementioned header value into an option
def authOption(r : Req) : Option[String] = {
val a = auth(r)
if(a != null && a.length > 0) Some(a) else None
}
//Turns the aforementioned header value into an option
def authOption(r: Req): Option[String] = {
val a = auth(r)
if (a != null && a.length > 0) Some(a) else None
}
}
/**
@ -220,127 +212,117 @@ trait AuthenticationActor[C <: Credentials] extends Actor with Logging
* mix this trait into a class to create an authenticator
* Don't forget to set the authenticator FQN in the rest-part of the akka config
*/
trait BasicAuthenticationActor extends AuthenticationActor[BasicCredentials]
{
override def unauthorized =
Response.status(401).header("WWW-Authenticate","Basic realm=\"" + realm + "\"").build
trait BasicAuthenticationActor extends AuthenticationActor[BasicCredentials] {
override def unauthorized =
Response.status(401).header("WWW-Authenticate", "Basic realm=\"" + realm + "\"").build
override def extractCredentials(r : Req) : Option[BasicCredentials] = {
val Authorization = """(.*):(.*)""".r
authOption(r) match {
case Some(token) => {
val authResponse = new String(Base64.decode(token.substring(6).getBytes))
authResponse match {
case Authorization(username, password) => Some(BasicCredentials(username, password))
case _ => None
}
}
case _ => None
}
}
override def extractCredentials(r: Req): Option[BasicCredentials] = {
val Authorization = """(.*):(.*)""".r
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
mkDefaultSecurityContext(r,u,SecurityContext.BASIC_AUTH)
authOption(r) match {
case Some(token) => {
val authResponse = new String(Base64.decode(token.substring(6).getBytes))
authResponse match {
case Authorization(username, password) => Some(BasicCredentials(username, password))
case _ => None
}
}
case _ => None
}
}
override def mkSecurityContext(r: Req, u: UserInfo): SecurityContext =
mkDefaultSecurityContext(r, u, SecurityContext.BASIC_AUTH)
}
/**
* This trait implements the logic for Http Digest authentication
* mix this trait into a class to create an authenticator
* Don't forget to set the authenticator FQN in the rest-part of the akka config
* This trait implements the logic for Http Digest authentication mix this trait into a
* class to create an authenticator. Don't forget to set the authenticator FQN in the
* rest-part of the akka config
*/
trait DigestAuthenticationActor extends AuthenticationActor[DigestCredentials]
{
import Enc._
trait DigestAuthenticationActor extends AuthenticationActor[DigestCredentials] {
import Enc._
private object InvalidateNonces
private object InvalidateNonces
//Holds the generated nonces for the specified validity period
val nonceMap = mkNonceMap
//Holds the generated nonces for the specified validity period
val nonceMap = mkNonceMap
//Discards old nonces
protected val invalidateNonces: PartialFunction[Any,Unit] = {
case InvalidateNonces =>
{
val ts = System.currentTimeMillis
//Discards old nonces
protected val invalidateNonces: PartialFunction[Any, Unit] = {
case InvalidateNonces =>
val ts = System.currentTimeMillis
nonceMap.retain((k, v) => (ts - v) < nonceValidityPeriod)
case e =>
log.info("Don't know what to do with: " + e)
}
nonceMap.retain((k,v) => (ts - v) < nonceValidityPeriod)
}
//Schedule the invalidation of nonces
Scheduler.schedule(this, InvalidateNonces, noncePurgeInterval, noncePurgeInterval, TimeUnit.MILLISECONDS)
case e => log.info("Don't know what to do with: " + e)
}
//authenticate or invalidate nonces
override def receive = authenticate orElse invalidateNonces
//Schedule the invalidation of nonces
Scheduler.schedule(this, InvalidateNonces, noncePurgeInterval, noncePurgeInterval, TimeUnit.MILLISECONDS )
override def unauthorized: Response = {
val nonce = randomString(64);
nonceMap.put(nonce, System.currentTimeMillis)
unauthorized(nonce, "auth", randomString(64))
}
//authenticate or invalidate nonces
override def receive: PartialFunction[Any, Unit] = authenticate orElse invalidateNonces
def unauthorized(nonce: String, qop: String, opaque: String): Response = {
Response.status(401).header("WWW-Authenticate",
"Digest realm=\"" + realm + "\", " +
"qop=\"" + qop + "\", " +
"nonce=\"" + nonce + "\", " +
"opaque=\"" + opaque + "\"").build
}
override def unauthorized : Response =
{
val nonce = randomString(64);
nonceMap.put(nonce,System.currentTimeMillis)
unauthorized(nonce,"auth",randomString(64))
}
//Tests wether the specified credentials are valid
def validate(auth: DigestCredentials, user: UserInfo): Boolean = {
def h(s: String) = hexEncode(md5(s.getBytes("UTF-8")))
def unauthorized(nonce : String, qop : String, opaque : String) : Response =
{
Response.status(401).header("WWW-Authenticate",
"Digest realm=\"" + realm + "\", " +
"qop=\"" + qop + "\", " +
"nonce=\"" + nonce + "\", " +
"opaque=\"" + opaque + "\"").build
}
val ha1 = h(auth.userName + ":" + auth.realm + ":" + user.password)
val ha2 = h(auth.method + ":" + auth.uri)
//Tests wether the specified credentials are valid
def validate(auth: DigestCredentials,user : UserInfo) : Boolean = {
def h(s : String) = hexEncode(md5(s.getBytes("UTF-8")))
val response = h(ha1 + ":" + auth.nonce + ":" +
auth.nc + ":" + auth.cnonce + ":" +
auth.qop + ":" + ha2)
val ha1 = h(auth.userName + ":" + auth.realm + ":" + user.password)
val ha2 = h(auth.method + ":" + auth.uri)
(response == auth.response) && (nonceMap.getOrElse(auth.nonce, -1) != -1)
}
val response = h(ha1 + ":" + auth.nonce + ":" +
auth.nc + ":" + auth.cnonce + ":" +
auth.qop + ":" + ha2)
(response == auth.response) && (nonceMap.getOrElse(auth.nonce, -1) != -1)
}
override def verify(odc : Option[DigestCredentials]) : Option[UserInfo] = odc match {
case Some(dc) => {
userInfo(dc.userName) match {
case Some(u) if validate(dc,u) =>
nonceMap.get(dc.nonce).map( t => (System.currentTimeMillis - t) < nonceValidityPeriod ).map(_ => u)
case _ => None
}
}
override def verify(odc: Option[DigestCredentials]): Option[UserInfo] = odc match {
case Some(dc) => {
userInfo(dc.userName) match {
case Some(u) if validate(dc, u) =>
nonceMap.get(dc.nonce).map(t => (System.currentTimeMillis - t) < nonceValidityPeriod).map(_ => u)
case _ => None
}
}
case _ => None
}
override def extractCredentials(r : Req) : Option[DigestCredentials] =
{
authOption(r).map( s => {
val ? = splitNameValuePairs(s.substring(7,s.length ))
override def extractCredentials(r: Req): Option[DigestCredentials] = {
authOption(r).map(s => {
val ? = splitNameValuePairs(s.substring(7, s.length))
DigestCredentials(r.getMethod.toUpperCase,
?("username"), ?("realm"), ?("nonce"),
?("uri"), ?("qop"), ?("nc"),
?("cnonce"), ?("response"), ?("opaque"))
})
}
DigestCredentials(r.getMethod.toUpperCase, ?("username"), ?("realm"), ?("nonce"),
?("uri"), ?("qop"), ?("nc"),
?("cnonce"), ?("response"), ?("opaque"))
})
}
override def mkSecurityContext(r: Req, u: UserInfo): SecurityContext =
mkDefaultSecurityContext(r, u, SecurityContext.DIGEST_AUTH)
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
mkDefaultSecurityContext(r,u,SecurityContext.DIGEST_AUTH)
//Mandatory overrides
def userInfo(username: String): Option[UserInfo]
//Mandatory overrides
def userInfo(username : String) : Option[UserInfo]
def mkNonceMap: scala.collection.mutable.Map[String, Long]
def mkNonceMap : scala.collection.mutable.Map[String,Long]
//Optional overrides
def nonceValidityPeriod = 60*1000//ms
def noncePurgeInterval = 2*60*1000 //ms
//Optional overrides
def nonceValidityPeriod = 60 * 1000 //ms
def noncePurgeInterval = 2 * 60 * 1000 //ms
}
import _root_.java.security.Principal
@ -356,143 +338,126 @@ import _root_.javax.security.auth.kerberos.KerberosPrincipal
import _root_.org.ietf.jgss.GSSContext
import _root_.org.ietf.jgss.GSSCredential
import _root_.org.ietf.jgss.GSSManager
trait SpnegoAuthenticationActor extends AuthenticationActor[SpnegoCredentials]
{
override def unauthorized =
Response.status(401).header("WWW-Authenticate", "Negotiate").build
// for some reason the jersey Base64 class does not work with kerberos
// but the commons Base64 does
import _root_.org.apache.commons.codec.binary.Base64
override def extractCredentials(r : Req) : Option[SpnegoCredentials] = {
trait SpnegoAuthenticationActor extends AuthenticationActor[SpnegoCredentials] {
override def unauthorized =
Response.status(401).header("WWW-Authenticate", "Negotiate").build
val AuthHeader = """Negotiate\s(.*)""".r
// for some reason the jersey Base64 class does not work with kerberos
// but the commons Base64 does
import _root_.org.apache.commons.codec.binary.Base64
override def extractCredentials(r: Req): Option[SpnegoCredentials] = {
val AuthHeader = """Negotiate\s(.*)""".r
authOption(r) match {
case Some(AuthHeader(token)) => {
Some(SpnegoCredentials(Base64.decodeBase64(token.trim.getBytes)))
}
case _ => None
}
}
override def verify(odc : Option[SpnegoCredentials]) : Option[UserInfo] = odc match {
case Some(dc) => {
try {
val principal = Subject.doAs(this.serviceSubject, new KerberosValidateAction(dc.token));
val user = stripRealmFrom(principal)
Some(UserInfo(user, null, rolesFor(user)))
} catch {
case e: PrivilegedActionException => {
e.printStackTrace
return None
}
}
}
authOption(r) match {
case Some(AuthHeader(token)) =>
Some(SpnegoCredentials(Base64.decodeBase64(token.trim.getBytes)))
case _ => None
}
}
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
mkDefaultSecurityContext(r,u,SecurityContext.CLIENT_CERT_AUTH) // the security context does not know about spnego/kerberos
// not sure whether to use a constant from the security context or something like "SPNEGO/Kerberos"
/**
* returns the roles for the given user
*/
def rolesFor(user: String): List[String]
// Kerberos
/**
* strips the realm from a kerberos principal name, returning only the user part
*/
private def stripRealmFrom(principal: String):String = principal.split("@")(0)
/**
* principal name for the HTTP kerberos service, i.e HTTP/{server}@{realm}
*/
lazy val servicePrincipal = akka.Config.config.getString("akka.rest.kerberos.servicePrincipal").getOrElse(throw new IllegalStateException("akka.rest.kerberos.servicePrincipal"))
/**
* keytab location with credentials for the service principal
*/
lazy val keyTabLocation = akka.Config.config.getString("akka.rest.kerberos.keyTabLocation").getOrElse(throw new IllegalStateException("akka.rest.kerberos.keyTabLocation"))
lazy val kerberosDebug = akka.Config.config.getString("akka.rest.kerberos.kerberosDebug").getOrElse("false")
/**
* is not used by this authenticator, so accept an empty value
*/
lazy val realm = akka.Config.config.getString("akka.rest.kerberos.realm").getOrElse("")
/**
* verify the kerberos token from a client with the server
*/
class KerberosValidateAction(kerberosTicket: Array[Byte]) extends PrivilegedExceptionAction[String] {
def run = {
val context = GSSManager.getInstance().createContext(null.asInstanceOf[GSSCredential])
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length)
val user = context.getSrcName().toString()
context.dispose()
user
override def verify(odc: Option[SpnegoCredentials]): Option[UserInfo] = odc match {
case Some(dc) => {
try {
val principal = Subject.doAs(this.serviceSubject, new KerberosValidateAction(dc.token));
val user = stripRealmFrom(principal)
Some(UserInfo(user, null, rolesFor(user)))
} catch {
case e: PrivilegedActionException => {
e.printStackTrace
return None
}
}
}
case _ => None
}
// service principal login to kerberos on startup
override def mkSecurityContext(r: Req, u: UserInfo): SecurityContext =
mkDefaultSecurityContext(r, u, SecurityContext.CLIENT_CERT_AUTH) // the security context does not know about spnego/kerberos
// not sure whether to use a constant from the security context or something like "SPNEGO/Kerberos"
val serviceSubject = servicePrincipalLogin
/**
* returns the roles for the given user
*/
def rolesFor(user: String): List[String]
/**
* acquire an initial ticket from the kerberos server for the HTTP service
*/
def servicePrincipalLogin = {
val loginConfig = new LoginConfig(
new java.net.URL(this.keyTabLocation).toExternalForm(),
this.servicePrincipal,
this.kerberosDebug)
// Kerberos
val princ = new java.util.HashSet[Principal](1)
princ.add(new KerberosPrincipal(this.servicePrincipal))
/**
* strips the realm from a kerberos principal name, returning only the user part
*/
private def stripRealmFrom(principal: String): String = principal.split("@")(0)
val sub = new Subject(false, princ, new java.util.HashSet[Object], new java.util.HashSet[Object])
/**
* principal name for the HTTP kerberos service, i.e HTTP/ { server } @ { realm }
*/
lazy val servicePrincipal = Config.config.getString("akka.rest.kerberos.servicePrincipal").getOrElse(throw new IllegalStateException("akka.rest.kerberos.servicePrincipal"))
val lc = new LoginContext("", sub, null, loginConfig)
lc.login()
/**
* keytab location with credentials for the service principal
*/
lazy val keyTabLocation = Config.config.getString("akka.rest.kerberos.keyTabLocation").getOrElse(throw new IllegalStateException("akka.rest.kerberos.keyTabLocation"))
lc.getSubject()
lazy val kerberosDebug = Config.config.getString("akka.rest.kerberos.kerberosDebug").getOrElse("false")
/**
* is not used by this authenticator, so accept an empty value
*/
lazy val realm = Config.config.getString("akka.rest.kerberos.realm").getOrElse("")
/**
* verify the kerberos token from a client with the server
*/
class KerberosValidateAction(kerberosTicket: Array[Byte]) extends PrivilegedExceptionAction[String] {
def run = {
val context = GSSManager.getInstance().createContext(null.asInstanceOf[GSSCredential])
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length)
val user = context.getSrcName().toString()
context.dispose()
user
}
}
/**
* this class simulates a login-config.xml
*/
class LoginConfig(keyTabLocation: String, servicePrincipal: String, debug: String) extends Configuration {
// service principal login to kerberos on startup
override def getAppConfigurationEntry(name: String):Array[AppConfigurationEntry] = {
val options = new java.util.HashMap[String, String]
options.put("useKeyTab", "true");
options.put("keyTab", this.keyTabLocation);
options.put("principal", this.servicePrincipal);
options.put("storeKey", "true");
options.put("doNotPrompt", "true");
options.put("isInitiator", "true");
options.put("debug", debug);
val serviceSubject = servicePrincipalLogin
Array(new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options))
}
/**
* acquire an initial ticket from the kerberos server for the HTTP service
*/
def servicePrincipalLogin = {
val loginConfig = new LoginConfig(
new java.net.URL(this.keyTabLocation).toExternalForm(),
this.servicePrincipal,
this.kerberosDebug)
val princ = new java.util.HashSet[Principal](1)
princ.add(new KerberosPrincipal(this.servicePrincipal))
val sub = new Subject(false, princ, new java.util.HashSet[Object], new java.util.HashSet[Object])
val lc = new LoginContext("", sub, null, loginConfig)
lc.login()
lc.getSubject()
}
/**
* this class simulates a login-config.xml
*/
class LoginConfig(keyTabLocation: String, servicePrincipal: String, debug: String) extends Configuration {
override def getAppConfigurationEntry(name: String): Array[AppConfigurationEntry] = {
val options = new java.util.HashMap[String, String]
options.put("useKeyTab", "true")
options.put("keyTab", this.keyTabLocation)
options.put("principal", this.servicePrincipal)
options.put("storeKey", "true")
options.put("doNotPrompt", "true")
options.put("isInitiator", "true")
options.put("debug", debug)
Array(new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options))
}
}
}