cas shiro spring實現單點登入
阿新 • • 發佈:2019-01-05
這裡貼出傳送門,來自幕課網大神。講了cas配置和cas的基本原理。
CAS
deployerConfigContext.xml配置檔案
預設通過配置檔案管理授權登入賬戶
<bean id="primaryAuthenticationHandler"
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="casuser" value="Mellon"/>
</map>
</property>
</bean>
在實際應用中我們的賬戶管理多半在資料庫中,cas也支援這種模式
這裡使用4.0.0為例
<!-- cas自帶 設定密碼的加密方式,這裡使用的是MD5加密 -->
<bean id="passwordEncoder"
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"
c:encodingAlgorithm ="MD5" p:characterEncoding="UTF-8" />
<!-- 通過資料庫驗證身份,這個得自己去實現 -->
<bean id="primaryAuthenticationHandler"
class="com.distinct.cas.jdbc.QueryDatabaseAuthenticationHandler"
p:dataSource-ref="dataSource" p:passwordEncoder-ref="passwordEncoder"
p:sql="select top 1 UserPwd from Users where UserName=? " />
<!-- 設定資料來源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"></property>
<property name="url"
value="jdbc:jtds:sqlserver://test:1433;DatabaseName=test"></property>
<property name="username" value="test"></property>
<property name="password" value="test123"></property>
</bean>
我們看到QueryDatabaseAuthenticationHandler的原始碼
public class QueryDatabaseAuthenticationHandler
extends AbstractJdbcUsernamePasswordAuthenticationHandler
{
@NotNull
private String sql;
protected final HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential)
throws GeneralSecurityException, PreventedException
{
String username = credential.getUsername();
System.err.println("======= input username:(" + username + ")");
String encryptedPassword = getPasswordEncoder().encode(credential.getPassword());
System.err.println("======= input password:(" + encryptedPassword + ")");
System.out.println("======= sql:(" + this.sql + ")");
try
{
String dbPassword = (String)getJdbcTemplate().queryForObject(this.sql, String.class,
new Object[] { username });
System.err.println("++++++ dbPassword:(" + dbPassword.trim() + ")");
if (!dbPassword.trim().equals(encryptedPassword))
{
System.err.println("Password not match.");
throw new FailedLoginException("Password does not match value on record.");
}
}
catch (IncorrectResultSizeDataAccessException e)
{
if (e.getActualSize() == 0) {
throw new AccountNotFoundException(username + " not found with SQL query");
}
e.printStackTrace();
throw new FailedLoginException("Multiple records found for " + username);
}
catch (DataAccessException e)
{
e.printStackTrace();
throw new PreventedException("SQL exception while executing query for " + username, e);
}
return createHandlerResult(credential, new SimplePrincipal(username), null);
}
public void setSql(String sql)
{
this.sql = sql;
}
}
這裡實際上在針對密碼做比對,之後的cas版本程式碼可能有所變更,但是基本的原理都是一樣的。
這裡說明一下MD5自帶加密方式加密出來的字串是小寫,我的測試庫中是大寫密碼,那麼自己繼承處理一下
package com.yvan.cas.password;
import java.security.MessageDigest;
import org.jasig.cas.authentication.handler.PasswordEncoder;
public class YvanCasPassword implements PasswordEncoder {
@Override
public String encode(String arg0) {
return getMD5(arg0).toUpperCase();
}
public static String getMD5(String message) {
String md5str = "";
try {
// 1 建立一個提供資訊摘要演算法的物件,初始化為md5演算法物件
MessageDigest md = MessageDigest.getInstance("MD5");
// 2 將訊息變成byte陣列
byte[] input = message.getBytes();
// 3 計算後獲得位元組陣列,這就是那128位了
byte[] buff = md.digest(input);
// 4 把陣列每一位元組(一個位元組佔八位)換成16進位制連成md5字串
md5str = bytesToHex(buff);
} catch (Exception e) {
e.printStackTrace();
}
return md5str;
}
/**
* 二進位制轉十六進位制
*
* @param bytes
* @return
*/
public static String bytesToHex(byte[] bytes) {
StringBuffer md5str = new StringBuffer();
// 把陣列每一位元組換成16進位制連成md5字串
int digital;
for (int i = 0; i < bytes.length; i++) {
digital = bytes[i];
if (digital < 0) {
digital += 256;
}
if (digital < 16) {
md5str.append("0");
}
md5str.append(Integer.toHexString(digital));
}
return md5str.toString().toUpperCase();
}
}
新的配置如下:
<!-- 自定義加密 -->
<bean id="yvanPasswordEncoder" class="com.yvan.cas.password.YvanCasPassword"></bean>
<!-- cas自帶 設定密碼的加密方式,這裡使用的是MD5加密 -->
<bean id="passwordEncoder"
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"
c:encodingAlgorithm="MD5" p:characterEncoding="UTF-8" />
<!-- 通過資料庫驗證身份,這個得自己去實現 -->
<bean id="primaryAuthenticationHandler"
class="com.distinct.cas.jdbc.QueryDatabaseAuthenticationHandler"
p:dataSource-ref="dataSource" p:passwordEncoder-ref="yvanPasswordEncoder"
p:sql="select top 1 UserPwd from Users where UserName=? " />
授權成功後
預設超時時間
ticketExpirationPolicies.xml中配置如下:
<bean id="grantingTicketExpirationPolicy" class="org.jasig.cas.ticket.support.TicketGrantingTicketExpirationPolicy"
p:maxTimeToLiveInSeconds="${tgt.maxTimeToLiveInSeconds:28800}"
p:timeToKillInSeconds="${tgt.timeToKillInSeconds:7200}"/>
shiro+spring
這裡預設spring mvc的基本配置,列出重要的sso配置
web.xml配置
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>singleSignOutFilter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>singleSignOutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring.xml配置
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- 設定使用者的登入連結,這裡為cas登入頁面的連結地址可配置回撥地址 -->
<property name="loginUrl" value="${shiro.loginUrl}" />
<property name="filters">
<map>
<!-- 新增casFilter到shiroFilter -->
<entry key="casFilter" value-ref="casFilter" />
<entry key="logoutFilter" value-ref="logoutFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
/shiro-cas1 = casFilter
/logout = logoutFilter
/users/test = anon
/users/** = user
</value>
</property>
</bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<!-- 配置驗證錯誤時的失敗頁面 -->
<property name="failureUrl" value="${shiro.failureUrl}" />
<property name="successUrl" value="${shiro.successUrl}" />
</bean>
<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<!-- 配置驗證錯誤時的失敗頁面 -->
<property name="redirectUrl" value="${shiro.logoutUrl}" />
</bean>
<bean id="casRealm" class="com.yvan.shiro.realm.UserRealm">
<!-- 認證通過後的預設角色 -->
<property name="defaultRoles" value="ROLE_USER" />
<!-- cas服務端地址字首 -->
<property name="casServerUrlPrefix" value="${shiro.cas.serverUrlPrefix}" />
<!-- 應用服務地址,用來接收cas服務端票據 -->
<property name="casService" value="${shiro.cas.service}" />
</bean>
<!-- Shiro's main business-tier object for web-enabled applications -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- <property name="sessionManager" ref="sessionManager" /> -->
<property name="subjectFactory" ref="casSubjectFactory"></property>
<property name="realm" ref="casRealm" />
</bean>
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"></bean>
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager"></property>
<property name="arguments" ref="securityManager"></property>
</bean>
properties檔案配置
#統一登入url
shiro.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8080/shiroNode/shiro-cas1
#統一登出url
shiro.logoutUrl=http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8080/shiroNode/shiro-cas1
#cas服務地址字首
shiro.cas.serverUrlPrefix=http://127.0.0.1:8080/cas
#接收cas票據地址
shiro.cas.service=http://127.0.0.1:8080/shiroNode/shiro-cas1
shiro.failureUrl=/users/loginSuccess
shiro.successUrl=/users/loginSuccess
<property name="filterChainDefinitions">
<value>
<!-- 指定訪問什麼url交給casFilter處理 -->
/sso-test = casFilter
/logout = logoutFilter
/users/test = anon
/users/** = user
</value>
</property>
realm實現
public class UserRealm extends CasRealm {
protected final Map<String, SimpleAuthorizationInfo> roles = new ConcurrentHashMap<String, SimpleAuthorizationInfo>();
/**
* 設定角色和許可權資訊
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String account = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = null;
if (authorizationInfo == null) {
authorizationInfo = new SimpleAuthorizationInfo();
}
return authorizationInfo;
}
/**
* 1、CAS認證 ,驗證使用者身份
* 2、將使用者基本資訊設定到會話中
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
/*List<Object> list = authc.getPrincipals().asList();
for (Object object : list) {
System.out.println((String)object.toString());
}*/
String account = (String) authc.getPrincipals().getPrimaryPrincipal();
SecurityUtils.getSubject().getSession().setAttribute("user", account);
return authc;
}
}