[心缘地方]同学录
首页 | 功能说明 | 站长通知 | 最近更新 | 编码查看转换 | 代码下载 | 常见问题及讨论 | 《深入解析ASP核心技术》 | 王小鸭自动发工资条VBA版
登录系统:用户名: 密码: 如果要讨论问题,请先注册。

[整理]Java采用CAS实现单点登录的大概步骤~~

上一篇:[转帖]linux下swing中文显示为方框的解决方法
下一篇:[转帖]CAS认证流程详解

添加日期:2013/9/13 17:05:23 快速返回   返回列表 阅读10039次
    单点登录,决定采用开源框架CAS,官网www.jasig.org/cas
现在好像改成:https://www.apereo.org/cas
    初次访问子系统时,Client端的Filter会指挥request跳转到Server端要求登录,登录后跳回子系统,并将username传递给子系统,放在session中。访问其它子系统时,仍然会跳转到Server端验证,通过后跳回子系统,并将username带回。由于用户只登录Server端,所以没有跨域的烦恼。

CAS下载
(1)访问官网,下载最新的Server及Client的zip包。本次下载的是cas-server-3.5.2-release.zip和cas-client-3.2.1-release.zip。


CAS服务端安装

(1)解压server的zip包,复制modules目录下的cas-server-webapp-3.5.2.war包到tomcat的webapp目录下,并改名为cas.war。

(2)启动tomcat,访问http://localhost:8080/cas/login即可。

(3)默认设置下,用户名和密码一样即可登录。修改WEB-INF/deployerConfigContext.xml,根据数据库进行验证即可。
具体怎么改,请搜索这一大串吧:org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler

(4)根据需要修改页面,在WEB-INF/view/jsp/default/ui目录下,主要是两个:
    casLoginView.jsp,登录页面
    casGenericSuccess.jsp,登录成功界面

(5)如果只修改界面,还比较简单,如果修改流程,就很麻烦了,此处省略啦。
实际部署时,复制修改好的cas目录,到tomcat的webapp目录,并修改WEB-INF/deployerConfigContext.xml中的数据源设置即可。


子系统设置:
----------------------------------------------------
(1)解压client的zip包,复制modules目录下的cas-client-core-3.2.1.jar和commons-logging-1.1.jar到子系统的WEB-INF/lib目录下。

(2)在WEB-INF/Web.xml中添加以下内容,添加在靠前的位置。
其中两个serverName的地址改为你本机的ip和端口


<!-- ======================设置字符编码,必须在最前面======================= -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>edu.vt.middleware.servlet.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>requestCharsetName</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>responseCharsetName</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ======================CAS 设置 Begin======================= -->
<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <!-- CAS login 服务地址-->
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>http://10.0.103.136:8080/cas/login</param-value>
    </init-param>
    <!-- 客户端应用服务地址-->
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://10.0.103.136:8080</param-value>
    </init-param>
    <init-param>
        <param-name>renew</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>gateway</param-name>
        <param-value>false</param-value>
    </init-param>
</filter>

<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>http://10.0.103.136:8080/cas/</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://10.0.103.136:8080</param-value>
    </init-param>
</filter>

<filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>

<filter>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 退出URL-->
<context-param>
    <param-name>casLogoutUrl</param-name>
    <param-value>http://10.0.103.136:8080/cas/logout</param-value>
</context-param>
<!-- ======================CAS 设置 End======================= -->



(3)修改casServer的ip地址,serverName修改为子系统的地址.

(4)退出的地址改为这样:
<a href="${initParam.casLogoutUrl}">退出</a>

(5)显示用户名,使用${pageContext.request.remoteUser}即可。

(6)原系统的checkSession之类的校验可以去掉,
因为,单点登录已经帮忙做了用户认证,并已经把用户名放在了Session中

(7)入口地址改为登录后的地址,也就是,用户能进来的话,就已经是登录状态了。

(8)在入口地址中,如果需要,可以


