The buji-pac4j
project is an easy and powerful security library for Shiro web applications which supports authentication and authorization, but also advanced features like CSRF protection.
It's based on Java 8, Shiro 1.4 and on the pac4j security engine v2. It's available under the Apache 2 license.
- A client represents an authentication mechanism. It performs the login process and returns a user profile. An indirect client is for UI authentication while a direct client is for web services authentication:
▸ OAuth - SAML - CAS - OpenID Connect - HTTP - OpenID - Google App Engine - Kerberos - LDAP - SQL - JWT - MongoDB - CouchDB - IP address
- An authorizer is meant to check authorizations on the authenticated user profile(s) or on the current web context:
▸ Roles / permissions - Anonymous / remember-me / (fully) authenticated - Profile type, attribute - CORS - CSRF - Security headers - IP address, HTTP method
-
The
SecurityFilter
protects an url by checking that the user is authenticated and that the authorizations are valid, according to the clients and authorizers configuration. If the user is not authenticated, it performs authentication for direct clients or starts the login process for indirect clients -
The
CallbackFilter
finishes the login process for an indirect client.
Just follow these easy steps to secure your Shiro web application:
buji-pac4j
+ pac4j-*
libraries)
1) Add the required dependencies (You need to add a dependency on:
- the
buji-pac4j
library (groupId: io.buji, version: 3.1.0) - the appropriate
pac4j
submodules (groupId: org.pac4j, version: 2.2.1):pac4j-oauth
for OAuth support (Facebook, Twitter...),pac4j-cas
for CAS support,pac4j-ldap
for LDAP authentication, etc.
All released artifacts are available in the Maven central repository.
Config
+ Client
+ Authorizer
)
2) Define the configuration (The configuration (org.pac4j.core.config.Config
) contains all the clients and authorizers required by the application to handle security.
It must be defined in your shiro.ini
file:
[main]
roleAdminAuthGenerator = org.pac4j.demo.shiro.RoleAdminAuthGenerator
oidcConfig = org.pac4j.oidc.config.OidcConfiguration
oidcConfig.clientId = 167480702619-8e1lo80dnu8bpk3k0lvvj27noin97vu9.apps.googleusercontent.com
oidcConfig.secret =MhMme_Ik6IH2JMnAT6MFIfee
oidcConfig.useNonce = true
googleOidClient = org.pac4j.oidc.client.GoogleOidcClient
googleOidClient.configuration = $oidcConfig
googleOidClient.authorizationGenerator = $roleAdminAuthGenerator
saml2Config = org.pac4j.saml.client.SAML2ClientConfiguration
saml2Config.keystorePath = resource:samlKeystore.jks
saml2Config.keystorePassword = pac4j-demo-passwd
saml2Config.privateKeyPassword = pac4j-demo-passwd
saml2Config.identityProviderMetadataPath = resource:metadata-okta.xml
saml2Config.maximumAuthenticationLifetime = 3600
saml2Config.serviceProviderEntityId = http://localhost:8080/callback?client_name=SAML2Client
saml2Config.serviceProviderMetadataPath = sp-metadata.xml
saml2Client = org.pac4j.saml.client.SAML2Client
saml2Client.configuration = $saml2Config
facebookClient = org.pac4j.oauth.client.FacebookClient
facebookClient.key = 145278422258960
facebookClient.secret = be21409ba8f39b5dae2a7de525484da8
twitterClient = org.pac4j.oauth.client.TwitterClient
twitterClient.key = CoxUiYwQOSFDReZYdjigBA
twitterClient.secret = 2kAzunH5Btc4gRSaMr7D7MkyoJ5u1VzbOOzE8rBofs
simpleAuthenticator = org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator
formClient = org.pac4j.http.client.indirect.FormClient
formClient.loginUrl = http://localhost:8080/loginForm.jsp
formClient.authenticator = $simpleAuthenticator
indirectBasicAuthClient = org.pac4j.http.client.indirect.IndirectBasicAuthClient
indirectBasicAuthClient.authenticator = $simpleAuthenticator
casConfig = org.pac4j.cas.config.CasConfiguration
casConfig.loginUrl = https://casserverpac4j.herokuapp.com/login
casClient = org.pac4j.cas.client.CasClient
casClient.configuration = $casConfig
vkClient = org.pac4j.oauth.client.VkClient
vkClient.key = 4224582
vkClient.secret = nDc4IHTqu8ioFMkHKifq
signingConfig = org.pac4j.jwt.config.signature.SecretSignatureConfiguration
signingConfig.secret = 12345678901234567890123456789012
encryptionConfig = org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration
encryptionConfig.secret = 12345678901234567890123456789012
jwtAuthenticator = org.pac4j.jwt.credentials.authenticator.JwtAuthenticator
jwtAuthenticator.signatureConfiguration = $signingConfig
jwtAuthenticator.encryptionConfiguration = $encryptionConfig
parameterClient = org.pac4j.http.client.direct.ParameterClient
parameterClient.parameterName = token
parameterClient.authenticator = $jwtAuthenticator
parameterClient.supportGetRequest = true
parameterClient.supportPostRequest = false
directBasicAuthClient = org.pac4j.http.client.direct.DirectBasicAuthClient
directBasicAuthClient.authenticator = $simpleAuthenticator
clients.callbackUrl = http://localhost:8080/callback
clients.clients = $googleOidClient,$facebookClient,$twitterClient,$formClient,$indirectBasicAuthClient,$casClient,$vkClient,$saml2Client,$parameterClient,$directBasicAuthClient
requireRoleAdmin = org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer
requireRoleAdmin.elements = ROLE_ADMIN
customAuthorizer = org.pac4j.demo.shiro.CustomAuthorizer
excludedPathMatcher = org.pac4j.core.matching.ExcludedPathMatcher
excludedPathMatcher.excludePath = ^/facebook/notprotected\.jsp$
config.authorizers = admin:$requireRoleAdmin,custom:$customAuthorizer
config.matchers = excludedPath:$excludedPathMatcher
The clients
and config
components are available by default (they are automatically loaded thanks to the Pac4jIniEnvironment
component).
http://localhost:8080/callback
is the url of the callback endpoint, which is only necessary for indirect clients.
Notice that you can define specific matchers via the matchers
map.
SecurityFilter
)
3) Protect urls (You can protect (authentication + authorizations) the urls of your Shiro application by using the SecurityFilter
and declaring the filter on the appropriate url mapping. It has the following behaviour:
-
If the HTTP request matches the
matchers
configuration (or nomatchers
are defined), the security is applied. Otherwise, the user is automatically granted access. -
First, if the user is not authenticated (no profile) and if some clients have been defined in the
clients
parameter, a login is tried for the direct clients. -
Then, if the user has a profile, authorizations are checked according to the
authorizers
configuration. If the authorizations are valid, the user is granted access. Otherwise, a 403 error page is displayed. -
Finally, if the user is still not authenticated (no profile), he is redirected to the appropriate identity provider if the first defined client is an indirect one in the
clients
configuration. Otherwise, a 401 error page is displayed.
The following parameters are available:
-
config
: the security configuration previously defined -
clients
(optional): the list of client names (separated by commas) used for authentication:
- in all cases, this filter requires the user to be authenticated. Thus, if the
clients
is blank or not defined, the user must have been previously authenticated - if the
client_name
request parameter is provided, only this client (if it exists in theclients
) is selected.
authorizers
(optional): the list of authorizer names (separated by commas) used to check authorizations:
- if the
authorizers
is blank or not defined, no authorization is checked - the following authorizers are available by default (without defining them in the configuration):
isFullyAuthenticated
to check if the user is authenticated but not remembered,isRemembered
for a remembered user,isAnonymous
to ensure the user is not authenticated,isAuthenticated
to ensure the user is authenticated (not necessary by default unless you use theAnonymousClient
)hsts
to use theStrictTransportSecurityHeader
authorizer,nosniff
forXContentTypeOptionsHeader
,noframe
forXFrameOptionsHeader
,xssprotection
forXSSProtectionHeader
,nocache
forCacheControlHeader
orsecurityHeaders
for the five previous authorizerscsrfToken
to use theCsrfTokenGeneratorAuthorizer
with theDefaultCsrfTokenGenerator
(it generates a CSRF token and saves it as thepac4jCsrfToken
request attribute and in thepac4jCsrfToken
cookie),csrfCheck
to check that this previous token has been sent as thepac4jCsrfToken
header or parameter in a POST request andcsrf
to use both previous authorizers.
-
matchers
(optional): the list of matcher names (separated by commas) that the request must satisfy to check authentication / authorizations -
multiProfile
(optional): it indicates whether multiple authentications (and thus multiple profiles) must be kept at the same time (false
by default).
In your shiro.ini
file:
[main]
facebookSecurityFilter = io.buji.pac4j.filter.SecurityFilter
facebookSecurityFilter.config = $config
facebookSecurityFilter.clients = FacebookClient
[url]
/facebook/** = facebookSecurityFilter
CallbackFilter
)
4) Define the callback endpoint only for indirect clients (For indirect clients (like Facebook), the user is redirected to an external identity provider for login and then back to the application.
Thus, a callback endpoint is required in the application. It is managed by the CallbackFilter
which has the following behaviour:
-
the credentials are extracted from the current request to fetch the user profile (from the identity provider) which is then saved in the web session
-
finally, the user is redirected back to the originally requested url (or to the
defaultUrl
).
The following parameters are available:
-
config
(automatically defined): the security configuration previously defined -
defaultUrl
(optional): it's the default url after login if no url was originally requested (/
by default) -
multiProfile
(optional): it indicates whether multiple authentications (and thus multiple profiles) must be kept at the same time (false
by default) -
httpActionAdapter
(optional): a specificHttpActionAdapter
for custom treatments/actions.
In your shiro.ini
file:
[main]
callbackFilter.defaultUrl = /afterCallback
[url]
/callback = callbackFilter
The callbackFilter
component and its config
are automatically defined by default (thanks to the Pac4jIniEnvironment
component).
5) Get the user profile
Like for any Shiro web application, you can get the authenticated user via the SecurityUtils.getSubject().getPrincipals()
.
If the user is authenticated or remembered, the appropriate principal: Pac4jPrincipal
will be stored in the principals,
on which you can get the main profile (getProfile
method) or all profiles (getProfiles
method) of the authenticated user:
final PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
if (principals != null) {
final Pac4jPrincipal principal = principals.oneByType(Pac4jPrincipal.class);
if (principal != null) {
CommonProfile profile = principal.getProfile();
}
}
The retrieved profile is at least a CommonProfile
, from which you can retrieve the most common attributes that all profiles share. But you can also cast the user profile to the appropriate profile according to the provider used for authentication. For example, after a Facebook authentication:
FacebookProfile facebookProfile = (FacebookProfile) commonProfile;
6) Logout
Like for any Shiro webapp, use the default logout filter (in your shiro.ini
file):
[url]
/logout = logout
Migration guide
From the deprecated shiro-cas module (CAS support)
Instead of using the shiro-cas
module, you need to use the buji-pac4j
library and the pac4j-cas
module. Though, the way both implementations work is close.
The CasFilter
is replaced by the CallbackFilter
which has the same role (receiving callbacks from identity providers), but not only for CAS.
The CasRealm
is replaced by the Pac4jRealm
and the CasSubjectFactory
by the Pac4jsubjectFactory
.
Finally, you must use the SecurityFilter
to secure an url, in addition of the default Shiro filters (like roles
).
2.0 -> 2.2
The config
, clients
, pac4jRealm
, pac4jSubjectFactory
and callbackFilter
components are available by default (they are automatically loaded thanks to the Pac4jIniEnvironment
component).
1.4 - > 2.0
The buji-pac4j
library strongly changes in version 2:
- the
core
andservlet
modules are merged back into one main module - the
ClientRealm
is replaced by thePac4jRealm
and theClientToken
by thePac4jToken
- the
ClientUserFilter
,ClientPermissionsAuthorizationFilter
andClientRolesAuthorizationFilter.java
are removed, more generally replaced by theSecurityFilter
which ensures the url security (as usually in the pac4j world) - the
CallbackFilter
replaces theClientFilter
to finish the login process for indirect clients (as usually in the pac4j world).
Demo
The demo webapp: buji-pac4j-demo is available for tests and implements many authentication mechanisms: Facebook, Twitter, form, basic auth, CAS, SAML, OpenID Connect, JWT...
Release notes
See the release notes. Learn more by browsing the buji-pac4j Javadoc and the pac4j Javadoc.
Need help?
If you need commercial support (premium support or new/specific features), contact us at info@pac4j.org.
If you have any question, please use the following mailing lists:
Development
The version 3.2.0-SNAPSHOT is under development.
Maven artifacts are built via Travis: and available in the Sonatype snapshots repository. This repository must be added in the Maven pom.xml file for example:
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>