15 四月 2018

什么是 pac4j

pac4j 是一个 java 的安全引擎.

在一套标准的 interface 下,

  1. 提供了很多种认证机制: form 表单登录, JWS, cas, OAuth 等等.

  2. 提供了很多收授权和权限检查机制: role/permissions, CORS, CSRF, HTTP Security headers

  3. 提供了跟很多框架的集成方式: springboot, play, shiro, spring security

这些认证方式都是可插拔的, 你可以同时使用其中的一种或几种.

比如, 最常见的情况, 就是我们用 spring boot + cas 单点登录 + role/permissions

重要组件和概念

client

引入不同的认证包会包含不同的 Client 类, 每一种 Client 类就代表一种登录和认证方式. 比如:

使用 pac4j-cas, 里面就是 CasClient

authenticator

Authenticator 是 client 的一个组件, 用来 validate credentials, 并且用来创建 user profile.

authorizer

是用来检测 user profiles 是否能够通过授权. pac4j 内置了很多 authorizer:

StrictTransportSecurityHeader
XContentTypeOptionsHeader
XFrameOptionsHeader
XSSProtectionHeader
CacheControlHeader
CsrfAuthorizer
CsrfTokenGeneratorAuthorizer
CorsAuthorizer
IsAnonymousAuthorizer
IsAuthenticatedAuthorizer
IsFullyAuthenticatedAuthorizer
IsRememberedAuthorizer

我们也可以自己实现, 添加到 config 中的 authorizerMap

matcher

matcher 通常用来判断一个请求, 是否要经过登录或认证.

开发者可以在 config 中定义很多 matcher, 并定义每个请求路径要经过哪些 matcher.

如果一个请求经过所有 matcher 判断结果都为 true, 那么才能进行认证检测

如果为 false, 则直接授权访问

config

config 配置了要用到的 clients, authorizers, matchers

user profile

代表一个认证后的用户, 包括 id, 属性, 角色, 权限等.

web context

是对一个请求的抽象, 包含了 request, response, session, 还有 pac4j 提供的其他的来辅助描述这次请求的数据.

security filter

security filter 或者其他拦截 http 请求的机制. 根据 client 的不同来进行认证检查, 授权检查, 从而保护一个 url 地址.

如果是没有认证的用户, 则尝试登录操作

callback controller

对与 "间接 client" indirect client, 比如需要在一个特殊页面进行登录, 而不是每个请求直接携带认证信息的 client.

在完成登录操作后需要调用 callback controller 最终完成登录操作.

logout controller

执行登出操作

流程

通常是一个请求过来, 访问一个 url A, 如果 A 需要被保护, 那么开发人员会设置需要先经过 "security filter".

如果 "security filter" 能从 "web context" 里获取到 "user profile", 且 "user profile" 是登录状态, 那么通过认证, 允许访问.

如果没有 "user profile", 那么从 config 中获取改应用所支持的登录方式, 即配置的各种 clients.

先用 direct clients 来判断能否登录, 如果此时 http context 里包含了这类 client 所需要的登录信息, 比如 token, 则能够被授权成功访问.

如果不行, 进行 indirect client 的登录方式, 跳转的特定的登录页.

在特定的登录页登录成功后, 回跳转回 "callback controller" 进行最后的登录操作, 即获取 "user profile" 并保存.

当退出是, 访问 "logout controller", 执行退出.

例子

spring mvc 使用 pac4j-cas 进行登录

TODO

spring mvc 使用 pac4j-cas 和 buji-pac4j(pac4j for shiro) 进行登录

ShiroCasProperties

cas.shiro.login-url: http://passport.xxx.com:8088/cas/login
cas.shiro.callback-url: /callback
cas:
  shiro:
    filter-chain-definition-map:
      /page1: securityFilter
      /callback: callbackFilter
      /logout: logout

config 配置:

@Bean
public Config config() {
    final CasConfiguration configuration = new CasConfiguration(shiroCasProperties.getLoginUrl());
    final CasClient casClient = new CasClient(configuration);
    casClient.setUrlResolver(new RelativeUrlResolver());
    final Clients clients = new Clients(shiroCasProperties.getCallbackUrl(), casClient);
    final Config config = new Config(clients);
    config.setSessionStore(new J2ESessionStore());
    return config;
}

shiroFilter 配置:

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {

    final Config config = config();


    ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(new Pac4jRealm());
    shiroFilter.setSecurityManager(securityManager);
    shiroFilter.setFilterChainDefinitionMap(shiroCasProperties.getFilterChainDefinitionMap());
    shiroFilter.setFilters(new LinkedHashMap<String, Filter>() {{
        CallbackFilter callbackFilter = new CallbackFilter();
        callbackFilter.setConfig(config);
        put("callbackFilter", callbackFilter);
        SecurityFilter securityFilter = new SecurityFilter();
        securityFilter.setConfig(config);
        securityFilter.setClients("CasClient");
        put("securityFilter", securityFilter);
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setConfig(config);
        logoutFilter.setCentralLogout(true);
        put("logout", logoutFilter);
    }});
    return shiroFilter;
}

完整源码见: TODO