您的位置:首页 > 编程语言 > Java开发

Shiro + CAS + (Spring) 集成

2017-06-21 00:00 381 查看
1 CAS 简介

CAS 全称为 Central Authentication Service(中央认证服务),它是耶鲁大学发起的一个开源项目,为 Web 应用系统提供一种可靠的单点登录方式,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目,它具有以下特点:

CAS 是一款开源的企业级单点登录解决方案,为应用系统提供中央认证服务。

CAS 服务器是独立部署的 Web 应用,可运行在主流的 Web 容器上,例如:Tomcat、Jetty 等。

CAS 客户端支持许多不同语言开发的应用系统,包括:Java、.Net、PHP、Apache、Perl、Ruby 等。

2 下载 CAS 服务器

可以从 CAS 官网:http://www.jasig.org/cas 下载,这可能需要翻墙,如果你不太愿意翻墙的话,可以访问这个地址:http://downloads.jasig.org/cas/,同样可以下载。

下载 CAS 服务器最新版:cas-server-3.5.2-release.zip

解压程序包

复制 cas-server-3.5.2/modules/cas-server-webapp-3.5.2.war 到 Tomcat 的 webapps 目录下,并重命名为 ROOT.war。

在启动 Tomcat 之前,还需要做两件事情:

创建 CAS 密钥库

使 Tomcat 支持 HTTPS

3 创建密钥库

使用 JDK 的 keytool 命令生成密钥库(keystore),其实就是一份 keystore 文件,keystore 必须通过密码才能访问。

keystore 里包含了多个密钥对(keypair),每个 keypair 都有一个别名(alias),alias 必须保证唯一性,而且都有一个密码,有此可知,keystore 与 keypair 都有自己的密码。

JDK 也有自己的 keystore,位于 %JAVA_HOME%\jre\lib\security\cacerts,其密码就是 changeit,当然也可以通过 keytool 命令来修改。

我们首先生成 keypair 及其存放 keypair 的 keystore,然后从 keystore 里导出证书,最后将证书导入 JDK 的 keystore 里,Tomcat 在运行时就会自动读取 JDK 的 keystore,以确保所部署的应用可以享受 HTTPS 协议(SSL 通道)带来的安全性。

3.1 生成 keypair

使用以下命令生成密钥对:

keytool -genkeypair -alias cas -keyalg RSA -storepass changeit

生成要注意,第一个姓和名需要和你的项目用到的域名相同 比如我的 server.cas,其他的项随便写

默认情况下,生成的 keystore 就是用户目录下的 .keystore 文件。对于 Win8 而言,默认的用户目录为 C:\Users\ 用户名。

以上命令执行完毕后,会在用户目录下生成一份 .keystore 文件(如果以前不存在该文件的话),其中包括一个 keypair。

注意:

为了简化操作,建议 keystore 与 keypair 的密码相同,且均为 changeit。

提示“您的名字与姓氏是什么?”,这里需要输入一个域名,例如:www.xxx.com,本例中输入 cas 需要在 hosts 文件中做如下映射:

127.0.0.1 cas

还有几条常用的 keytool 命令:

查看 keypair:

keytool -list -storepass changeit

删除 keypair:

keytool -delete -alias <别名> -storepass changeit

3.2 从 keystore 中导出证书

使用以下命令导出证书:

keytool -exportcert -alias cas -file cas.crt -storepass changeit

生成的证书文件为 cas.crt,位于运行该命令所在的当前目录下。

可双击该证书文件,将该证书安装到“受信任的根证书颁发机构”中,这样在浏览器中使用 HTTPS 协议访问时才不会出现一个红色的叉叉。

3.3 导入证书到 JVM 中

使用以下命令导入证书到 JVM 的 keystore 中:

keytool -importcert -alias cas -file cas.crt -keystore "%JAVA_HOME%\jre\lib\security\cacerts" -storepass changeit -noprompt

这里注意单独安装的JRE和JDK中的JRE

默认情况下,Tomcat 将读取 JVM 中的密钥库,而不是用户目录下的 .keystore 密钥库,当然也可以配置 Tomcat 使其读取指定的密钥库。

注意:

在执行以上命令前,必须需要保证 cacerts 文件对当前用户有写权限。

JDK 的 keystore 的密码必须为 changeit,这样 Tomcat 无需做任何配置就能访问。

4 使 Tomcat 支持 HTTPS

打开 Tomcat 的 conf/server.xml 文件。

修改以下配置:

...
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off" />
...


将以上配置中的 SSLEngine 改为 off,默认为 on。

...
<!--<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="18443" />-->
...
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
...
<!--<Connector port="8009" protocol="AJP/1.3" redirectPort="18443" />-->
...

禁用 HTTP 8080 端口,开启 HTTPS 8443 端口,禁用 AJP 8009 端口,以确保只能通过 HTTPS 协议访问 CAS 服务器。

