# 如何为新框架/工具实现 pac4j
pac4j 是一个简单而强大的安全引擎。它附带了要在任何框架/工具中实现的相应概念和组件。
# 1)依赖
添加 pac4j-core
依赖以从 pac4j
的核心 API 或 JEE环境中的 pac4j-javaee
(弃用)/pac4j-jakartaee
依赖中获益。
为了获得特定的支持,还可以添加其他依赖项:用于 OAuth 的 pac4j-oauth
、用于 CAS 的 pac4j-cas
、用于 SAML 的 pac4j-saml
……
# 2)配置
要定义安全配置,请通过 Clients
类收集所有认证机制 = 客户端(以共享相同的回调 url)。还要定义授权器以检查授权,并在 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());
2
3
4
5
6
7
8
9
你还可以添加匹配器来定义是否必须应用安全性。
# 3)“过滤器/控制器”
为了保护Java web应用程序,参考实现是创建一个过滤器和两个端点:
- 一个过滤器来保护 url
- 一个端点,用于接收回调:来自有状态认证进程的(间接客户端)
- 另一个端点用于执行注销。
在你的框架中,你需要创建:
1) 实现 WebContext (opens new window) 接口的特定 EnvSpecificWebContext
(JEE 环境除外),在 JEE 环境中,你已经可以使用现有的 JEEContext
。EnvSpecificWebContext
应将有关 web 会话管理的调用委托给 SessionStore
2) 实现 HttpActionAdapter (opens new window) 以在 web 上下文上执行操作的特定 EnvSpecificHttpActionAdapter
。
# A)保护 URL
保护 URL 的逻辑由 SecurityLogic
接口及其默认实现 DefaultSecurityLogic (opens new window) 定义。
在框架中,必须定义适当的“过滤器”、“拦截器”、“控制器”或用于拦截 HTTP 请求并委托给 SecurityLogic
类的任何机制。
示例:
- JEE
@Override
protected final void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore<JEEContext> bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter<Object, JEEContext> bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final SecurityLogic<Object, JEEContext> bestLogic = FindBest.securityLogic(securityLogic, config, DefaultSecurityLogic.INSTANCE);
final JEEContext context = new JEEContext(request, response, bestSessionStore);
bestLogic.perform(context, config, (ctx, 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, multiProfile);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# B)为间接客户端处理回调
处理回调的逻辑由 CallbackLogic
接口及其默认实现 DefaultCallbackLogic (opens new window) 定义。
在框架中,必须定义适当的“控制器”来响应 HTTP 请求,并将调用委托给 CallbackLogic
类。
示例:
- JEE
@Override
protected void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore<JEEContext> bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter<Object, JEEContext> bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final CallbackLogic<Object, JEEContext> bestLogic = FindBest.callbackLogic(callbackLogic, config, DefaultCallbackLogic.INSTANCE);
final JEEContext context = new JEEContext(request, response, bestSessionStore);
bestLogic.perform(context, config, bestAdapter, this.defaultUrl, this.saveInSession, this.multiProfile, this.renewSession, this.defaultClient);
}
2
3
4
5
6
7
8
9
10
11
12
13
- 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());
}
2
3
4
5
6
7
8
9
# C)注销
执行应用程序/认证提供者注销的逻辑由 LogoutLogic
接口及其默认实现 DefaultLogoutLogic (opens new window) 定义。
在你的框架中,必须定义适当的“控制器”来响应 HTTP 请求并将调用委托给 LogoutLogic
类。
示例:
- JEE
@Override
protected void internalFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
final Config config = getSharedConfig();
final SessionStore<JEEContext> bestSessionStore = FindBest.sessionStore(null, config, JEESessionStore.INSTANCE);
final HttpActionAdapter<Object, JEEContext> bestAdapter = FindBest.httpActionAdapter(null, config, JEEHttpActionAdapter.INSTANCE);
final LogoutLogic<Object, JEEContext> bestLogic = FindBest.logoutLogic(logoutLogic, config, DefaultLogoutLogic.INSTANCE);
final JEEContext context = new JEEContext(request, response, bestSessionStore);
bestLogic.perform(context, config, bestAdapter, this.defaultUrl, this.logoutUrlPattern, this.localLogout, this.destroySession, this.centralLogout);
}
2
3
4
5
6
7
8
9
10
11
12
13
- 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());
}
2
3
4
5
6
7
8
9