Saturday, April 18, 2015

URL format for Spring Security SAML extension

When accessing login URL for a Spring Security SAML based application you can use the following syntax:

https://fqdn-server/context-path/saml/login/alias/url-encoded-sp-alias-name?idp=url-encoded-idp-entity-id
  • url-encoded-sp-alias-name is the alias name of the desired Service Provider (the value of <property name="alias" value="sp-alias-name"/>
  • url-encoded-idp-entity-id is the entityId value (URL encoded) as defined in IDP meta-data <EntityDescriptor entityID="http://idp.ssocircle.com" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">, http%3A%2F%2Fidp.ssocircle.com in our case

There are at least two scenarios when this feature can be useful:

  • Having a single application for multiple companies, each one having it's own internal IDP implementation (each user will access the application through a specific URL containing the appropriate IDP as a query parameter)
  • Having a single configuration/single IDP but multiple deployment environments (like development, test, production); different SP aliases will be used to access a specific environment
V2hlbiBhY2Nlc3NpbmcgbG9naW4gYFVSTGAgZm9yIGEgU3ByaW5nIFNlY3VyaXR5IFNBTUwgYmFzZWQgYXBwbGljYXRpb24geW91IGNhbiB1c2UgdGhlIGZvbGxvd2luZyBzeW50YXg6DQoNCmBgYGh0bWwNCmh0dHBzOi8vZnFkbi1zZXJ2ZXIvY29udGV4dC1wYXRoL3NhbWwvbG9naW4vYWxpYXMvdXJsLWVuY29kZWQtc3AtYWxpYXMtbmFtZT9pZHA9dXJsLWVuY29kZWQtaWRwLWVudGl0eS1pZA0KYGBgDQoqIGB1cmwtZW5jb2RlZC1zcC1hbGlhcy1uYW1lYCBpcyB0aGUgYWxpYXMgbmFtZSBvZiB0aGUgZGVzaXJlZCBTZXJ2aWNlIFByb3ZpZGVyICh0aGUgdmFsdWUgb2YgYDxwcm9wZXJ0eSBuYW1lPSJhbGlhcyIgdmFsdWU9InNwLWFsaWFzLW5hbWUiLz5gDQoqIGB1cmwtZW5jb2RlZC1pZHAtZW50aXR5LWlkYCBpcyB0aGUgYGVudGl0eUlkYCB2YWx1ZSAoVVJMIGVuY29kZWQpIGFzIGRlZmluZWQgaW4gSURQIG1ldGEtZGF0YSBgPEVudGl0eURlc2NyaXB0b3IgZW50aXR5SUQ9Imh0dHA6Ly9pZHAuc3NvY2lyY2xlLmNvbSIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDptZXRhZGF0YSI+YCwgYGh0dHAlM0ElMkYlMkZpZHAuc3NvY2lyY2xlLmNvbWAgaW4gb3VyIGNhc2UNCg0KVGhlcmUgYXJlIGF0IGxlYXN0IHR3byBzY2VuYXJpb3Mgd2hlbiB0aGlzIGZlYXR1cmUgY2FuIGJlIHVzZWZ1bDoNCiogSGF2aW5nIGEgc2luZ2xlIGFwcGxpY2F0aW9uIGZvciBtdWx0aXBsZSBjb21wYW5pZXMsIGVhY2ggb25lIGhhdmluZyBpdCdzIG93biBpbnRlcm5hbCBJRFAgaW1wbGVtZW50YXRpb24gKGVhY2ggdXNlciB3aWxsIGFjY2VzcyB0aGUgYXBwbGljYXRpb24gdGhyb3VnaCBhIHNwZWNpZmljIFVSTCBjb250YWluaW5nIHRoZSBhcHByb3ByaWF0ZSBJRFAgYXMgYSBxdWVyeSBwYXJhbWV0ZXIpDQoqIEhhdmluZyBhIHNpbmdsZSBjb25maWd1cmF0aW9uL3NpbmdsZSBJRFAgYnV0IG11bHRpcGxlIGRlcGxveW1lbnQgZW52aXJvbm1lbnRzIChsaWtlIGRldmVsb3BtZW50LCB0ZXN0LCBwcm9kdWN0aW9uKTsgZGlmZmVyZW50IFNQIGFsaWFzZXMgd2lsbCBiZSB1c2VkIHRvIGFjY2VzcyBhIHNwZWNpZmljIGVudmlyb25tZW50

Markdown Code Styling

  • Edit your MarkDown content with your preferred editor.
  • Copy the resulted HTML content (the content of the body tag) as the blog HTML content
  • Add at the beginning the following lines:
<link href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/styles/solarized_light.min.css' rel='stylesheet'/>
<script src='//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/highlight.min.js'/>
<script>hljs.initHighlightingOnLoad();</script>
<style>

:not(pre)>code{
  margin: 0 0px;
  padding: 0px 5px;
  border: 1px solid #eaeaea;
  color:RoyalBlue;
  background-color: #f8f8f8;
  border-radius: 3px;

}
.hljs{
background-color: #f8f8f8;
font-weight:bold;
border: 2px solid #eaeaea;
}

body, pre {
font-family: Consolas, Arial, Tahoma, Helvetica, FreeSans, sans-serif;

}
body{
font-size: 14px;
  }
pre{
font-size: 12px;

  }
h1, h2, h3, h4, h5, h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
}