若将 HTTPS 端口号配置为 443,则可以使用无端口方式发送 HTTPS 请求。

5 运行 CAS 服务器

启动部署了 CAS 服务器的 Tomcat,并使用以下地址访问 CAS 登录页面:
https://cas:8443/login
一定要使用域名 cas 来访问,实际上 cas 指向了 127.0.0.1(需要在本机的 hosts 中进行配置)。

CAS 默认采用了最简单的认证,即用户名与密码必须有,但两者必须相同,也可对 CAS 做个性化认证配置。

可以使用如下 URL 注销 CAS:

https://cas:8443/logout



6 经验小结

这里只是个人总结,我也是初学者,我在学习的过程中这些内容网上查找资料没有合适的说明,导致走了很多弯路,希望我的情况记录可以帮助和我有相同问题的朋友。

下面贴出我的spring-shiro -config.xml

tip: 以下是我学习遇到的问题,希望自己记住同时给大家参考

① 请注意我在下边XML配置中标红的地方(算了,挪到os不能标红了,对应下面的代码块看吧),

<!-- 验证 tikict向 cas服务器发送验证请求用的,cas服务端前缀 -->
<property name="casServerUrlPrefix" value="https://server.cas:8443/cas/"/>

<!-- 应用服务地址, cas tikect这个要和回调地址一样,shiro会验证两个地址是否相同 -->

<property name="casService" value="https://cli1.cas:8443/cli1/oauth"/>

<!-- cas server登陆请求地址后面一定要跟回调地址,要不然是回不来的 -->

<property name="loginUrl"

value="https://server.cas:8443/cas/login?https://cli1.cas:8443/cli1/oauth"/>

这三个请求地址不要弄混,本人在做集成配置的时候,这三个配置把我折磨的不行,可能别人的文章中跟我的有出入,也许是使用环境问题,我这里使用的TOMCAT,应用程序客户端和cas服务端一起发布的,所以一定要有 /cas/这样的项目路径,有的文章中说用 ip+port,这个说法是在你项目根路径就是 “/”的情况下试用,实际上是不适用所有情况的

② SSL证书问题,我是不太了解SSL的,但是本文上边的SSL生成没有问题,只需要CAS服务端生成一个加入JRE就行了,注意是JRE,如果你同时安装了JDK和JRE要分清楚,只安装JDK好说,只有一个JRE。

③ 就是这个登陆流程的问题,稍微说一下(本项目的情况下 Spring、Shiro、CAS):

客户端请求应用,shiro过滤器请求,如果需要验证,会根据设置的loginUrl

请求CAS服务器,用户在CAS服务器进行身份验证,不成功没什么好说的,如果成功,CAS服务器会将请求重定向到应用服务器(这个重定向地址,是你设置的loginUrl的service参数,这个参数中你应该加上shrio的过滤器key,这个下边的配置中我标出来了),带上一个ticket,应用服务器接到这个重定向请求后,由于含有过滤器 key ,所以会被 CasFilter过滤,CasRealm会对这个ticket进行验证,根据 casServerUrlPrefix 和 ticket 以及回调地址 casService 我写到这突然觉得这个是在这里用的,只是猜测没有验证,但拼接请求后面肯定有回调函数,这个我看过有保证,然后CAS服务器会对ticket进行验证,发回结果。

下面的图还是很清晰的



<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd"> 
<!-- 缓存管理器 使用 Ehcache实现 -->

<bean id="cacheManager" class="com.shige.mall.shiro.cacahe.ShiroMemcachedManager"/>

<!-- 凭证匹配器 -->

<bean id="credentialsMatcher" class="com.shige.mall.shiro.credentials.RetryLimitHashedCredentialsMatcher">

<constructor-arg ref="cacheManager"/>

<property name="hashAlgorithmName" value="md5"/>

<property name="hashIterations" value="1"/>

<property name="storedCredentialsHexEncoded" value="true"/>

</bean>

<!-- session Id Generator -->

<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

<!-- session Id Cookie模板 -->

<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">

<constructor-arg value="jsid"/>

<property name="httpOnly" value="true"/>

<property name="maxAge" value="1800000"/>

</bean>

<!-- remember me start-->

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">

<constructor-arg value="rememberMe"/>

<property name="httpOnly" value="true"/>

<property name="maxAge" value="892000"/>

</bean>

<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">

<!-- 看了源码这个长度需要 16 ,22,32字节,但是后边还有一个判断,我做的时候只有16长度的 OK,坑死我,这个仅供参考 -->

<property name="cipherKey"

value="#{T(org.apache.shiro.codec.Base64).decode('f22*^*)(*BN)(I*oN3*d)M(*8u*43*4s6*7df')}"/>

<property name="cookie" ref="rememberMeCookie"/>

</bean>

