2009-09-07 18:42:15 +02:00
|
|
|
/*
|
|
|
|
|
* Copyright 2007-2008 WorldWide Conferencing, LLC
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing,
|
|
|
|
|
* software distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions
|
|
|
|
|
* and limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* AKKA AAS (Authentication and Authorization Service)
|
|
|
|
|
* Rework of lift's (www.liftweb.com) HTTP Authentication module
|
|
|
|
|
* All cred to the Lift team (www.liftweb.com), especially David Pollak and Tim Perrett
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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_.com.sun.jersey.api.model.AbstractMethod
|
|
|
|
|
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.WebApplicationException
|
|
|
|
|
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}
|
|
|
|
|
|
|
|
|
|
object Enc extends SecurityHelpers with StringHelpers with IoHelpers
|
|
|
|
|
|
|
|
|
|
case object OK
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Authenticate represents a message to authenticate a request
|
|
|
|
|
*/
|
|
|
|
|
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])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trait Credentials
|
|
|
|
|
|
|
|
|
|
case class BasicCredentials(username : String, password : String) extends Credentials
|
|
|
|
|
|
|
|
|
|
case class DigestCredentials(method: String,
|
|
|
|
|
userName: String,
|
|
|
|
|
realm: String,
|
|
|
|
|
nonce: String,
|
|
|
|
|
uri: String,
|
|
|
|
|
qop: String,
|
|
|
|
|
nc: String,
|
|
|
|
|
cnonce: String,
|
|
|
|
|
response: String,
|
|
|
|
|
opaque: String) extends Credentials
|
|
|
|
|
|
2009-10-18 02:48:11 +08:00
|
|
|
case class SpnegoCredentials(token : Array[Byte]) extends Credentials
|
|
|
|
|
|
2009-09-07 18:42:15 +02:00
|
|
|
/**
|
|
|
|
|
* 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 {
|
|
|
|
|
|
|
|
|
|
override def getRequestFilter : ContainerRequestFilter = this
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lazy val authenticatorFQN = akka.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
|
|
|
|
|
|
|
|
|
|
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] = {
|
|
|
|
|
|
|
|
|
|
//DenyAll takes precedence
|
|
|
|
|
if (am.isAnnotationPresent(classOf[DenyAll]))
|
|
|
|
|
return mkFilter(None)
|
|
|
|
|
|
|
|
|
|
//Method-level RolesAllowed takes precedence
|
|
|
|
|
val ra = am.getAnnotation(classOf[RolesAllowed])
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
//Last but not least, the resource-level RolesAllowed
|
|
|
|
|
val cra = am.getResource.getAnnotation(classOf[RolesAllowed])
|
|
|
|
|
if (cra ne null)
|
|
|
|
|
return mkFilter(Some(ra.value.toList))
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
//What realm does the authentication use?
|
|
|
|
|
def realm : String
|
|
|
|
|
|
|
|
|
|
//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]
|
|
|
|
|
|
|
|
|
|
//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
|
|
|
|
|
|
|
|
|
|
//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 }
|
2009-10-18 02:48:11 +08:00
|
|
|
|
2009-09-07 18:42:15 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This trait implements the logic for Http Basic 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 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 a = r.getHeaderValue("Authorization")
|
|
|
|
|
new String(Base64.decode(a.substring(6,a.length).getBytes)).split(":").toList match {
|
|
|
|
|
case userName :: password :: _ => Some(BasicCredentials(userName, password))
|
|
|
|
|
case userName :: Nil => Some(BasicCredentials(userName, ""))
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
trait DigestAuthenticationActor extends AuthenticationActor[DigestCredentials]
|
|
|
|
|
{
|
|
|
|
|
import Enc._
|
|
|
|
|
|
|
|
|
|
private object InvalidateNonces
|
|
|
|
|
|
|
|
|
|
//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
|
|
|
|
|
|
|
|
|
|
nonceMap.retain((k,v) => (ts - v) < nonceValidityPeriod)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case e => log.info("Don't know what to do with: " + e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Schedule the invalidation of nonces
|
|
|
|
|
Scheduler.schedule(this, InvalidateNonces, noncePurgeInterval, noncePurgeInterval, TimeUnit.MILLISECONDS )
|
|
|
|
|
|
|
|
|
|
//authenticate or invalidate nonces
|
|
|
|
|
override def receive: PartialFunction[Any, Unit] = authenticate orElse invalidateNonces
|
|
|
|
|
|
|
|
|
|
override def unauthorized : Response =
|
|
|
|
|
{
|
|
|
|
|
val nonce = randomString(64);
|
|
|
|
|
nonceMap.put(nonce,System.currentTimeMillis)
|
|
|
|
|
unauthorized(nonce,"auth",randomString(64))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//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 ha1 = h(auth.userName + ":" + auth.realm + ":" + user.password)
|
|
|
|
|
val ha2 = h(auth.method + ":" + auth.uri)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case _ => None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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"))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
|
|
|
|
|
mkDefaultSecurityContext(r,u,SecurityContext.DIGEST_AUTH)
|
|
|
|
|
|
|
|
|
|
//Mandatory overrides
|
|
|
|
|
def userInfo(username : String) : Option[UserInfo]
|
|
|
|
|
|
|
|
|
|
def mkNonceMap : scala.collection.mutable.Map[String,Long]
|
|
|
|
|
|
|
|
|
|
//Optional overrides
|
|
|
|
|
def nonceValidityPeriod = 60*1000//ms
|
|
|
|
|
def noncePurgeInterval = 2*60*1000 //ms
|
2009-10-18 02:48:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
import _root_.java.security.Principal
|
|
|
|
|
import _root_.java.security.PrivilegedActionException
|
|
|
|
|
import _root_.java.security.PrivilegedExceptionAction
|
|
|
|
|
|
|
|
|
|
import _root_.javax.security.auth.login.AppConfigurationEntry
|
|
|
|
|
import _root_.javax.security.auth.login.Configuration
|
|
|
|
|
import _root_.javax.security.auth.login.LoginContext
|
|
|
|
|
import _root_.javax.security.auth.Subject
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
override def extractCredentials(r : Req) : Option[SpnegoCredentials] = {
|
|
|
|
|
|
|
|
|
|
val a = auth(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
|
|
|
|
|
if (a != null && a.startsWith("Negotiate "))
|
|
|
|
|
Some(
|
|
|
|
|
SpnegoCredentials(Base64.decodeBase64(a.substring(10).trim.getBytes))
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case _ => None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
|
|
|
|
|
mkDefaultSecurityContext(r,u,SecurityContext.CLIENT_CERT_AUTH)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// service principal login to kerberos on startup
|
|
|
|
|
|
|
|
|
|
val serviceSubject = servicePrincipalLogin
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|