Assertion assertion = (Assertion) req.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
if(assertion!=null){
    String userName = assertion.getPrincipal().getName();
    ..根据用户名取得其他权限等信息


需要把cas-client-core-3.2.1.jar包放到build path中

(9)如果不需要同步用户信息,则添加完毕。
----------------------------------------------------
CharacterEncodingFilter是在
http://code.google.com/p/vt-middleware/wiki/vtservletfilters#CharacterEncodingFilter
下载的,比较简单,可以自己写一个。

注意,一定要加CharacterEncodingFilter,否则单点退出会影响编码,导致乱码。

---------------------------------------------------
单点登录,只是第一步,登录后,子系统应该根据用户名取得用户的权限等信息才行。

还有,Server端应该与子系统实现用户的同步,比如添加,删除~~

初步打算用activeMq,Server端发布消息,子系统持久订阅,收到消息后进行同步添加或删除等操作。
---------------------------------------------------
用户同步方法
    考虑到子系统的现状,决定将用户登录信息,放到Server端统一管理。用户权限由每个子系统自己实现,也就是用户数据与权限数据分离,CAS只管用户验证,不管用户权限。
    用户同步决定采用ActiveMq,Server端添加或删除用户时,发送同步消息到ActiveMq。子系统添加Filter监听ActiveMq的消息,接收到消息后,交给子系统进行用户同步的添加或删除。
    为了防止同步消息丢失,子系统的Filter应该在ActiveMq中进行持久订阅。这样,即使Server端发送消息时,子系统没有启动,消息也不会丢失。子系统下次启动时,仍能接收错过的消息,进行处理。

服务端用户管理
    服务端应该提供用户管理的功能,故单独建立一个UserManage的工程。省略。添加或删除用户时,应该向activeMq发送消息,代码参考下面的ProducerTest即可,配置参考下面的web.xml修改。

服务端ActiveMq启动
    ActiveMq官网:activemq.apache.org/
    下载apache-activemq-5.8.0-bin.tar.gz,解压,执行bin/activemq文件即可。默认监听本机61616端口。

子系统监听同步消息

(1)复制以下文件到WEB-INF/lib下
activemq-all-5.8.0.jar(在activemq目录下)
userSync.jar(自己写的,代码见后)

(2)在WEB-INF/Web.xml中添加以下内容


    <!-- activeMQ 地址,修改IP即可-->
    <context-param>
        <param-name>activeMqUrl</param-name>
        <param-value>tcp://10.0.103.136:61616</param-value>
    </context-param>
    
    <!-- 订阅的Topic名称,固定不动-->
    <context-param>
        <param-name>topicName</param-name>
        <param-value>userSyncTopic</param-value>
    </context-param>
    
    <!-- 客户端Id,字符串,value保证每个子系统不同即可-->
    <context-param>
        <param-name>clientId</param-name>
        <param-value>web</param-value>
    </context-param>
    
    <listener>  
        <listener-class>cn.xx.ocean.js.cas.UserSyncListener</listener-class>  
    </listener>


(3)在子系统,增加一个框架启动后,自动执行的类。
如nutz框架,可以在MainModule增加
@SetupBy(SystemSetup.class)
public class MainModule {
}


package cn.xx.cas.user.manage.action;

import org.nutz.mvc.NutConfig;
import org.nutz.mvc.Setup;

import cn.xx.ocean.js.cas.UserSyncMessage;
import cn.xx.ocean.js.cas.UserSyncMessageCenter;
import cn.xx.ocean.js.cas.UserSyncType;

public class SystemSetup implements Setup {

    @Override
    public void destroy(NutConfig arg0) {

    }

    @Override
    public void init(NutConfig arg0) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    UserSyncMessage msg = UserSyncMessageCenter.takeMsg();
                    if (msg != null) {
                        System.out.println(msg.getUsername());
                        switch (msg.getType()) {
                        case Add:
                            // 增加用户
                            break;
                        case Delete:
                            // 删除用户
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}


监听用户同步消息,并进行同步处理即可。

UserSync代码


package cn.xx.ocean.js.cas;

public enum UserSyncType {
    Add,
    Enable,
    Disable,
    Delete
}




package cn.xx.ocean.js.cas;

import java.io.Serializable;

public class UserSyncMessage implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = -4995848483963127420L;

    private UserSyncType type = null;

    private  String username = null;

    public UserSyncType getType() {
        return type;
    }

    public void setType(UserSyncType type) {
        this.type = type;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}



package cn.xx.ocean.js.cas;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;

/**
 * 用户同步消息的监听器.
 */
public class UserSyncListener implements ServletContextListener,
        MessageListener {

    /**
     * Logger.
     */
    Logger log = Logger.getLogger(UserSyncListener.class);

    /**
     * ActiveMq的默认用户名.
     */
    private static String user = ActiveMQConnection.DEFAULT_USER;

    /**
     * ActiveMq的默认密码.
     */
    private static String password = ActiveMQConnection.DEFAULT_PASSWORD;

    /**
     * JMS Connection对象.
     */
    private Connection connection = null;

    /**
     * 初始化.
     */
    public void contextInitialized(ServletContextEvent event) {

        // 从web.xml取配置参数,懒得检查了.
        ServletContext context = event.getServletContext();
        String url = context.getInitParameter("activeMqUrl");
        String topicName = context.getInitParameter("topicName");
        String clientId = context.getInitParameter("clientId");

        try {
            // 创建一个连接
            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                    user, password, url);
            connection = connectionFactory.createConnection();
            connection.setClientID(clientId); // 设置客户端ID,持久订阅必须的。
            connection.start();

            // 创建一个session
            Session session = connection.createSession(false,
                    Session.AUTO_ACKNOWLEDGE);

            // 取得Topic,没有的话,会自动创建
            Topic topic = session.createTopic(topicName);

            // 创建持久订阅,第二个参数是订户名称,和clientID可以不同
            MessageConsumer consumer = session.createDurableSubscriber(topic,
                    clientId);

            // 注册消息监听
            consumer.setMessageListener(this);

        } catch (JMSException e) {
            log.error("向activemq订阅出错.", e);
        }

    }

    /**
     * 销毁.
     */
    public void contextDestroyed(ServletContextEvent event) {
        if (connection != null) {
            try {
                connection.close();
            } catch (JMSException e) {
            }
        }
    }

    /**
     * 消息处理.
     */
    @Override
    public void onMessage(Message msg) {

        try {
            if (msg instanceof TextMessage) {
                // 普通文本消息,直接显示看看
                System.out.println(((TextMessage) msg).getText());

            } else if (msg instanceof ObjectMessage) {
                Object obj = ((ObjectMessage) msg).getObject();

                // 如果是用户同步消息
                if (obj instanceof UserSyncMessage) {

                    // 放入消息队列
                    UserSyncMessageCenter.putMsg((UserSyncMessage) obj);
                }
            }

        } catch (JMSException e) {
            log.error("处理activemq消息出错.", e);
        }

    }

}



package cn.xx.ocean.js.cas;

import java.util.concurrent.LinkedBlockingQueue;

/**
 * 消息中心.
 */
public class UserSyncMessageCenter {

    /**
     * 消息队列,本身是线程安全的.
     */
    private static final LinkedBlockingQueue<UserSyncMessage> queue = new LinkedBlockingQueue<UserSyncMessage>();

    /**
     * 放入一条消息.
     * 
     * @param msg
     *            消息内容
     */
    public static void putMsg(final UserSyncMessage msg) {
        if (msg == null) {
            return;
        }

        try {
            queue.put(msg);
        } catch (InterruptedException e) {
        }
    }

    /**
     * 取一条消息.
     * 
     * @return 消息内容
     */
    public static UserSyncMessage takeMsg() {
        try {
            return queue.take();
        } catch (InterruptedException e) {
            return null;
        }
    }

    /**
     * 清空消息.
     */
    public static void clear() {
        queue.clear();
    }

}




package cn.xx.ocean.js.cas;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class ProducerTest {

    // ActiveMq的地址、用户名、密码,这里都是默认的。
    private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
    private static String user = ActiveMQConnection.DEFAULT_USER;
    private static String password = ActiveMQConnection.DEFAULT_PASSWORD;

    // Topic的名称
    private static String topicName = "userSyncTopic";

    public static void main(String[] args) {

        Connection connection = null;

        try {
            // 创建一个连接
            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                    user, password, "tcp://10.0.103.136:61616");
            connection = connectionFactory.createConnection();
            connection.start();

            // 创建一个session
            Session session = connection.createSession(false,
                    Session.AUTO_ACKNOWLEDGE);

            // 取得Topic,没有的话,会自动创建
            Topic topic = session.createTopic(topicName);

            // 创建一个消息生产者对象
            MessageProducer producer = session.createProducer(topic);

            // 设置消息持久化存储
            producer.setDeliveryMode(DeliveryMode.PERSISTENT);

            // 创建一个消息对象
            ObjectMessage message = session.createObjectMessage();
            UserSyncMessage msg = new UserSyncMessage();
            msg.setType(UserSyncType.Add);
            msg.setUsername("admin");
            message.setObject(msg);

            // 消息发送
            producer.send(message);

            // 关一切
            connection.stop();
            connection.close();

        } catch (JMSException e) {
            e.printStackTrace();
        }

    }
}

 

评论 COMMENTS
没有评论 No Comments.

添加评论 Add new comment.
昵称 Name:
评论内容 Comment:
验证码(不区分大小写)
Validation Code:
(not case sensitive)
看不清?点这里换一张!(Change it here!)
 
评论由管理员查看后才能显示。the comment will be showed after it is checked by admin.
CopyRight © 心缘地方 2005-2999. All Rights Reserved