單點登入
Shiro 1.2 開始提供了 Jasig CAS 單點登入的支援,單點登入主要用于多系統內建,即在多個系統中,使用者隻需要到一個中央伺服器登入一次即可通路這些系統中的任何一個,無須多次登入。此處我們使用 Jasig CAS v4.0.0-RC3 版本:https://github.com/Jasig/cas/tree/v4.0.0-RC3
Jasig CAS 單點登入系統分為伺服器端和用戶端,伺服器端提供單點登入,多個用戶端(子系統)将跳轉到該伺服器進行登入驗證,大體流程如下:
- 通路用戶端需要登入的頁面
,此時會跳到單點登入伺服器http://localhost:9080/client/
;https://localhost:8443/server/login?service=https://localhost:9443/client/cas
- 如果此時單點登入伺服器也沒有登入的話,會顯示登入表單頁面,輸入使用者名 / 密碼進行登入;
- 登入成功後伺服器端會回調用戶端傳入的位址:
,且帶着一個 ticket;https://localhost:9443/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org
- 用戶端會把 ticket 送出給伺服器來驗證 ticket 是否有效;如果有效伺服器端将傳回使用者身份;
- 用戶端可以再根據這個使用者身份擷取如目前系統使用者 / 角色 / 權限資訊。
本章使用了和《第十四章 SSL》一樣的數字證書。
伺服器端
我們使用了 Jasig CAS 伺服器 v4.0.0-RC3 版本,可以到其官方的 github 下載下傳:
https://github.com/Jasig/cas/tree/v4.0.0-RC3
下載下傳,然後将其 cas-server-webapp 子產品封裝到 shiro-example-chapter15-server 子產品中,具體請參考源碼。
1、數字證書使用和《第十四章 SSL》一樣的數字證書,即将 localhost.keystore 拷貝到 shiro-example-chapter15-server 子產品根目錄下;
2、在 pom.xml 中添加 Jetty Maven 插件,并添加 SSL 支援:
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>8.1.8.v20121106</version>
<configuration>
<webAppConfig>
<contextPath>/${project.build.finalName}</contextPath>
</webAppConfig>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>8080</port>
</connector>
<connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
<port>8443</port>
<keystore>${project.basedir}/localhost.keystore</keystore>
<password>123456</password>
<keyPassword>123456</keyPassword>
</connector>
</connectors>
</configuration>
</plugin>
3、修改
src/main/webapp/WEB-INF/deployerConfigContext.xml
,找到 primaryAuthenticationHandler,然後添加一個賬戶:
<entry key="zhang" value="123"/>
其也支援如 JDBC 查詢,可以自己定制;具體請參考文檔。
4、mvn jetty:run 啟動伺服器測試即可:
通路
https://localhost:8443/chapter15-server/login
将彈出如下登入頁面:
輸入使用者名 / 密碼,如 zhang/123,将顯示登入成功頁面:
到此伺服器端的簡單配置就完成了。
用戶端
1、首先使用 localhost.keystore 導出數字證書(公鑰)到
D:localhost.cer
keytool -export -alias localhost -file D:localhost.cer -keystore D:localhost.keystore
2、因為 CAS client 需要使用該證書進行驗證,需要将證書導入到 JDK 中:
cd D:jdk1.7.0_21jrelibsecurity
keytool -import -alias localhost -file D:localhost.cer -noprompt -trustcacerts -storetype jks -keystore cacerts -storepass 123456
如果導入失敗,可以先把 security 目錄下的 cacerts 删掉;
3、按照伺服器端的 Jetty Maven 插件的配置方式配置 Jetty 插件;
4、在 shiro-example-chapter15-client 子產品中導入 shiro-cas 依賴,具體請參考其 pom.xml;
5、自定義 CasRealm:
public class MyCasRealm extends CasRealm {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
}
CasRealm 根據 CAS 伺服器端傳回的使用者身份擷取相應的角色 / 權限資訊。
6、spring-shiro-web.xml 配置:
<bean id="casRealm" class="com.github.zhangkaitao.shiro.chapter13.realm.MyCasRealm">
<property name="userService" ref="userService"/>
……
<property name="casServerUrlPrefix" value="https://localhost:8443/chapter14-server"/>
<property name="casService" value="https://localhost:9443/chapter14-client/cas"/>
</bean>
casServerUrlPrefix:是 CAS Server 伺服器端位址;
casService:是目前應用 CAS 服務 URL,即用于接收并處理登入成功後的 Ticket 的;
如果角色 / 權限資訊是由伺服器端提供的話,我們可以直接使用 CasRealm:
<bean id="casRealm" class="org.apache.shiro.cas.CasRealm">
……
<property name="defaultRoles" value="admin,user"/>
<property name="defaultPermissions" value="user:create,user:update"/>
<property name="roleAttributeNames" value="roles"/>
<property name="permissionAttributeNames" value="permissions"/>
<property name="casServerUrlPrefix" value="https://localhost:8443/chapter14-server"/>
<property name="casService" value="https://localhost:9443/chapter14-client/cas"/>
</bean>
defaultRoles/ defaultPermissions:預設添加給所有 CAS 登入成功使用者的角色和權限資訊; roleAttributeNames/ permissionAttributeNames:角色屬性 / 權限屬性名稱,如果使用者的角色 / 權限資訊是從伺服器端傳回的(即傳回的 CAS Principal 中除了 Principal 之外還有如一些 Attributes),此時可以使用 roleAttributeNames/ permissionAttributeNames 得到 Attributes 中的角色 / 權限資料;請自行查詢 CAS 擷取使用者更多資訊。
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="/casFailure.jsp"/>
</bean>
CasFilter 類似于 FormAuthenticationFilter,隻不過其驗證伺服器端傳回的 CAS Service Ticket。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="https://localhost:8443/chapter14-server/login?service=https://localhost:9443/chapter14-client/cas"/>
<property name="successUrl" value="/"/>
<property name="filters">
<util:map>
<entry key="cas" value-ref="casFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/casFailure.jsp = anon
/cas = cas
/logout = logout
/** = user
</value>
</property>
</bean>
loginUrl:https://localhost:8443/chapter15-server/login
表示服務端端登入位址,登入成功後跳轉到?service 參數對于的位址進行用戶端驗證及登入;
“/cas=cas”:即 /cas 位址是伺服器端回調位址,使用 CasFilter 擷取 Ticket 進行登入。
7、測試,輸入
http://localhost:9080/chapter15-client
位址進行測試即可,可以使用如 Chrome 開這 debug 觀察網絡請求的變化。
如果遇到以下異常,一般是證書導入錯誤造成的,請嘗試重新導入,如果還是不行,有可能是運作應用的 JDK 和安裝數字證書的 JDK 不是同一個造成的:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
... 67 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 73 more