cas shiro spring实现单点登录

来源:互联网 发布:上海争游网络首页 编辑:程序博客网 时间:2024/06/06 08:30

《SSO CAS单点系列》之 实操!轻松玩转SSO CAS就这么简单(相识篇)
《SSO CAS单点系列》之 实操!轻松玩转SSO CAS就这么简单(相遇篇)

这里贴出传送门,来自幕课网大神。讲了cas配置和cas的基本原理。

CAS

Github地址:https://github.com/apereo/cas/tags
war下载地址:http://mvnrepository.com/artifact/org.jasig.cas/cas-server-webapp
解压war包,新建一个工程,将解压后的war内容依次放入工程中。
结构如下:
这里写图片描述
这里只是使用和修改一下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}"/>

默认为两小时。
ps:cas提供了remember me的解决方案,需要修改配置。
具体参考:https://my.oschina.net/mashiguang/blog/71005

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文件配置

#统一登录urlshiro.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8080/shiroNode/shiro-cas1#统一登出urlshiro.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-cas1shiro.failureUrl=/users/loginSuccessshiro.successUrl=/users/loginSuccess

这里着重说明一下接收票据的service url配置:
这里配置不是绝对的,是可以修改的,但是如果修改那么对应需要修改几个地方。
如现在的地址为:
shiro.cas.service=http://127.0.0.1:8080/shiroNode/sso-test
那么
shiro.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8080/shiroNode/sso-test

shiro.logoutUrl=http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8080/shiroNode/sso-test

<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;    }}

cas默认返回用户名,也可以返回更多信息,具体参考如下地址:
http://blog.csdn.net/u012410733/article/details/51729791