The j2e-pac4j project is an easy and powerful security library for J2E web applications which supports authentication and authorization, but also logout and advanced features like session fixation and CSRF protection.
It's based on Java 8, servlet 3 and on the pac4j security engine v2.0. 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 -
The
LogoutFilterhandles the logout process.
==
Just follow these easy steps to secure your J2E web application:
You need to add a dependency on:
- the
j2e-pac4jlibrary (groupId: org.pac4j, version: 2.0.0-RC2) - the appropriate
pac4jsubmodules (groupId: org.pac4j, version: 2.0.0-RC2):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 built via a configuration factory (org.pac4j.core.config.ConfigFactory):
public class DemoConfigFactory implements ConfigFactory {
@Override
public Config build() {
final OidcConfiguration oidcConfiguration = new OidcConfiguration();
oidcConfiguration.setClientId("167480702619-8e1lo80dnu8bpk3k0lvvj27noin97vu9.apps.googleusercontent.com");
oidcConfiguration.setSecret("MhMme_Ik6IH2JMnAT6MFIfee");
oidcConfiguration.setUseNonce(true);
oidcConfiguration.addCustomParam("prompt", "consent");
final GoogleOidcClient oidcClient = new GoogleOidcClient(oidcConfiguration);
oidcClient.setAuthorizationGenerator((ctx, profile) -> { profile.addRole("ROLE_ADMIN"); return profile; });
final SAML2ClientConfiguration cfg = new SAML2ClientConfiguration("resource:samlKeystore.jks", "pac4j-demo-passwd", "pac4j-demo-passwd", "resource:testshib-providers.xml");
cfg.setMaximumAuthenticationLifetime(3600);
cfg.setServiceProviderEntityId("http://localhost:8080/callback?client_name=SAML2Client");
cfg.setServiceProviderMetadataPath(new File("sp-metadata.xml").getAbsolutePath());
final SAML2Client saml2Client = new SAML2Client(cfg);
final FacebookClient facebookClient = new FacebookClient("145278422258960", "be21409ba8f39b5dae2a7de525484da8");
final TwitterClient twitterClient = new TwitterClient("CoxUiYwQOSFDReZYdjigBA", "2kAzunH5Btc4gRSaMr7D7MkyoJ5u1VzbOOzE8rBofs");
final FormClient formClient = new FormClient("http://localhost:8080/loginForm.jsp", new SimpleTestUsernamePasswordAuthenticator());
final IndirectBasicAuthClient indirectBasicAuthClient = new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
final CasConfiguration configuration = new CasConfiguration("http://localhost:8888/cas/login");
final CasProxyReceptor casProxy = new CasProxyReceptor();
configuration.setProxyReceptor(casProxy);
final CasClient casClient = new CasClient(configuration);
final List<SignatureConfiguration> signatures = new ArrayList<>();
signatures.add(new SecretSignatureConfiguration(Constants.JWT_SALT));
ParameterClient parameterClient = new ParameterClient("token", new JwtAuthenticator(signatures));
parameterClient.setSupportGetRequest(true);
parameterClient.setSupportPostRequest(false);
final DirectBasicAuthClient directBasicAuthClient = new DirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
final Clients clients = new Clients("http://localhost:8080/callback", oidcClient, saml2Client, facebookClient,
twitterClient, formClient, indirectBasicAuthClient, casClient, parameterClient,
directBasicAuthClient, new AnonymousClient(), casProxy);
final Config config = new Config(clients);
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer<>("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
config.addAuthorizer("mustBeAnon", new IsAnonymousAuthorizer<>("/?mustBeAnon"));
config.addAuthorizer("mustBeAuth", new IsAuthenticatedAuthorizer<>("/?mustBeAuth"));
config.addMatcher("excludedPath", new PathMatcher().excludeRegex("^/facebook/notprotected\\.jsp$"));
return config;
}
}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 addMatcher(name, Matcher) method.
If your application is configured via dependency injection, no factory is required to build the configuration, you can directly inject the Config via the appropriate setter.
You can protect (authentication + authorizations) the urls of your J2E application by using the SecurityFilter and defining the appropriate mapping. 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:
-
configFactory: the factory to initialize the configuration. By default, the configuration is shared across filters so it can be specified only once, but each filter can defined its own configuration if necessary -
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 web.xml file:
<filter>
<filter-name>FacebookAdminFilter</filter-name>
<filter-class>org.pac4j.j2e.filter.SecurityFilter</filter-class>
<init-param>
<param-name>configFactory</param-name>
<param-value>org.pac4j.demo.j2e.DemoConfigFactory</param-value>
</init-param>
<init-param>
<param-name>clients</param-name>
<param-value>FacebookClient</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FacebookAdminFilter</filter-name>
<url-pattern>/facebook/*</url-pattern>
</filter-mapping>This filter can be defined via dependency injection as well. In that case, these parameters will be defined via setters.
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:
-
configFactory(optional): the factory to initialize the configuration. By default, the configuration is shared across filters so it can be specified only once, but each filter can defined its own configuration if necessary -
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 web.xml file:
<filter>
<filter-name>callbackFilter</filter-name>
<filter-class>org.pac4j.j2e.filter.CallbackFilter</filter-class>
<init-param>
<param-name>defaultUrl</param-name>
<param-value>/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>callbackFilter</filter-name>
<url-pattern>/callback</url-pattern>
</filter-mapping>Using dependency injection via Spring, you can define the callback filter as a DelegatingFilterProxy in the web.xml file:
<filter>
<filter-name>callbackFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>callbackFilter</filter-name>
<url-pattern>/callback</url-pattern>
</filter-mapping>and the specific bean in the application-context.xml file:
<bean id="callbackFilter" class="org.pac4j.j2e.filter.CallbackFilter">
<property name="defaultUrl" value="/" />
</bean>You can get the profile of the authenticated user using profileManager.get(true) (false not to use the session, but only the current HTTP request).
You can test if the user is authenticated using profileManager.isAuthenticated().
You can get all the profiles of the authenticated user (if ever multiple ones are kept) using profileManager.getAll(true).
Example:
WebContext context = new J2EContext(request, response);
ProfileManager manager = new ProfileManager(context);
Optional<CommonProfile> profile = manager.get(true);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;The LogoutFilter can handle:
- the local logout by removing the pac4j profiles from the session (it can be used for the front-channel logout from the identity provider in case of a central logout)
- the central logout by calling the identity provider logout endpoint.
It has the following behaviour:
-
If the
localLogoutproperty istrue, the pac4j profiles are removed from the web session (and the web session is destroyed if thedestroySessionproperty istrue) -
A post logout action is computed as the redirection to the
urlrequest parameter if it matches thelogoutUrlPatternor to thedefaultUrlif it is defined or as a blank page otherwise -
If the
centralLogoutproperty istrue, the user is redirected to the identity provider for a central logout and then optionally to the post logout redirection URL (if it's supported by the identity provider and if it's an absolute URL). If no central logout is defined, the post logout action is performed directly.
The following parameters are available:
-
defaultUrl(optional): the default logout url if nourlrequest parameter is provided or if theurldoes not match thelogoutUrlPattern(not defined by default) -
logoutUrlPattern(optional): the logout url pattern that theurlparameter must match (only relative urls are allowed by default) -
localLogout(optional): whether a local logout must be performed (trueby default) -
destroySession(optional): whether we must destroy the web session during the local logout (falseby default) -
centralLogout(optional): whether a central logout must be performed (falseby default).
In the web.xml file:
<filter>
<filter-name>logoutFilter</filter-name>
<filter-class>org.pac4j.j2e.filter.LogoutFilter</filter-class>
<init-param>
<param-name>defaultUrl</param-name>
<param-value>/urlAfterLogout</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>logoutFilter</filter-name>
<url-pattern>/logout</url-pattern>
</filter-mapping>The ApplicationLogoutFilter has been renamed as LogoutFilter and now handles both the application and identity provider logouts.
The RequiresAuthenticationFilter is now named SecurityFilter with the clients, authorizers and matchers parameters instead of the previous clientName, authorizerName and matcherName.
The ApplicationLogoutFilter behaviour has slightly changed: even without any url request parameter, the user will be redirected to the defaultUrl if it has been defined.
Authorizations are now handled by the library so the ClientFactory can now longer be used and is replaced by a ConfigFactory which builds a Config which gathers clients (for authentication) and authorizers (for authorizations).
The isAjax parameter is no longer available as AJAX requests are now automatically detected. The stateless parameter is no longer available as the stateless nature is held by the client itself.
The requireAnyRole and requieAllRoles parameters are no longer available and authorizers must be used instead (with the authorizerName parameter).
The application logout process can be managed with the ApplicationLogoutFilter.
The demo webapp: j2e-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 j2e-pac4j Javadoc and the pac4j Javadoc.
If you have any question, please use the following mailing lists:
The version 2.0.0-RC3-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>