UPDATE
As Milos Tomic mentioned in his comment, aerialship/lightsaml is replaced by lightsaml/sp-bundle. You can find an introduction here.
+++++++++++++++++++++++++++
I have recently set up a SAML Solution using Simplesamlphp as IDP and SamlSPBundle as SP and everything is working well.
I recommend installing Simplesamlphp first, following this good Documentation here.
Once you have the IDP up and running, you should see a Welcome page and a Tab called Federation (or something like that, my Installation is in german). There you should see one option "SAML 2.0 IdP Metadata". Follow that link and copy the XML shown to a seperate file and save that file.
On the symfony side, I created a new Bundle and called that "SamlBundle". Download and install SamlSPBundle as described in their Documentation (Step 1 and Step 2).
Create your SSO State/User class (Step 3). Here is an example how I did it:
namespace SamlBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity
* @ORM\Table(name="samlUser")
*/
class SamlUser extends \AerialShip\SamlSPBundle\Entity\SSOStateEntity implements UserInterface
{
/**
* initialize User object and generates salt for password
*/
public function __construct()
{
if (!$this->userData instanceof UserData) {
$this->userData = new UserData();
}
$this->setRoles('ROLE_USER');
}
/**
* @var int
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string username
*
* @ORM\Column(type="string", length=64, nullable=true)
*/
protected $username;
/**
* @var string targetedId
*
* @ORM\Column(type="string", length=64, nullable=true, name="targeted_id")
*/
protected $targetedID;
/**
* @var string
* @ORM\Column(type="string", length=32, name="provider_id", nullable=true)
*/
protected $providerID;
/**
* @var string
* @ORM\Column(type="string", length=32, name="auth_svc_name")
*/
protected $authenticationServiceName;
/**
* @var string
* @ORM\Column(type="string", length=64, name="session_index", nullable=true)
*/
protected $sessionIndex;
/**
* @var string
* @ORM\Column(type="string", length=64, name="name_id")
*/
protected $nameID;
/**
* @var string
* @ORM\Column(type="string", length=64, name="name_id_format")
*/
protected $nameIDFormat;
/**
* @var \DateTime
* @ORM\Column(type="datetime", name="created_on")
*/
protected $createdOn;
/**
* @var UserData
* @ORM\OneToOne(targetEntity="UserData", cascade={"all"}, fetch="EAGER")
* @ORM\JoinColumn(name="user_data", referencedColumnName="id")
*/
protected $userData;
Add your class to config.yml (Step 4):
# app/config/config.yml
aerial_ship_saml_sp:
driver: orm
sso_state_entity_class: SamlBundle\Entity\SamlUser
Update your security.yml (Step 5). Example;
providers:
saml_user_provider:
id: SamlToState
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
saml:
pattern: ^/(?!login_check)
anonymous: true
aerial_ship_saml_sp:
login_path: /saml/sp/login
check_path: /saml/sp/acs
logout_path: /saml/sp/logout
failure_path: /saml/sp/failure
metadata_path: /saml/sp/FederationMetadata.xml
discovery_path: /saml/sp/discovery
local_logout_path: /logout
provider: saml_user_provider
create_user_if_not_exists: true
services:
openidp:
idp:
#the XML-File you saved from the IDP earlier
file: "@SamlBundle/Resources/idp-FederationMetadata.xml"
sp:
config:
# required, has to match entity id from IDP XML
entity_id: http://your-idp-domain.com
# if different then url being used in request
# used for construction of assertion consumer and logout urls in SP entity descriptor
base_url: http://your-sp-domain.com
signing:
#self signed certificate, see [SamlSPBundle docs][4]
cert_file: "@SamlBundle/Resources/saml.crt"
key_file: "@SamlBundle/Resources/saml.pem"
key_pass: ""
meta:
# must implement SpMetaProviderInterface
# id: my.sp.provider.service.id
# or use builtin SpMetaConfigProvider
# any valid saml name id format or shortcuts: persistent or transient
name_id_format: transient
binding:
# any saml binding or shortcuts: post or redirect
authn_request: redirect
logout_request: redirect
logout:
path: /logout
target: /
invalidate_session: true
Next import the routes as described in Step 6. Before you go on to Step 7, I recommend to create your User provider class first. Here is an example:
namespace SamlBundle\Models;
use SamlBundle\Entity\SamlUser;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use AerialShip\SamlSPBundle\Bridge\SamlSpInfo;
use AerialShip\SamlSPBundle\Security\Core\User\UserManagerInterface as UserManagerInterface;
class SamlToState implements UserManagerInterface
{
/**
* @var ContainerInterface base bundle container
*/
public $container;
/**
* Constructor with DependencyInjection params.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function loadUserBySamlInfo(SamlSpInfo $samlInfo)
{
$user = $this->loadUserByTargetedID($samlInfo->getAttributes()['eduPersonTargetedID']->getFirstValue());
return $user;
}
private function loadUserByTargetedID($targetedID) {
$repository = $this->container->get('doctrine')->getManager()->getRepository('MrmPosSamlBundle:SamlUser');
$user = $repository->findOneBy(
array('targetedID' => $targetedID)
);
if ($user) {
return $user;
}
throw new \Symfony\Component\Security\Core\Exception\UsernameNotFoundException();
}
/**
* {@inheritdoc}
*/
public function createUserFromSamlInfo(SamlSpInfo $samlInfo)
{
$repository = $this->container->get('doctrine')->getManager()->getRepository('MrmPosSamlBundle:SamlUser');
$user = $repository->findOneBy(
array('nameID' => $samlInfo->getNameID()->getValue())
);
if ($user) {
$user->setUsername($samlInfo->getAttributes()['eduPersonPrincipalName']->getFirstValue());
$user->setTargetedID($samlInfo->getAttributes()['eduPersonTargetedID']->getFirstValue());
$user->setRoles($samlInfo->getAttributes()['role']->getFirstValue());
} else {
$user = new SamlUser();
$user->setUsername($samlInfo->getAttributes()['eduPersonPrincipalName']->getFirstValue());
$user->setTargetedID($samlInfo->getAttributes()['eduPersonTargetedID']->getFirstValue());
$user->setRoles($samlInfo->getAttributes()['role']->getFirstValue());
$user->setSessionIndex($samlInfo->getAuthnStatement()->getSessionIndex());
$user->setProviderID($samlInfo->getNameID()->getSPProvidedID());
$user->setAuthenticationServiceName($samlInfo->getAuthenticationServiceID());
$user->setNameID($samlInfo->getNameID()->getValue());
$user->setNameIDFormat($samlInfo->getNameID()->getFormat());
}
$em = $this->container->get('doctrine')->getManager();
$em->persist($user);
$em->flush();
return $user;
}
public function loadUserByUsername($username)
{
$repository = $this->container->get('doctrine')->getManager()->getRepository('MrmPosSamlBundle:SamlUser');
$user = $repository->findOneBy(
array('username' => $username)
);
if ($user) {
return $user;
}
throw new \Symfony\Component\Security\Core\Exception\UsernameNotFoundException();
return false;
}
/**
* {@inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
$repository = $this->container->get('doctrine')->getManager()->getRepository('MrmPosSamlBundle:SamlUser');
$newUser = $repository->findOneBy(
array('nameID' => $user->getNameID())
);
if (!$newUser) {
throw new \Symfony\Component\Security\Core\Exception\UsernameNotFoundException();
}
return $newUser;
}
/**
* {@inheritdoc}
*/
public function supportsClass($class)
{
return true;
}
}
Create your service in SamlBundle/Resources/config/services.yml:
services:
SamlToState:
class: SamlBundle\Models\SamlToState
arguments: [@service_container]
Now its time for Step 7, exchanging Metadata. get the SP XML as described and go back to your IDP. You find a "XML to simpleSAMLphp Metadata converter" Link on the Federation Tab. Follow that link and convert your SP XML Data to the simpleSAMLphp format. Add that data to the saml20-sp-remote.php file in your IDPs metadata folder.
Ok, I'm pretty sure that I forgot something, but hopefully this information helps. If you get stuck, your welcome to get back to me.