How to implement pac4j for a new framework/tool:
pac4j is an easy and powerful security engine. It comes with the appropriate concepts and components to be implemented in any framework/tools.
1) Dependency
Add the pac4j-core
dependency to benefit from the core API of pac4j
or the pac4j-javaee
/ pac4j-jakartaee
dependency in a JEE environment.
Other dependencies will be optionally added for specific support: pac4j-oauth
for OAuth, pac4j-cas
for CAS, pac4j-saml
for SAML…
2) Configuration
To define your security configuration, gather all your authentication mechanisms = clients via the Clients
class (to share the same callback url).
Also define your authorizers to check authorizations and aggregate both (clients and authorizers) on the Config
:
FacebookClient facebookClient = new FacebookClient(FB_KEY, FB_SECRET);
TwitterClient twitterClient = new TwitterClient(TW_KEY, TW_SECRET);
FormClient formClient = new FormClient("http://localhost:8080/theForm.jsp", new SimpleTestUsernamePasswordAuthenticator(), new UsernameProfileCreator());
CasClient casClient = new CasClient();
casClient.setCasLoginUrl("http://mycasserver/login");
Clients clients = new Clients("http://localhost:8080/callback", facebookClient, twitterClient, formClient, casClient);
Config config = new Config(clients);
config.addAuthorizer("admin", new RequireAnyRoleAuthorizer("ROLE_ADMIN"));
config.addAuthorizer("custom", new CustomAuthorizer());
You can also add matchers to define whether the security must apply or not.
3) “Filters/controllers”
To secure your Java web application, the reference implementation is to create one filter and two endpoints:
- one filter to protect urls
- one endpoint to receive callbacks for stateful authentication processes (indirect clients)
- another endpoint to perform logout.
In your framework, you will need to create:
1) a specific EnvSpecificWebContext
implementing the WebContext
interface except for JEE environment where you can already use the existing JEEContext
.
Your EnvSpecificWebContext
should delegate to a SessionStore
the calls regarding the web session management
2) a specific EnvSpecificHttpActionAdapter
implementing the HttpActionAdapter
to perform actions on the web context.
A) Secure an URL
The logic to secure an URL is defined by the SecurityLogic
interface and its default implementation: DefaultSecurityLogic
.
In your framework, you must define the appropriate “filter”, “interceptor”, “controller” or whatever the mechanism used to intercept the HTTP request and delegate to the SecurityLogic
class.
Examples:
- In JEE:
@Override
protected final void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final SecurityLogic bestLogic = FindBest.securityLogic(securityLogic, config, DefaultSecurityLogic.INSTANCE);
final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response);
bestLogic.perform(context, bestSessionStore, config, (ctx, session, profiles, parameters) -> {
// if no profiles are loaded, pac4j is not concerned with this request
filterChain.doFilter(profiles.isEmpty() ? request : new Pac4JHttpServletRequestWrapper(request, profiles), response);
return null;
}, bestAdapter, clients, authorizers, matchers);
}
- In Play:
protected CompletionStage<Result> internalCall(final Http.Request req, final PlayWebContext webContext, final String clients, final String authorizers, final String matchers, final boolean multiProfile) throws Throwable {
final HttpActionAdapter<Result, PlayWebContext> bestAdapter = FindBest.httpActionAdapter(null, config, PlayHttpActionAdapter.INSTANCE);
final SecurityLogic<CompletionStage<Result>, PlayWebContext> bestLogic = FindBest.securityLogic(securityLogic, config, DefaultSecurityLogic.INSTANCE);
final HttpActionAdapter<CompletionStage<Result>, PlayWebContext> actionAdapterWrapper = (action, webCtx) -> CompletableFuture.completedFuture(bestAdapter.adapt(action, webCtx));
return bestLogic.perform(webContext, config, (webCtx, profiles, parameters) -> {
// when called from Scala
if (delegate == null) {
return CompletableFuture.completedFuture(null);
} else {
return delegate.call(webCtx.supplementRequest(req));
}
}, actionAdapterWrapper, clients, authorizers, matchers, multiProfile);
}
B) Handle callback for indirect client
The logic to handle callbacks is defined by the CallbackLogic
interface and its default implementation: DefaultCallbackLogic
.
In your framework, you must define the appropriate “controller” to reply to an HTTP request and delegate the call to the CallbackLogic
class.
Examples:
- In JEE:
@Override
protected void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final CallbackLogic bestLogic = FindBest.callbackLogic(callbackLogic, config, DefaultCallbackLogic.INSTANCE);
final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response);
bestLogic.perform(context, bestSessionStore, config, bestAdapter, this.defaultUrl, this.renewSession, this.defaultClient);
}
- In Play:
public CompletionStage<Result> callback(final Http.Request request) {
final HttpActionAdapter<Result, PlayWebContext> bestAdapter = FindBest.httpActionAdapter(null, config, PlayHttpActionAdapter.INSTANCE);
final CallbackLogic<Result, PlayWebContext> bestLogic = FindBest.callbackLogic(callbackLogic, config, DefaultCallbackLogic.INSTANCE);
final PlayWebContext playWebContext = new PlayWebContext(request, playSessionStore);
return CompletableFuture.supplyAsync(() -> bestLogic.perform(playWebContext, config, bestAdapter,
this.defaultUrl, this.saveInSession, this.multiProfile, this.renewSession, this.defaultClient), ec.current());
}
C) Logout
The logic to perform the application/identity provider logout is defined by the LogoutLogic
interface and its default implementation: DefaultLogoutLogic
.
In your framework, you must define the appropriate “controller” to reply to an HTTP request and delegate the call to the LogoutLogic
class.
Examples:
- In JEE:
@Override
protected void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final LogoutLogic bestLogic = FindBest.logoutLogic(logoutLogic, config, DefaultLogoutLogic.INSTANCE);
final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response);
bestLogic.perform(context, bestSessionStore, config, bestAdapter, this.defaultUrl, this.logoutUrlPattern, this.localLogout, this.destroySession, this.centralLogout);
}
- In Play:
public CompletionStage<Result> logout(final Http.Request request) {
final HttpActionAdapter<Result, PlayWebContext> bestAdapter = FindBest.httpActionAdapter(null, config, PlayHttpActionAdapter.INSTANCE);
final LogoutLogic<Result, PlayWebContext> bestLogic = FindBest.logoutLogic(logoutLogic, config, DefaultLogoutLogic.INSTANCE);
final PlayWebContext playWebContext = new PlayWebContext(request, playSessionStore);
return CompletableFuture.supplyAsync(() -> bestLogic.perform(playWebContext, config, bestAdapter, this.defaultUrl,
this.logoutUrlPattern, this.localLogout, this.destroySession, this.centralLogout), ec.current());
}