h1 {
  font-size: 28px;
  color: #000;
}
h2 {
  font-size: 24px;
  border-bottom: 1px solid #ccc;
  color: #000;
}
h3 {
  font-size: 18px;
}
h4 {
  font-size: 16px;
}
h5 {
  font-size: 14px;
}
h6 {
  color: #777;
  font-size: 14px;
}

</style>

Go to https://cdnjs.com/libraries/highlight.js to pick-up your preferred colouring scheme

As an alternative you can paste the above snippet before </head> tag in blog template.

Certificate Management

Certificate creation

Use XCA (http://sourceforge.net/projects/xca)

Create Certification Authority root certificate

  • Create a new database
  • Create a new key by using the CA template (RSA, minimum 2048 bits length)
  • Create a new certificate for CA (using previously created key, minimum SHA256 signing algorithm - SHA1 is about to be not supported anymore starting from 2017)

Create certificates

  • Create a new Template based on HTTPS server existing template (depending on your future needs)
  • Create certificates:
    • Go to Templates tab->right click->Create certificate;if using the option to create a new certificate from Certificates tab do not forget to select a template and click one of the Apply options buttons
    • Change the default option for signing the certificate (->Use this certificate...; select the CA certificate)
    • Change the default signing algorithm to something >SHA1
    • On Subject tab:
      • Change the relevant fields (commonName especially)
      • Create a new private key for certificate (Generate new key button)

Misc operations

Apart from exporting certificates to different formats (including or not private key, including or not certificate chain) you can obtain the public or private key in PEM format by going to Private Keys tab ->right click a key->Export to clipboard, etc

Managing a JKS Store

Use KeyStore Explorer (http://keystore-explorer.sourceforge.net/). An alternative would be to use Poretcle but KeyStore has the advantage (from my point of view) that being a Windows application can be registered as default for JKS files.

Import now the trusted CA certificates (*.cer or *.crt for instance) and the key pairs (certificates containing the private key) (*.p12 for instance).

IyBDZXJ0aWZpY2F0ZSBjcmVhdGlvbg0KDQpVc2UgKipYQ0EqKiAoW2h0dHA6Ly9zb3VyY2Vmb3JnZS5uZXQvcHJvamVjdHMveGNhXShodHRwOi8vc291cmNlZm9yZ2UubmV0L3Byb2plY3RzL3hjYSkpDQoNCiMjIENyZWF0ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSByb290IGNlcnRpZmljYXRlDQoqIENyZWF0ZSBhIG5ldyBkYXRhYmFzZQ0KKiBDcmVhdGUgYSBuZXcga2V5IGJ5IHVzaW5nIHRoZSBDQSB0ZW1wbGF0ZSAoUlNBLCBtaW5pbXVtIDIwNDggYml0cyBsZW5ndGgpDQoqIENyZWF0ZSBhIG5ldyBjZXJ0aWZpY2F0ZSBmb3IgQ0EgKHVzaW5nIHByZXZpb3VzbHkgY3JlYXRlZCBrZXksIG1pbmltdW0gYFNIQTI1NmAgc2lnbmluZyBhbGdvcml0aG0gLSBgU0hBMWAgaXMgYWJvdXQgdG8gYmUgbm90IHN1cHBvcnRlZCBhbnltb3JlIHN0YXJ0aW5nIGZyb20gMjAxNykNCg0KIyMgQ3JlYXRlIGNlcnRpZmljYXRlcw0KDQoqIENyZWF0ZSBhIG5ldyBUZW1wbGF0ZSBiYXNlZCBvbiBIVFRQUyBzZXJ2ZXIgZXhpc3RpbmcgdGVtcGxhdGUgKGRlcGVuZGluZyBvbiB5b3VyIGZ1dHVyZSBuZWVkcykNCiogQ3JlYXRlIGNlcnRpZmljYXRlczoNCgkqIEdvIHRvIGBUZW1wbGF0ZXNgIHRhYi0+YHJpZ2h0IGNsaWNrYC0+YENyZWF0ZSBjZXJ0aWZpY2F0ZWA7aWYgdXNpbmcgdGhlIG9wdGlvbiB0byBjcmVhdGUgYSBuZXcgY2VydGlmaWNhdGUgZnJvbSBgQ2VydGlmaWNhdGVzYCB0YWIgZG8gbm90IGZvcmdldCB0byBzZWxlY3QgYSB0ZW1wbGF0ZSBhbmQgY2xpY2sgb25lIG9mIHRoZSBgQXBwbHlgIG9wdGlvbnMgYnV0dG9ucw0KCSogQ2hhbmdlIHRoZSBkZWZhdWx0IG9wdGlvbiBmb3Igc2lnbmluZyB0aGUgY2VydGlmaWNhdGUgKC0+VXNlIHRoaXMgY2VydGlmaWNhdGUuLi47IHNlbGVjdCB0aGUgQ0EgY2VydGlmaWNhdGUpDQoJKiBDaGFuZ2UgdGhlIGRlZmF1bHQgc2lnbmluZyBhbGdvcml0aG0gdG8gc29tZXRoaW5nID5TSEExDQoJKiBPbiBgU3ViamVjdGAgdGFiOg0KCQkqIENoYW5nZSB0aGUgcmVsZXZhbnQgZmllbGRzIChjb21tb25OYW1lIGVzcGVjaWFsbHkpDQoJCSogQ3JlYXRlIGEgbmV3IHByaXZhdGUga2V5IGZvciBjZXJ0aWZpY2F0ZSAoYEdlbmVyYXRlIG5ldyBrZXlgIGJ1dHRvbikNCg0KIyMgTWlzYyBvcGVyYXRpb25zDQpBcGFydCBmcm9tIGV4cG9ydGluZyBjZXJ0aWZpY2F0ZXMgdG8gZGlmZmVyZW50IGZvcm1hdHMgKGluY2x1ZGluZyBvciBub3QgcHJpdmF0ZSBrZXksIGluY2x1ZGluZyBvciBub3QgY2VydGlmaWNhdGUgY2hhaW4pIHlvdSBjYW4gb2J0YWluIHRoZSBwdWJsaWMgb3IgcHJpdmF0ZSBrZXkgaW4gUEVNIGZvcm1hdCBieSBnb2luZyB0byBgUHJpdmF0ZSBLZXlzYCB0YWIgLT5gcmlnaHQgY2xpY2tgIGEga2V5LT5gRXhwb3J0YCB0byBjbGlwYm9hcmQsIGV0Yw0KDQoNCiMgTWFuYWdpbmcgYSBKS1MgU3RvcmUNCg0KVXNlIEtleVN0b3JlIEV4cGxvcmVyIChbaHR0cDovL2tleXN0b3JlLWV4cGxvcmVyLnNvdXJjZWZvcmdlLm5ldC9dKGh0dHA6Ly9rZXlzdG9yZS1leHBsb3Jlci5zb3VyY2Vmb3JnZS5uZXQvKSkuDQpBbiBhbHRlcm5hdGl2ZSB3b3VsZCBiZSB0byB1c2UgW1BvcmV0Y2xlXShodHRwOi8vcG9ydGVjbGUuc291cmNlZm9yZ2UubmV0LykgYnV0IEtleVN0b3JlIGhhcyB0aGUgYWR2YW50YWdlIChmcm9tIG15IHBvaW50IG9mIHZpZXcpIHRoYXQgYmVpbmcgYSBXaW5kb3dzIGFwcGxpY2F0aW9uIGNhbiBiZSByZWdpc3RlcmVkIGFzIGRlZmF1bHQgZm9yIEpLUyBmaWxlcy4NCg0KSW1wb3J0IG5vdyB0aGUgdHJ1c3RlZCBDQSBjZXJ0aWZpY2F0ZXMgKFwqLmNlciBvciBcKi5jcnQgZm9yIGluc3RhbmNlKSBhbmQgdGhlIGtleSBwYWlycyAoY2VydGlmaWNhdGVzIGNvbnRhaW5pbmcgdGhlIHByaXZhdGUga2V5KSAoKi5wMTIgZm9yIGluc3RhbmNlKS4=

Chrome Windows Integrated Authentication with ADFS 2.0

Method 1

Since Chrome will use primarily IE configurations add the site in Internet Options->Security->Local Intranet->Sites->Advanced (provide the FDQN, using wildcards if required and press Add)

Method 2

If you do not want to use IE settings run the following registry entries

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome]
"AuthServerWhitelist"="fdqn"
"AuthSchemes"="basic,digest,ntlm,negotiate"
"AuthNegotiateDelegateWhitelist"="fdqn"

fdqn can be a single host qualified name (like www.domain.com ) or a full domain (like *domain.com); the wild card syntax is slightly different the one of IE (no dot required!?)

ADFS/SAML Remark

In order to have WIA working against ADFS (at least version 2.0) we have two constraints:

  • Use integrated authentication (in web.config)
  <microsoft.identityServer.web>
    <localAuthenticationTypes>
      <add name="Integrated" page="auth/integrated/" />
      <add name="Forms" page="FormsSignIn.aspx" />
      <add name="TlsClient" page="auth/sslclient/" />
...
  • Disable Extended Protection for adsf\ls site (Authentication->Windows Authentication->Advanced Settings, set `Extended Protection' to Off)
IyBNZXRob2QgMQ0KU2luY2UgQ2hyb21lIHdpbGwgdXNlIHByaW1hcmlseSAgSUUgY29uZmlndXJhdGlvbnMgYWRkIHRoZSBzaXRlIGluIGBJbnRlcm5ldCBPcHRpb25zYC0+YFNlY3VyaXR5YC0+YExvY2FsIEludHJhbmV0YC0+YFNpdGVzYC0+YEFkdmFuY2VkYCAocHJvdmlkZSB0aGUgRkRRTiwgdXNpbmcgd2lsZGNhcmRzIGlmIHJlcXVpcmVkIGFuZCBwcmVzcyBgQWRkYCkNCg0KIyBNZXRob2QgMg0KSWYgeW91IGRvIG5vdCB3YW50IHRvIHVzZSBJRSBzZXR0aW5ncyBydW4gdGhlIGZvbGxvd2luZyByZWdpc3RyeSBlbnRyaWVzDQogDQpgYGANCldpbmRvd3MgUmVnaXN0cnkgRWRpdG9yIFZlcnNpb24gNS4wMA0KDQpbSEtFWV9MT0NBTF9NQUNISU5FXFNvZnR3YXJlXFBvbGljaWVzXEdvb2dsZVxDaHJvbWVdDQoiQXV0aFNlcnZlcldoaXRlbGlzdCI9ImZkcW4iDQoiQXV0aFNjaGVtZXMiPSJiYXNpYyxkaWdlc3QsbnRsbSxuZWdvdGlhdGUiDQoiQXV0aE5lZ290aWF0ZURlbGVnYXRlV2hpdGVsaXN0Ij0iZmRxbiINCmBgYA0KDQpgZmRxbmAgY2FuIGJlIGEgc2luZ2xlIGhvc3QgcXVhbGlmaWVkIG5hbWUgKGxpa2Ugd3d3LmRvbWFpbi5jb20gKSBvciBhIGZ1bGwgZG9tYWluIChsaWtlICpkb21haW4uY29tKTsgdGhlIHdpbGQgY2FyZCBzeW50YXggaXMgc2xpZ2h0bHkgZGlmZmVyZW50IHRoZSBvbmUgb2YgSUUgKG5vIGRvdCByZXF1aXJlZCE/KQ0KDQojIEFERlMvU0FNTCBSZW1hcmsNCg0KSW4gb3JkZXIgdG8gaGF2ZSBXSUEgd29ya2luZyBhZ2FpbnN0IEFERlMgKGF0IGxlYXN0IHZlcnNpb24gMi4wKSB3ZSBoYXZlIHR3byBjb25zdHJhaW50czoNCiogVXNlIGludGVncmF0ZWQgYXV0aGVudGljYXRpb24gKGluIHdlYi5jb25maWcpDQpgYGB4bWwNCiAgPG1pY3Jvc29mdC5pZGVudGl0eVNlcnZlci53ZWI+DQogICAgPGxvY2FsQXV0aGVudGljYXRpb25UeXBlcz4NCiAgICAgIDxhZGQgbmFtZT0iSW50ZWdyYXRlZCIgcGFnZT0iYXV0aC9pbnRlZ3JhdGVkLyIgLz4NCiAgICAgIDxhZGQgbmFtZT0iRm9ybXMiIHBhZ2U9IkZvcm1zU2lnbkluLmFzcHgiIC8+DQogICAgICA8YWRkIG5hbWU9IlRsc0NsaWVudCIgcGFnZT0iYXV0aC9zc2xjbGllbnQvIiAvPg0KLi4uDQpgYGANCiogRGlzYWJsZSBFeHRlbmRlZCBQcm90ZWN0aW9uIGZvciBhZHNmXFxscyBzaXRlIChgQXV0aGVudGljYXRpb25gLT5gV2luZG93cyBBdXRoZW50aWNhdGlvbmAtPmBBZHZhbmNlZCBTZXR0aW5nc2AsIHNldCBgRXh0ZW5kZWQgUHJvdGVjdGlvbicgdG8gT2ZmKQ==

HTTPS in Wildfly

Create a certificate store (file name wildfly.jks) placed in configuration directory. Add both CA root public certificate and SSL certificate (including private key, like PKCS#12 format).

Add a security-realm in security-realms section.

        <security-realms>
...
            <security-realm name="SslRealm">
                <server-identities>
                    <ssl>
                        <keystore path="wildfly.jks" relative-to="jboss.server.config.dir" keystore-password="secret4jks" alias="alias-of-ssl-certificate" key-password="secret4certificate"/>
                    </ssl>
                </server-identities>
            </security-realm>
        </security-realms>

Add a https-listener referring the previously defined security-realm

        <subsystem xmlns="urn:jboss:domain:undertow:1.2">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" socket-binding="http" max-parameters="5000"/>
                <https-listener name="default-ssl" socket-binding="https" security-realm="SslRealm"/>
...

PHA+Q3JlYXRlIGEgY2VydGlmaWNhdGUgc3RvcmUgKGZpbGUgbmFtZSA8c3Ryb25nPndpbGRmbHkuamtzPC9zdHJvbmc+KSBwbGFjZWQgaW4gY29uZmlndXJhdGlvbiBkaXJlY3RvcnkuIEFkZCBib3RoIENBIHJvb3QgIHB1YmxpYyBjZXJ0aWZpY2F0ZSBhbmQgU1NMIGNlcnRpZmljYXRlIChpbmNsdWRpbmcgcHJpdmF0ZSBrZXksIGxpa2UgPHN0cm9uZz5QS0NTIzEyPC9zdHJvbmc+IGZvcm1hdCkuPC9wPg0KPHA+QWRkIGEgPGNvZGU+c2VjdXJpdHktcmVhbG08L2NvZGU+IGluIDxjb2RlPnNlY3VyaXR5LXJlYWxtczwvY29kZT4gc2VjdGlvbi48L3A+DQo8cHJlPjxjb2RlIGNsYXNzPSJsYW5ndWFnZS14bWwiPiAgICAgICAgJmx0O3NlY3VyaXR5LXJlYWxtcyZndDsNCi4uLg0KICAgICAgICAgICAgJmx0O3NlY3VyaXR5LXJlYWxtIG5hbWU9JnF1b3Q7U3NsUmVhbG0mcXVvdDsmZ3Q7DQogICAgICAgICAgICAgICAgJmx0O3NlcnZlci1pZGVudGl0aWVzJmd0Ow0KICAgICAgICAgICAgICAgICAgICAmbHQ7c3NsJmd0Ow0KICAgICAgICAgICAgICAgICAgICAgICAgJmx0O2tleXN0b3JlIHBhdGg9JnF1b3Q7d2lsZGZseS5qa3MmcXVvdDsgcmVsYXRpdmUtdG89JnF1b3Q7amJvc3Muc2VydmVyLmNvbmZpZy5kaXImcXVvdDsga2V5c3RvcmUtcGFzc3dvcmQ9JnF1b3Q7c2VjcmV0NGprcyZxdW90OyBhbGlhcz0mcXVvdDthbGlhcy1vZi1zc2wtY2VydGlmaWNhdGUmcXVvdDsga2V5LXBhc3N3b3JkPSZxdW90O3NlY3JldDRjZXJ0aWZpY2F0ZSZxdW90Oy8mZ3Q7DQogICAgICAgICAgICAgICAgICAgICZsdDsvc3NsJmd0Ow0KICAgICAgICAgICAgICAgICZsdDsvc2VydmVyLWlkZW50aXRpZXMmZ3Q7DQogICAgICAgICAgICAmbHQ7L3NlY3VyaXR5LXJlYWxtJmd0Ow0KICAgICAgICAmbHQ7L3NlY3VyaXR5LXJlYWxtcyZndDsNCjwvY29kZT48L3ByZT4NCjxwPkFkZCBhIDxjb2RlPmh0dHBzLWxpc3RlbmVyPC9jb2RlPiByZWZlcnJpbmcgdGhlIHByZXZpb3VzbHkgZGVmaW5lZCA8Y29kZT5zZWN1cml0eS1yZWFsbTwvY29kZT48L3A+DQo8cHJlPjxjb2RlIGNsYXNzPSJsYW5ndWFnZS14bWwiPiAgICAgICAgJmx0O3N1YnN5c3RlbSB4bWxucz0mcXVvdDt1cm46amJvc3M6ZG9tYWluOnVuZGVydG93OjEuMiZxdW90OyZndDsNCiAgICAgICAgICAgICZsdDtidWZmZXItY2FjaGUgbmFtZT0mcXVvdDtkZWZhdWx0JnF1b3Q7LyZndDsNCiAgICAgICAgICAgICZsdDtzZXJ2ZXIgbmFtZT0mcXVvdDtkZWZhdWx0LXNlcnZlciZxdW90OyZndDsNCiAgICAgICAgICAgICAgICAmbHQ7aHR0cC1saXN0ZW5lciBuYW1lPSZxdW90O2RlZmF1bHQmcXVvdDsgc29ja2V0LWJpbmRpbmc9JnF1b3Q7aHR0cCZxdW90OyBtYXgtcGFyYW1ldGVycz0mcXVvdDs1MDAwJnF1b3Q7LyZndDsNCiAgICAgICAgICAgICAgICAmbHQ7aHR0cHMtbGlzdGVuZXIgbmFtZT0mcXVvdDtkZWZhdWx0LXNzbCZxdW90OyBzb2NrZXQtYmluZGluZz0mcXVvdDtodHRwcyZxdW90OyBzZWN1cml0eS1yZWFsbT0mcXVvdDtTc2xSZWFsbSZxdW90Oy8mZ3Q7DQouLi4NCg0KPC9jb2RlPjwvcHJlPg0K

Wednesday, April 15, 2015

MdCharm

  • Paste above any other markup the style tag
  • Remove any & (there is one inside a comment)
  • Comment the aside specification (due to float:right all layout is messed up)
  • Paste the body content below style
  • use Notepad MimeTools plugin to encode Base64 the md content (be sure to select all content before applying the plugin)
  • paste the result inside a <div style="display:none:"> </div>
KiBQYXN0ZSBhYm92ZSBhbnkgb3RoZXIgbWFya3VwIHRoZSAqKnN0eWxlKiogdGFnDQoqIFJlbW92ZSBhbnkgJiAodGhlcmUgaXMgb25lIGluc2lkZSBhIGNvbW1lbnQpDQoqIENvbW1lbnQgdGhlICoqYXNpZGUqKiBzcGVjaWZpY2F0aW9uIChkdWUgdG8gZmxvYXQ6cmlnaHQgYWxsIGxheW91dCBpcyBtZXNzZWQgdXApDQoqIFBhc3RlIHRoZSAqKmJvZHkqKiBjb250ZW50IGJlbG93ICoqc3R5bGUqKg0KKiB1c2UgTm90ZXBhZCBNaW1lVG9vbHMgcGx1Z2luIHRvIGVuY29kZSBCYXNlNjQgdGhlIG1kIGNvbnRlbnQgKGJlIHN1cmUgdG8gc2VsZWN0IGFsbCBjb250ZW50IGJlZm9yZSBhcHBseWluZyB0aGUgcGx1Z2luKQ0KKiBwYXN0ZSB0aGUgcmVzdWx0IGluc2lkZSBhICZsdDtkaXYgc3R5bGU9JnF1b3Q7ZGlzcGxheTpub25lOiZxdW90OyZndDsgJmx0Oy9kaXYmZ3Q7

Tuesday, April 14, 2015

JSF login through Spring Security (4.0.0)

Add the required entries in web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext-security.xml
        </param-value>
    </context-param>
...
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher><!-- mandatory to allow the managed bean to forward the request to the filter -->
    </filter-mapping>
...    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

Be sure that springSecurityFilterChain hase the two dispatcher properties set as shown above otherwise the first method will not work

Configure security rules, authentication manager, authentication provider, etc

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:s="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" 
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    
        
    <context:annotation-config />
    
    <!--s:http pattern="/facesx/**" security="none"/-->

    <s:http authentication-manager-ref="authenticationManager">

        <s:intercept-url pattern="/faces/auth/admin/**" access="hasRole('ROLE_Admins')" />
        <s:intercept-url pattern="/faces/auth/**" access="authenticated" />
        <s:intercept-url pattern="/faces/login.xhtml" access="permitAll" />

        <!--s:form-login/-->
        <s:form-login login-page="/faces/login.xhtml" default-target-url="/faces/auth/home.xhtml"/>
        <s:csrf disabled="true"/>
    </s:http>


    <s:authentication-manager id="authenticationManager">
        <s:authentication-provider ref='AD-LdapProvider' />
    </s:authentication-manager>

    <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg value="ldap://mydomain.local:389"/>
        
        <property name="userDn" value="CN=aduser,OU=App Users,OU=MyCompany,DC=mydomain,DC=local" />
        <property name="password" value="******" />
    </bean>

    <bean id="AD-LdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userSearch">
                    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                        <constructor-arg index="0" value="OU=MyCompany,DC=mydomain,DC=local"/>
                        <constructor-arg index="1" value="(&amp;(objectClass=user)(sAMAccountName={0}))"/>
                        <constructor-arg index="2" ref="contextSource" />
                        <property name="searchSubtree" value="true"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
        <constructor-arg>
            <bean class="org.springframework.security.ldap.userdetails.NestedLdapAuthoritiesPopulator">
                <constructor-arg ref="contextSource" />
                <constructor-arg value="OU=MyCompany,DC=mydomain,DC=local" />
                <property name="groupSearchFilter" value="(&amp;(objectClass=group)(groupType:1.2.840.113556.1.4.803:=-2147483648)(member={0}))"/><!--http://ldapwiki.willeke.com/wiki/Active%20Directory%20Group%20Related%20Searches#section-Active+Directory+Group+Related+Searches-AllSecurityGroupsLocalGlobalAndUniversal -->
                <property name="rolePrefix" value="ROLE_"/>
                <property name="searchSubtree" value="true"/>
                <property name="convertToUpperCase" value="false"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

Remark: groupType:1.2.840.113556.1.4.803:=-2147483648 will filter only security group types

Method 1

Have the login form as follows:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton action="#{loginController.login}" value="Login" ajax="false"/>
        </h:form>

In managed bean, method called at pressing Login button:

Named()
@SessionScoped()
public class LoginController implements Serializable {
...
    public void login() throws ServletException, IOException { 
        
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/login");
        dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
        FacesContext.getCurrentInstance().responseComplete();
    }
}

Be sure that:

  • you have not overriden the default mapping for login-processing-url (default value "/login" as oposed to "/j_spring_security_check" for older versions of Spring
  • have the ids of user and password input buttons as "username" and "password" respectively (be aware older Spring versions have the defaults set to "j_xxx"; you can actually configure these values from username-parameter and password-parameter attributes of login element)
  • use ajax="false" in button definition
  • add prependId="false" to form definition
  • have the FORWARD and REQUEST dispatcher parameters set as shown above

Method 2

Make all web.xml and applicationContext-security.xml as above, apart from:

<s:form-login login-page="/faces/login.xhtml"/><!-- no  default-target-url -->

Use a regular JSF login page/controller and perform login as usual:

    public String login() {
        try {
            getRequest().login(lastLoginName, password);
            ...
            return "/auth/home?faces-redirect=true";
        } catch (ServletException ex) {
            Logger.getLogger(CommonController.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "/error?faces-redirect=true";

Method 3

Use the same login form as for Method 1 but add onclick method and remove action:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton value="Login" ajax="false" 
 onclick="$('#form').attr('action',$('#form').attr('action').replace('/faces/login.xhtml','/login'))"/>
        </h:form>

The main disatvantage of this method is that you have no chance to do additional processing after successfull login (but you can do it through JSF events in page defined as default-target-url)