The spark-pac4j
project is an easy and powerful security library for Sparkjava applications which supports authentication and authorization, but also application logout and advanced features like CSRF protection. It's available under the Apache 2 license and based on the pac4j security engine.
It supports most authentication mechanisms, called clients:
- indirect / stateful clients are for UI when the user authenticates once at an external provider (like Facebook, a CAS server...) or via a local form (or basic auth popup)
- direct / stateless clients are for web services when credentials (like basic auth, tokens...) are passed for each HTTP request.
See the authentication flows.
The authentication mechanism you want | The pac4j-* submodule(s) you must use |
---|---|
OAuth (1.0 & 2.0): Facebook, Twitter, Google, Yahoo, LinkedIn, Github... | pac4j-oauth |
CAS (1.0, 2.0, 3.0, SAML, logout, proxy) | pac4j-cas |
SAML (2.0) | pac4j-saml |
OpenID Connect (1.0) | pac4j-oidc |
HTTP (form, basic auth, IP, header, cookie, GET/POST parameter) + JWT or LDAP or Relational DB or MongoDB or Stormpath or CAS REST API |
pac4j-http + pac4j-jwt or pac4j-ldap or pac4j-sql or pac4j-mongo or pac4j-stormpath or pac4j-cas |
Google App Engine UserService | pac4j-gae |
OpenID | pac4j-openid |
It also supports many authorization checks, called authorizers available in the pac4j-core
(and pac4j-http
) submodules: role / permission checks, IP check, profile type verification, HTTP method verification... as well as regular security protections for CSRF, XSS, cache control, Xframe...
First, you need to add a dependency on this library as well as on the appropriate pac4j
submodules. Then, you must define the clients for authentication and the authorizers to check authorizations.
Define the CallbackRoute
to finish authentication processes if you use indirect clients (like Facebook).
Use the RequiresAuthenticationFilter
to secure the urls of your web application (using the clientName
parameter for authentication and the authorizerName
parameter for authorizations).
Just follow these easy steps:
You need to add a dependency on the spark-pac4j
library (groupId: org.pac4j, version: 1.1.0) as well as on the appropriate pac4j
submodules (groupId: org.pac4j, version: 1.8.3): the pac4j-oauth
dependency for OAuth support, the pac4j-cas
dependency for CAS support, the pac4j-ldap
module for LDAP authentication, ...
All released artifacts are available in the Maven central repository.
Each authentication mechanism (Facebook, Twitter, a CAS server...) is defined by a client (implementing the org.pac4j.core.client.Client
interface). All clients must be gathered in a org.pac4j.core.client.Clients
class.
All the Clients
and the authorizers must be gathered in a Config
object (which can be itself build in a org.pac4j.core.config.ConfigFactory
).
For example:
final OidcClient oidcClient = new OidcClient();
oidcClient.setClientID("id");
oidcClient.setSecret("secret");
oidcClient.setDiscoveryURI("https://accounts.google.com/.well-known/openid-configuration");
oidcClient.setUseNonce(true);
oidcClient.addCustomParam("prompt", "consent");
final SAML2ClientConfiguration cfg = new SAML2ClientConfiguration("resource:samlKeystore.jks", "pac4j-demo-passwd", "pac4j-demo-passwd", "resource:metadata-okta.xml");
cfg.setMaximumAuthenticationLifetime(3600);
cfg.setServiceProviderEntityId("http://localhost:8080/callback?client_name=SAML2Client");
cfg.setServiceProviderMetadataPath("sp-metadata.xml");
final SAML2Client saml2Client = new SAML2Client(cfg);
final FacebookClient facebookClient = new FacebookClient("fbId", "fbSecret");
final TwitterClient twitterClient = new TwitterClient("twId", "twSecret");
final FormClient formClient = new FormClient("http://localhost:8080/theForm.jsp", new SimpleTestUsernamePasswordAuthenticator());
final IndirectBasicAuthClient basicAuthClient = new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator());
final CasClient casClient = new CasClient("http://mycasserver/login");
final ParameterClient parameterClient = new ParameterClient("token", new JwtAuthenticator("salt"));
final Clients clients = new Clients("http://localhost:8080/callback", oidcClient, saml2Client, facebookClient, twitterClient, formClient, basicAuthClient, casClient, parameterClient);
final Config config = new Config(clients);
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
config.setHttpActionAdapter(new DemoHttpActionAdapter(templateEngine));
"http://localhost:8080/callback" is the url of the callback endpoint (see below). It may not be defined for REST support / direct clients only.
Notice the config.setHttpActionAdapter
call to define the way to handle specific HTTP actions (like redirections, forbidden / unauthorized pages). The only available implementation is currently the DefaultHttpActionAdapter
, but you can subclass it to define your own HTTP 401 / 403 error pages for example.
You can also use a specific SessionStore
by defining it via the Config.setSessionStore(sessionStore)
method.
Indirect clients rely on external identity providers (like Facebook) and thus require to define a callback endpoint in the application where the user will be redirected after login at the identity provider. For REST support / direct clients only, this callback endpoint is not necessary.
final Route callback = new CallbackRoute(config);
get("/callback", callback);
post("/callback", callback);
The defaultUrl
parameter of this route defines where the user will be redirected after login if no url was originally requested (/ by default).
You can protect an url and require the user to be authenticated by a client (and optionally have the appropriate authorizations) by using the RequiresAuthenticationFilter
:
before("/facebook", new RequiresAuthenticationFilter(config, "FacebookClient", "admin"));
Several constructors are available:
RequiresAuthenticationFilter(final Config config, final String clientName)
RequiresAuthenticationFilter(final Config config, final String clientName, final String authorizerName)
RequiresAuthenticationFilter(final Config config, final String clientName, final String authorizerName, final String matcherName)
with the following parameters:
clientName
(optional): the list of client names (separated by commas) used for authentication. If the user is not authenticated, direct clients are tried successively then if the user is still not authenticated and if the first client is an indirect one, this client is used to start the authentication. Otherwise, a 401 HTTP error is returned. If the client_name request parameter is provided, only the matching client is selectedconfigFactory
: the factory to initialize the configuration: clients and authorizers (only one filter needs to define it as the configuration is shared)authorizerName
(optional): the list of authorizer names (separated by commas) used to check authorizations. If the user is not authorized, a 403 HTTP error is returned. By default (if blank), the user only requires to be authenticated to access the resource. The following authorizers are available by default: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 adds it to the request and save it 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.
matcherName
(optional): the list of matcher names (separated by commas) that the request must satisfy to apply authentication / authorization. By default, all requests are checked
You can test if the user is authenticated using the ProfileManager.isAuthenticated()
method or get the user profile using the ProfileManager.get(true)
method (false
not to use the session, but only the current HTTP request):
final SparkWebContext context = new SparkWebContext(request, response);
final ProfileManager manager = new ProfileManager(context);
final UserProfile profile = manager.get(true);
The retrieved profile is at least a CommonProfile
, from which you can retrieve the most common properties 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;
You can log out the current authenticated user using the ApplicationLogoutFilter
:
get("/logout", new ApplicationLogoutRoute(config));
To perfom the logout, you must call the /logout url. A blank page is displayed by default unless an url request parameter is provided. In that case, the user will be redirected to this specified url (if it matches the logout url pattern defined) or to the default logout url otherwise.
The following parameters can be defined:
defaultUrl
(optional): the default logout url if the provided url parameter does not match thelogoutUrlPattern
(by default: /)logoutUrlPattern
(optional): the logout url pattern that the logout url must match (it's a security check, only relative urls are allowed by default).
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 application logout process can be managed with the ApplicationLogoutFilter
.
The demo webapp: spark-pac4j-demo is available for tests and implement many authentication mechanisms: Facebook, Twitter, form, basic auth, CAS, SAML, OpenID Connect, JWT...
See the release notes. Learn more by browsing the spark-pac4j Javadoc and the pac4j Javadoc.
If you have any question, please use the following mailing lists:
The version 1.1.1-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>