从Shiro-cas切换pac4j

1060

切换原因

shiro-1.3.x以后的版本中,shiro-cas包里面的所有类都被标识为deprecated,详细: https://github.com/apache/shiro/pull/33

个人认为不继续维护shiro-cas而切换到pac4j主要有以下几点原因:shiro-cas

  1. 非常的不灵活,比如在

CasFilter

onLoginSuccess(..)

  1. 登录成功事件,就只考虑到了通过cas跳转过来的情况,直接就执行了

issueSuccessRedirect(..)

  1. 跳转到保存的请求地址,并没有考虑到如果是ajax请求该如何处理。
  2. 认证协议太多,如果需要扩展一个微博登录(oauth2)那么还得加一个

shiro-oauth

  1. ?pac4j原生就支持很多的认证协议 OAuth (Facebook, Twitter, Google…) - SAML - CAS - OpenID Connect - HTTP - OpenID - Google App Engine LDAP - SQL - JWT - MongoDB - Stormpath - IP address

pac4j介绍

 pac4j定位是一个java安全引擎(Java security engine),所以不止包含支持各种认证协议进行登录,同时也支持 角色权限的管理、记住登录、CORS、CSRF、输出Http安全头 等功能,并且可以和很多著名的框架结合,比如官方列举的 J2E、Spring Web MVC (Spring Boot)、Spring Security (Spring Boot)、Shiro、Play 2.x、Vertx、Spark Java、Ratpack、Undertow、CAS server、Dropwizard、Knox、Jooby

由于我们的项目使用的Apache shiro,并且也经过了很长时间的开发和对shiro的扩展,所以如果没有特别大的需求,不会去替换掉shiro框架,所以采用了 buji-cas4j来提供shiro和pac4j的结合。pac4j大量用到了java8的特性编写,所以最低配置要求java8以及shiro 1.3.x+。

引入依赖

  1. 移除shiro-cas的依赖
  2. 加入

pac4j-cas

  1. 依赖(目前我们使用的 1.9.2),自身已依赖的

pac4j-core

  1. 无需再自己添加
  2. 加入

buji-pac4j

  1. 依赖(目前我们使用的 2.0.2)

去掉shiro-cas相关bean

各自对shiro-cas扩展程度不同,基本上去掉依赖以后所有ide报错的地方都需要改,这里就记录一下我们项目中修改的东西:

  1. 自定义的Realm不再继承自CasRealm,修改为

Pac4jRealm

  1. 去掉CasFilter过滤器,改用buji-pac4j提供的

CallbackFilter

  1. (下面会介绍如果配置这个bean)
  2. 将CasSubjectFactory修改为

Pac4jsubjectFactory

配置pac4j-cas

  1. 首先定义

CasConfiguration(loginUrl,prefixUrl)

  1. ,loginUrl为完整的cas登录地址,比如client项目的

https://passport.sqzryang.com/login?service=https://client.sqzryang.com

  1. ,prefixUrl则为cas路径前缀,根据cas的版本号拼接请求地址,用于验证sts是否正确并且返回登录成功后的信息。
  2. 定义

CasClient

  1. ,property

configuration(CasConfiguration)

  1. and

callbackUrl(String)

  1. ,在pac4j中,每一个client相当于一种认证协议,比如我们需要weibo登录则应该配置一个

WeiboClient

  1. ,具体回掉的时候应该采用哪个client进行验证授权则需要下面配置的

Clients

  1. 定义

Clients

  1. ,在这里面可以定义你所有的Client以及默认的client还有关于如何区分回掉的哪个client应该取某个参数的配置,具体详细看源码
  2. 定义

Config

  1. ,在config里面还有关于权限方面的配置以及session存储的一些配置,由于这部分我交给shiro去管理,所以只传入了

clients

  1. 即可
  2. 以上四个都是

pac4j

  1. 的配置,接下来配置一个由

buji-pac4j

  1. 提供用于和shiro结合的filter:

CallbackFilter

  1. ,直接传入

config

  1. 即可
  2. 定义好CallbackFilter以后,在

ShiroFilterFactoryBean

  1. 中注册好

filters

  1. ,并且配置好

filterChainDefinitions

具体各个bean的一些基本概念 Main concepts and components,一个稍微全面点的配置:

1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

<bean id="casClientConfiguration" class="org.pac4j.cas.config.CasConfiguration">


"loginUrl" value="${shiro.loginUrl}"/>


"prefixUrl" value="${shiro.casServerUrlPrefix}"/>


</bean>



<bean id="casClient" class="org.pac4j.cas.client.CasClient">


"configuration" ref="casClientConfiguration"/>


"callbackUrl" value="${shiro.casService}"/>


</bean>



<bean id="casClients" class=" org.pac4j.core.client.Clients">


"clients">


<util:list>


ref bean="casClient"/>


</util:list>


</property>


"defaultClient" ref="casClient"/>


</bean>



<bean id="casConfig" class="org.pac4j.core.config.Config">


"clients" ref="casClients"/>


</bean>



<bean name="casCallbackFilter" class="io.buji.pac4j.filter.CallbackFilter">


"config" ref="casConfig"/>


</bean>



<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"


ref="securityManager"


"/cas = cas\n/logout = logout"


"${shiro.loginUrl}"


"${shiro.successUrl}">


"filters">


<util:map>


<!-- ... -->


"cas" value-ref="casCallbackFilter"/> <!-- 注册filter -->


<!-- ... -->


</util:map>


</property>


</bean>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

其他修改地方

  • 登录后 Principal 为

Pac4jPrincipal

  • 对象,获取cas传递回来的username,通过:

String username = pac4jPrincipal.getProfile().getId();

  • 如果开启了缓存,应重写权限缓存以及认证缓存的key值,在

AuthorizingRealm

  • 中的

getAuthorizationCacheKey

  • 以及

getAuthenticationCacheKey

  • ,推荐使用username来作为缓存key

1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

@Override


protected Object getAuthorizationCacheKey(PrincipalCollection principals){


Pac4jPrincipal pac4jPrincipal = (Pac4jPrincipal) principals.getPrimaryPrincipal();


return


}



@Override


protected Object getAuthenticationCacheKey(AuthenticationToken token){


if (token instanceof


Pac4jToken pac4jToken = (Pac4jToken) token;


Object principal = pac4jToken.getPrincipal();


if (principal instanceof


@SuppressWarnings("unchecked") Optional<CasProfile> casProfileOptional = (Optional<CasProfile>) principal;


return


}


}


return super.getAuthenticationCacheKey(token);


}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 通过默认的

CallbackFilter

  • 登录成功以后,会直接

redirectToOriginallyRequestedUrl

  • ,但是在pac4j里面没有再去读取被shiro userfilter检测到未登录后存在session中的

SavedRequest

  • ,而是读取

org.pac4j.core.context.Pac4jConstants#REQUESTED_URL

  • ,因此重写UserFilter中的

saveRequest

  • 适配pac4j

1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

public class UserFilter extends org.apache.shiro.web.filter.authc.UserFilter{




@Override


protected void saveRequest(ServletRequest request){


// 还是先执行着shiro自己的方法


super.saveRequest(request);


Session session = SecurityUtils.getSubject().getSession();


session.setAttribute(Pac4jConstants.REQUESTED_URL, toHttp(request).getRequestURI());


}




}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

最后

以上配置以后基本就能跑起来了,但是还是有很多细节的地方需要去处理,后面会慢慢写出来,比如如何去配置jasig cas通过ajax来登录,以及相比Spring Securityshiro原生所缺乏的 csrf cors http头部安全

参考资料