单点登录,决定采用开源框架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(); }
} }
|