1. 程式人生 > >cas shiro spring實現單點登入

cas shiro spring實現單點登入

這裡貼出傳送門,來自幕課網大神。講了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;
    }

}