The spring-security-pac4j project is an easy and powerful security library for Spring Security web applications which supports authentication and authorization, but also advanced features like session fixation and CSRF protection.
It's based on Java 8, Spring Security 4.1 and on the pac4j security engine. 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 - LDAP - SQL - JWT - MongoDB - Stormpath - 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
SecurityFilterprotects 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
CallbackFilterfinishes the login process for an indirect client.
==
Just follow these easy steps to secure your Spring Security web application:
You need to add a dependency on:
- the
spring-security-pac4jlibrary (groupId: org.pac4j, version: 2.0.0) - the appropriate
pac4jsubmodules (groupId: org.pac4j, version: 1.9.1):pac4j-oauthfor OAuth support (Facebook, Twitter...),pac4j-casfor CAS support,pac4j-ldapfor LDAP authentication, etc.
All released artifacts are available in the Maven central repository.
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 a Spring application context file (or via a Java configuration class):
<bean id="samlConfig" class="org.pac4j.saml.client.SAML2ClientConfiguration">
<property name="keystorePath" value="resource:samlKeystore.jks" />
<property name="keystorePassword" value="pac4j-demo-passwd" />
<property name="privateKeyPassword" value="pac4j-demo-passwd" />
<property name="identityProviderMetadataPath" value="resource:metadata-okta.xml" />
<property name="maximumAuthenticationLifetime" value="3600" />
<property name="serviceProviderEntityId" value="http://localhost:8080/callback?client_name=SAML2Client" />
<property name="serviceProviderMetadataPath" value="sp-metadata.xml" />
</bean>
<bean id="samlClient" class="org.pac4j.saml.client.SAML2Client">
<constructor-arg name="configuration" ref="samlConfig" />
</bean>
<bean id="facebookClient" class="org.pac4j.oauth.client.FacebookClient">
<property name="key" value="${fb.key}" />
<property name="secret" value="${fb.secret}" />
</bean>
<bean id="twitterClient" class="org.pac4j.oauth.client.TwitterClient">
<property name="key" value="${tw.key}" />
<property name="secret" value="${tw.secret}" />
</bean>
<bean id="usernamePasswordAuthenticator" class="org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator" />
<bean id="formClient" class="org.pac4j.http.client.indirect.FormClient">
<property name="loginUrl" value="http://localhost:8080/loginForm.jsp" />
<property name="authenticator" ref="usernamePasswordAuthenticator" />
</bean>
<bean id="indirectBasicAuthClient" class="org.pac4j.http.client.indirect.IndirectBasicAuthClient">
<property name="authenticator" ref="usernamePasswordAuthenticator" />
</bean>
<bean id="casClient" class="org.pac4j.cas.client.CasClient">
<property name="casLoginUrl" value="https://casserverpac4j.herokuapp.com/login" />
</bean>
<bean id="parameterClient" class="org.pac4j.http.client.direct.ParameterClient">
<constructor-arg name="parameterName" value="token" />
<constructor-arg name="tokenAuthenticator">
<bean class="org.pac4j.jwt.credentials.authenticator.JwtAuthenticator">
<constructor-arg name="encryptionSecret" value="${encryptionSalt}" />
<constructor-arg name="signingSecret" value="${signingSecret}" />
</bean>
</constructor-arg>
<property name="supportGetRequest" value="true" />
<property name="supportPostRequest" value="false" />
</bean>
<bean id="directBasicAuthClient" class="org.pac4j.http.client.direct.DirectBasicAuthClient">
<constructor-arg name="usernamePasswordAuthenticator" ref="usernamePasswordAuthenticator" />
</bean>
<bean id="clients" class="org.pac4j.core.client.Clients">
<property name="callbackUrl" value="http://localhost:8080/callback" />
<property name="clients">
<list>
<ref bean="facebookClient" />
<ref bean="twitterClient" />
<ref bean="formClient" />
<ref bean="indirectBasicAuthClient" />
<ref bean="casClient" />
<ref bean="samlClient" />
<ref bean="parameterClient" />
<ref bean="directBasicAuthClient" />
</list>
</property>
</bean>
<bean id="config" class="org.pac4j.core.config.Config">
<property name="clients" ref="clients" />
<property name="authorizers">
<map>
<entry key="admin">
<bean class="org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer">
<constructor-arg name="roles" value="ROLE_ADMIN" />
</bean>
</entry>
<entry key="custom">
<bean class="org.pac4j.demo.spring.CustomAuthorizer" />
</entry>
</map>
</property>
<property name="matchers">
<map>
<entry key="excludedPath">
<bean class="org.pac4j.core.matching.ExcludedPathMatcher">
<constructor-arg name="excludePath" value="^/facebook/notprotected\.jsp$" />
</bean>
</entry>
</map>
</property>
</bean>http://localhost:8080/callback is the url of the callback endpoint, which is only necessary for indirect clients.
Notice that you can define:
-
a specific
SessionStoreusing thesetSessionStore(sessionStore)method (by default, it uses theJ2ESessionStorewhich relies on the J2E HTTP session) -
specific matchers via the
matchersmap.
You can protect (authentication + authorizations) the urls of your Spring Security application by using the SecurityFilter and declaring the filter in the appropriate security:http section. It has the following behaviour:
-
If the HTTP request matches the
matchersconfiguration (or nomatchersare 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
clientsparameter, a login is tried for the direct clients. -
Then, if the user has a profile, authorizations are checked according to the
authorizersconfiguration. 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
clientsconfiguration. 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
clientsis blank or not defined, the user must have been previously authenticated - if the
client_namerequest 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
authorizersis blank or not defined, no authorization is checked - the following authorizers are available by default (without defining them in the configuration):
isFullyAuthenticatedto check if the user is authenticated but not remembered,isRememberedfor a remembered user,isAnonymousto ensure the user is not authenticated,isAuthenticatedto ensure the user is authenticated (not necessary by default unless you use theAnonymousClient)hststo use theStrictTransportSecurityHeaderauthorizer,nosniffforXContentTypeOptionsHeader,noframeforXFrameOptionsHeader,xssprotectionforXSSProtectionHeader,nocacheforCacheControlHeaderorsecurityHeadersfor the five previous authorizerscsrfTokento use theCsrfTokenGeneratorAuthorizerwith theDefaultCsrfTokenGenerator(it generates a CSRF token and saves it as thepac4jCsrfTokenrequest attribute and in thepac4jCsrfTokencookie),csrfCheckto check that this previous token has been sent as thepac4jCsrfTokenheader or parameter in a POST request andcsrfto 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 (falseby default).
In the securityContext file:
<bean id="facebookSecurityFilter" class="org.pac4j.springframework.security.web.SecurityFilter">
<property name="config" ref="config" />
<property name="clients" value="FacebookClient" />
</bean>
<security:http create-session="always" pattern="/facebook/**" entry-point-ref="pac4jEntryPoint">
<security:custom-filter position="BASIC_AUTH_FILTER" ref="facebookSecurityFilter" />
</security:http>Notice that:
- you don't need to define any authentication manager for pac4j:
<security:authentication-manager />- as you need an entry point for each
security:httpsection, you should use thePac4jEntryPoint(which in fact, will never be called):
<bean id="pac4jEntryPoint" class="org.pac4j.springframework.security.web.Pac4jEntryPoint" />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: 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 (falseby default) -
renewSession(optional): it indicates whether the web session must be renewed after login, to avoid session hijacking (trueby default).
In the securityContext file:
<bean id="callbackFilter" class="org.pac4j.springframework.security.web.CallbackFilter">
<property name="config" ref="config" />
<property name="multiProfile" value="true" />
</bean>
<security:http create-session="always" pattern="/callback*" entry-point-ref="pac4jEntryPoint">
<security:csrf disabled="true"/>
<security:custom-filter position="BASIC_AUTH_FILTER" ref="callbackFilter" />
</security:http>Like for any Spring Security web application, you can get the authenticated user via the SecurityContextHolder.getContext().getAuthentication().
If the user is authenticated or remembered, the appropriate token will be stored in the context: Pac4jAuthenticationToken or Pac4jRememberMeAuthenticationToken.
As both implement the same interface: Pac4jAuthentication you should use it and get the main profile (getProfile method) or all profiles (getProfiles method) of the authenticated user:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth instanceof Pac4jAuthentication) {
Pac4jAuthentication token = (Pac4jAuthentication) auth;
CommonProfile profile = token.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;Like for any Spring Security webapp, use the default logout filter (in your Spring context XML file):
<security:logout logout-success-url="/" />The spring-security-pac4j library has strongly changed in version 2:
- the
ClientAuthenticationProviderhas been removed as the authentication happens in theSecurityFilter(for direct clients) or in theCallbackFilter(for indirect clients) - the
ClientAuthenticationEntryPointis replaced by thePac4jEntryPointwhich should never be called - the
ClientAuthenticationTokenis replaced by thePac4jAuthenticationTokenandPac4jRememberMeAuthenticationToken(depending on the use case) - the security is ensured by the
SecurityFilter(as usually in the pac4j world) - the
CallbackFilterfinishes the login process for indirect clients (as usually in the pac4j world).
The demo webapp: spring-security-pac4j-demo is available for tests and implements many authentication mechanisms: Facebook, Twitter, form, basic auth, CAS, SAML, OpenID Connect, JWT...
See the release notes. Learn more by browsing the spring-security-pac4j Javadoc and the pac4j Javadoc.
If you have any question, please use the following mailing lists:
The version 2.0.1-SNAPSHOT is under development.
Maven artifacts are built via Travis: 
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>