Fork me on GitHub

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. 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:

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:

    @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);
    }
    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:

    @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);
    }
    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:

    @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);
    }
    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());
    }