<!-- remember me end-->

<!-- 会话 DAO -->

<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">

<property name="sessionIdGenerator" ref="sessionIdGenerator"/>

<!-- 根据源码来看,这里不注入的话会构造匿名实现一个 manager 创建的 cache是 ConcurrentHashMap -->

<property name="cacheManager" ref="cacheManager"/>

</bean>

<!-- 会话验证调度器 -->

<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">

<property name="sessionValidationInterval" value="1800000"/>

<property name="sessionManager" ref="sessionManager"/>

</bean>

<!-- session listener -->

<bean id="sessionOnlinNumListener" class="com.shige.mall.shiro.listener.OnlineNumberListener"/>

<!-- 会话管理器 -->

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">

<property name="globalSessionTimeout" value="1800000"/>

<property name="deleteInvalidSessions" value="true"/>

<!-- 这块换成 memcached了 应当弄一弄 -->

<property name="sessionValidationSchedulerEnabled" value="true"/>

<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>

<property name="sessionDAO" ref="sessionDAO"/>

<property name="sessionIdCookieEnabled" value="true"/>

<property name="sessionIdCookie" ref="sessionIdCookie"/>

<property name="sessionListeners" ref="sessionOnlinNumListener"/>

</bean>

<!-- Realm实现 -->

<bean id="casRealm" class="org.apache.shiro.cas.CasRealm">

<property name="cachingEnabled" value="true"/>

<property name="authenticationCachingEnabled" value="true"/>

<property name="authenticationCacheName" value="authenticationCache"/>

<property name="authorizationCachingEnabled" value="true"/>

<property name="authorizationCacheName" value="authorizationCache"/>

<property name="defaultRoles" value="admin,user"/>

<property name="defaultPermissions" value="user:create,user:update"/>

<property name="roleAttributeNames" value="roles"/>

<property name="permissionAttributeNames" value="permissions"/>

<!-- 验证 tikict向 cas服务器发送验证请求用的,cas服务端前缀 -->

<property name="casServerUrlPrefix" value="https://server.cas:8443/cas/"/>

<!-- 应用服务地址, cas tikect这个要和回调地址一样,shiro会验证两个地址是否相同 -->

<property name="casService" value="https://cli1.cas:8443/cli1/oauth"/>

</bean>

<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"/>

<!-- 验证服务器端返回的 CAS Service Ticket -->

<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">

<property name="failureUrl" value="/casFailure.jsp"/>

</bean>

<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">

<property name="redirectUrl" value="index.jsp" />

</bean>

<!-- 基于 Form表单的身份验证过滤器 -->

<!--<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">

<property name="loginUrl" valulogins.jsp.jsp"/>

</bean>-->

<!-- Shiro的 Web过滤器 shiro实现的 filter多条件都是 &&关系,如果想要 || 可自定义 -->

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

<property name="securityManager" ref="securityManager"/>

<!-- cas server登陆请求地址后面一定要跟回调地址,要不然是回不来的 -->

<property name="loginUrl" value="https://server.cas:8443/cas/login?https://cli1.cas:8443/cli1/oauth"/>

<property name="successUrl" value="/index.jsp"/>

<property name="unauthorizedUrl" value="/unauthorized.jsp"/>

<property name="filters">

<map>

<entry key="cas" value-ref="casFilter"/> 过滤器对应的KEY

</map>

</property>

<property name="filterChainDefinitions">

<value>

<!-- user -->

<!--/user/passwordChange = anon

/user/newUserInfo = cas

/user/loginInfo = user

<!– goods –>

/goods/** = anon

/management/goods/** = roles[merchant]

<!– cart –>

/cart/** = cas

<!– order –>

/order/** = cas

<!– address –>

/ReceiverAddress/** = cas

<!– category –>

/management/category/** = roles[merchant]

<!– order –>

/order/** = cas

/Management/Order = roles[merchant]-->

/casFailure.jsp=anon

/oauth*=cas

/**=user

</value>

</property>

</bean>

<!-- 安全管理器的超级父接口是 SessionManager -->

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

<property name="realm" ref="casRealm"/>

<property name="sessionManager" ref="sessionManager"/>

<property name="cacheManager" ref="cacheManager"/>

<property name="rememberMeManager" ref="rememberMeManager"/>

<property name="subjectFactory" ref="casSubjectFactory"/>

</bean>

<!-- Shiro生命周期处理器 -->

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!-- 相当于调用 SecurityUtils.setSecurityManager(securityManager) -->

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">

<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>

<property name="arguments" ref="securityManager"/>

</bean>

<!-- shiro注解 -->

<bean class=" org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

<property name="securityManager" ref="securityManager"/>

</bean>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

depends-on="lifecycleBeanPostProcessor">

<property name="proxyTargetClass" value="true"/>

</bean>

</beans>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  CAS Shiro shiro+cas