CAS多点登录

转自:http://www.blogjava.net/alwayscy/archive/2012/12/01/392322.html

场景

想要用到的场景:用户访问WEB服务,WEB访问非WEB服务1,服务1又再访问2、3,合并计算后,把数据返回给WEB及前端用户。想让访问链上的所有服务都能得到认证和鉴权,认为本次请求确实是来自用户的。所以想到用CAS,让用户在一点登录,所有服务都到此处认证和鉴权。

CAS小介绍

下面是两张图,来自网上两个PPT(猛戳下载),其中一个还有动画演示,感谢原分享者。

用我的话解释下就是:

1、 用户先访问http://adm/index.html服务,因为没有登陆被重定向到CAS去输入用户名密码,这个好理解。注意重定向地址:
https://cas.company.com/login?service=http://adm/index.html 
问号前是CAS服务地址,后面跟了原来要访问的服务,方便CAS把你再重定向回来。

2、 登陆完成后,CAS会写一个COOKIE(CASTGC),它的作用是下次再认证时不用再输入密码。同时,CAS把用户重定向回原来访问地址:
http://adm/index.html?ticket= ST-1-qRPh34B1xhe4dquzz
注意后面多了个ticket

3、 这时候,ADM这个WEB服务,再用ticket去CAS做认证,CAS报告OK,它即认为用户登陆了。

4、 如:用户再访问下一个AMS的WEB服务时,因为带有CASTGC这个COOKIE,被重定向到CAS后,它就会用这个COOKIE直接生成一个ticket(就没有让用户登陆的过程了哦!),AMS拿到ticket后再去认证就可以了。

开头场景遇到的问题

开始我们的场景如果全部照搬CAS的应用,会存在如下问题:

1、 和CAS的交互全走HTTPS,要在JRE中生成和导入证书(网上搜配置tomcat的https一大把),用户认证时会被提示证书不可信。如果是一个直接交付给终端的产品,谁来配置这些东东?让用户看到这种提示又情何以堪?

2、 每当访问一个新服务都要和CAS产生两次交互,申请签发TICKET,再去认证TICKET

3、 默认的ticket有效时间很短,重定向回来后,要马上去认证,并且一个ticket只能去CAS认证一次就失效了

4、 Ticket是和原始的URL绑定的,两者都要提供给CAS才能认证通过,即你不能用AMS服务签发的ticket,去用在ADM服务的认证上

5、 如果是非WEB的服务要认证,需要用到CAS的代理模式,过程比较繁复

结论是开头场景要用CAS是很艰难的。

变通后的方案

这是我想到的一些改动来满足开头场景:

1、 改为HTTP验证方式

2、 由WEB服务去CAS签发一次TICKET,后继的非WEB服务全部用这一个TICKET到CAS做认证,它和用户登陆后有效期一致,也没有使用次数限制

3、 提供一个FILTER来为WEB层所有页面统一提供认证服务

4、 用户名、密码、鉴权信息(用户角色)存到数据库

下面就介绍这种非主流的改法,可能已经安全性大大降低,但至少能RUN啦。。。

下载安装

下载并解压CAS安装包:(不要问为啥下载JASIG的,因为网上全是它。。)

http://www.jasig.org/cas_server_3_5_1_release

解压后带源码,后面步骤还会用到。

把其中modules目录下的这个WAR包布到tomcat的webapp目录,重启下就算安装好了:

cas-server-3.5.1/modules/cas-server-webapp-3.5.1.war

改配置解决HTTP和TICKET生命期

1、 加长TICKET的生命期和使用次数:

2、 改为使用HTTP:

从JDBC认证和鉴权

1、 把默认的用户名密码相同即通过的认证方式注释掉:

替换为下面这段从数据库读取:

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">

<property name="sql" value="select passwd from t_admin where nickname=?"/>

<property name="dataSource" ref="dataSource"/>

</bean>

2、 把默认的鉴权信息获取方式注释掉:

替换为:

这种是一个用户仅一个角色(SingleRow):

<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">

<constructor-arg index="0" ref="dataSource"/>

<constructor-arg index="1" value="SELECT g.id,g.groupname,role.role FROM t_group AS g LEFT OUTER JOIN t_group_role AS grouprole ON (g.id = grouprole.groupid) LEFT OUTER JOIN t_role AS role ON (role.id = grouprole.roleid) LEFT OUTER JOIN t_group_user AS groupuser on (g.id = groupuser.groupid) LEFT OUTER JOIN t_admin ON (t_admin.id = groupuser.userid) WHERE t_admin.nickname = ?"/>

<!--这里的key需写username,value对应数据库用户名字段 -->

<property name="queryAttributeMapping">

<map>

<entry key="username" value="nickname"/>

</map>

</property>

<!--key对应数据库字段,value对应客户端获取参数 -->

<property name="resultAttributeMapping">

<map>

<entry key="role" value="authorities"/>

</map>

</property>

</bean>

3、 多行模式(和上面的单行模式二选一)

这种是一个用户对应多个角色(MultiRow):(这里这个attr_name绕了我半天。。。这里有点解释

<bean id="attributeRepositoryMulti" class="org.jasig.services.persondir.support.jdbc.MultiRowJdbcPersonAttributeDao">

<constructor-arg index="0" ref="dataSource"/>

<constructor-arg index="1" value="SELECT g.id,g.groupname,‘authorities‘ as attr_name,role.role FROM t_group AS g LEFT OUTER JOIN t_group_role AS grouprole ON (g.id = grouprole.groupid) LEFT OUTER JOIN t_role AS role ON (role.id = grouprole.roleid) LEFT OUTER JOIN t_group_user AS groupuser on (g.id = groupuser.groupid) LEFT OUTER JOIN t_admin ON (t_admin.id = groupuser.userid) WHERE t_admin.nickname = ?"/>

<!--这里的key需写username,value对应数据库用户名字段 -->

<property name="queryAttributeMapping">

<map>

<entry key="username" value="nickname"/>

</map>

</property>

<property name="nameValueColumnMappings">

<map>

<entry key="attr_name" value="role" />

</map>

</property>

</bean>

如果要用多行模式把相应的引用的类要变一下:

4、 鉴权信息要能输出到前端,还要改下JSP:

在上图所示位置加下:

<c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)> 0}">

<cas:attributes>

<c:forEach

var="attr"

items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"

varStatus="loopStatus"

begin="0"

end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)-1}"

step="1">

<cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>

</c:forEach>

</cas:attributes>

</c:if>

5、 加上数据源定义

如下:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

<property name="url" value="jdbc:mysql://127.0.0.1:3306/uu?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;transformedBitIsBoolean=true"></property>

<property name="username" value="root"></property>

<property name="password" value="xxxxx"></property>

</bean>

6、 建表:(表结构来自此处)

SET FOREIGN_KEY_CHECKS=0;

------------------------------

-- 创建管理员帐号表t_admin

-- ----------------------------

CREATE TABLE `t_admin` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`passwd` varchar(12) NOT NULL DEFAULT ‘‘ COMMENT ‘用户密码‘,

`nickname` varchar(20) NOT NULL DEFAULT ‘‘ COMMENT ‘用户名字‘,

`phoneno` varchar(32) NOT NULL DEFAULT ‘‘ COMMENT ‘电话号码‘,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------

-- 添加3个管理帐号

-- ----------------------------

INSERT INTO `t_admin` VALUES (‘1‘, ‘admin‘, ‘admin‘, ‘‘);

INSERT INTO `t_admin` VALUES (‘4‘, ‘123456‘, ‘test‘, ‘‘);

INSERT INTO `t_admin` VALUES (‘5‘, ‘111111‘, ‘111111‘, ‘‘);

-- ----------------------------

-- 创建权限表t_role

-- ----------------------------

CREATE TABLE `t_role` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`role` varchar(40) NOT NULL DEFAULT ‘‘,

`descpt` varchar(40) NOT NULL DEFAULT ‘‘ COMMENT ‘角色描述‘,

`category` varchar(40) NOT NULL DEFAULT ‘‘ COMMENT ‘分类‘,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8;

-- ----------------------------

-- 加入4个操作权限

-- ----------------------------

INSERT INTO `t_role` VALUES (‘1‘, ‘ROLE_ADMIN‘, ‘系统管理员‘, ‘系统管理员‘);

INSERT INTO `t_role` VALUES (‘2‘, ‘ROLE_UPDATE_FILM‘, ‘修改‘, ‘影片管理‘);

INSERT INTO `t_role` VALUES (‘3‘, ‘ROLE_DELETE_FILM‘, ‘删除‘, ‘影片管理‘);

INSERT INTO `t_role` VALUES (‘4‘, ‘ROLE_ADD_FILM‘, ‘添加‘, ‘影片管理‘);

-- ----------------------------

-- 创建权限组表

-- ----------------------------

CREATE TABLE `t_group` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`groupname` varchar(50) NOT NULL DEFAULT ‘‘,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------

-- 添加2个权限组

-- ----------------------------

INSERT INTO `t_group` VALUES (‘1‘, ‘Administrator‘);

INSERT INTO `t_group` VALUES (‘2‘, ‘影片维护‘);

-- ----------------------------

-- 创建权限组对应权限表t_group_role

-- ----------------------------

CREATE TABLE `t_group_role` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`groupid` bigint(20) unsigned NOT NULL,

`roleid` bigint(20) unsigned NOT NULL,

PRIMARY KEY (`id`),

UNIQUE KEY `groupid2` (`groupid`,`roleid`),

KEY `roleid` (`roleid`),

CONSTRAINT `t_group_role_ibfk_1` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`),

CONSTRAINT `t_group_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8;

-- ----------------------------

-- 加入权限组与权限的对应关系

-- ----------------------------

INSERT INTO `t_group_role` VALUES (‘1‘, ‘1‘, ‘1‘);

INSERT INTO `t_group_role` VALUES (‘2‘, ‘2‘, ‘2‘);

INSERT INTO `t_group_role` VALUES (‘4‘, ‘2‘, ‘4‘);

-- ----------------------------

-- 创建管理员所属权限组表t_group_user

-- ----------------------------

CREATE TABLE `t_group_user` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`userid` bigint(20) unsigned NOT NULL,

`groupid` bigint(20) unsigned NOT NULL,

PRIMARY KEY (`id`),

KEY `userid` (`userid`),

KEY `groupid` (`groupid`),

CONSTRAINT `t_group_user_ibfk_2` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`),

CONSTRAINT `t_group_user_ibfk_3` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

-- ----------------------------

-- 将管理员加入权限组

-- ----------------------------

INSERT INTO `t_group_user` VALUES (‘1‘, ‘1‘, ‘1‘);

INSERT INTO `t_group_user` VALUES (‘2‘, ‘4‘, ‘2‘);

-- ----------------------------

-- 创建管理员对应权限表t_user_role

-- 设置该表可跳过权限组,为管理员直接分配权限

-- ----------------------------

CREATE TABLE `t_user_role` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`userid` bigint(20) unsigned NOT NULL,

`roleid` bigint(20) unsigned NOT NULL,

PRIMARY KEY (`id`),

KEY `userid` (`userid`),

KEY `roleid` (`roleid`),

CONSTRAINT `t_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`),

CONSTRAINT `t_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

与spring-security结合使用

我们是自己开发filter,但顺便把spring-security的配置方法带一下:

1、 Web.xml加上spring-security的filter:

<!-- spring 配置文件 -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:/applicationContext-security-ns.xml</param-value>

</context-param>

<!-- spring 默认侦听器 -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<listener>

<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>

</listener>

<!-- Filter 定义 -->

<!-- spring security filter -->

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

2、 application参考配置以及修改的vote文件(这里

在单行模式下,权限信息可以正常解析到detail变量中,但在多行的时候,CAS传过来的多个角色是这种格式:[ROLE_1,ROLE_2],带有中括号,原有VOTE是精确比对,附件里改了个index函数来比对:

有了上面这些Spring-security就可以正常运作,但是场景里要使用非WEB服务多次验证,所以其实不能用spring-security的filter,我们还是要自己写的。

Go on…

解除TICKET与SERVICE的绑定,要改代码哦!

Cas服务器ticket和service验证时,要和来签发时的service url一致才行,否则就报下面的错误:

org.jasig.cas.client.validation.TicketValidationException:

ticket ‘ST-14-SYa99tdAMhI31ZehfSTW-cas01.example.org‘ does not match supplied service

为了我们多个不同服务可以重复使用一个ticket,CAS的源码上做个小小改动即可:

去到之前解压的CAS目录,搜出这句话的java文件:

grep -R "does not match supplied service" .

修改这个文件:

vi ./cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java

注释掉验证服务URL的相应行就好了:

编译一下:

mvn compile

mvn -DskipTests=true package

把编好的:

cas-server-3.5.1/cas-server-core/target/cas-server-core-3.5.1.jar

拷到下列位置替换原来的:

/usr/local/apache-tomcat-7.0.32/webapps/cas/WEB-INF/lib/

重启下TOM的小猫就可以了。

自制的FILTER

自己制作的filter要达到目标是:

1、 在没认证时可以重定向

2、 重定向回来的时候,要去认证并把ticket写COOKIE

3、 有COOKIE时,取出来直接去做认证

Filter的一个比较清晰易懂的基础介绍

一个讲COOKIE的文章

最后是完整代码:点我

Filter的代码是CasFilter.java,相当简单,下面这段就是用CAS提供的客户端去验证TICKET,因为service不验了,所以validate的第二个参数已经不重要。

Attributes就是权限信息:

非WEB服务的认证和鉴权

我们开头场景中的WEB服务在访问后端时,把TICKET信息也带上,这样后端的非WEB服务也可以用刚才提到的函数去认证,并且获取到权限信息,做自己的鉴权。

服务1再访问服务2时,也一样带上TICKET,这样因为我们已经延长了TICKET有效次数和期限,它也不会过期。

但是,一旦用户LOGOUT了,这个TICKET也就失效,此时所有服务都将验证不通过,WEB自然又会把用户重定向到CAS去登陆了。

罗里八嗦讲一大堆,用了好多歪门斜道,不值一提,只是为今后有个查阅的地方。

时间: 2024-10-14 04:32:20

CAS多点登录的相关文章

CAS单点登录服务器搭建

关于cas单点登录的原理及介绍这里不做说明了,直接开始: 1.war包下载 去官网(https://www.apereo.org/projects/cas/download-cas)下载cas_server-webapp_xxx.war文件,这里使用的是cas-server-webapp-4.0.0.war.(这里吐槽下,由于cas源码及war包都已经放到github上面去了,导致下载速度很慢或者无法下载,最后无奈之下去CSDN下载了...) 2.创建证书 证书是单点登录认证系统中很重要的一把钥

CAS单点登录(SSO)完整教程

转:http://blog.csdn.net/frinder/article/details/7969925 CAS单点登录(SSO)完整教程(2012-02-01更新) 一.教程说明 前言 教程目的:从头到尾细细道来单点登录服务器及客户端应用的每个步骤 单点登录(SSO):请看百科解释猛击这里打开 本教程使用的SSO服务器是Yelu大学研发的CAS(Central Authentication Server), 官网:http://www.jasig.org/cas 本教程环境: Tomcat

CAS单点登录之mysql数据库用户验证及常见问题

前面已经介绍了CAS服务器的搭建,详情见:搭建CAS单点登录服务器.然而前面只是简单地介绍了服务器的搭建,其验证方式是原始的配置文件的方式,这显然不能满足日常的需求.下面介绍下通过mysql数据库认证的方式. 一.CAS认证之mysql数据库认证 1.在mysql中新建一个cas数据库并创建user表 CREATE DATABASE /*!32312 IF NOT EXISTS*/`cas` /*!40100 DEFAULT CHARACTER SET gbk */; USE `cas`; /*

JAVA CAS单点登录之三:CAS代理模式演练

前言 JAVA CAS单点登录之一:搭建CAS服务器 JAVA CAS单点登录之二:CAS普通模式1演练 代理模式相相对上一节的普通模式,更加复杂了.但配置起来也会稍微有些差别.所谓难者不会,会者不难.如果遇到一个从来没有遇到的问题,解决起来也是非常棘手的,当然解决之后就不是事了.我就遇到了一个CAS 坑爹的错误.一步步按照别人的博客坐下来,普通模式部署没有多大问题,就是不知道为什么代理模式总是出错,搜遍了整个网络,也没找到问题所在,我就纳闷了,为什么就没有人 遇到过呢.还好,最后我使用了杀手锏

CAS单点登录配置[1]:准备工作

关于CAS是什么这里就不在赘述,网友将它比喻成旅游景点的套票,买了一个套票就可以观看所有景点,不需要一个景点买一次票...我们重点介绍CAS单点登录的配置. 工具/原料 1.配置好JDK环境,否则不方便使用JDK自带的keytool工具: 至少安装了一个数据库,本文以SQLServer2008为例: 3.最好安装了MyEclipse/Eclipse,配置了Tomcat,本文以MyEclipse为例: 下载cas服务器端及客户端程序,本文以cas-server-3.4.10-release和cas

SSO之CAS单点登录实例演示

一.概述 此文的目的就是为了帮助初步接触SSO和CAS 的人员提供一个入门指南,一步一步演示如何实现基于CAS的单点登录. CAS的官网:http://www.jasig.org/cas 二.演示环境 本文演示过程在同一个机器上的(也可以在三台实体机器或者三个的虚拟机上),环境如下: windows7 64位,主机名称:michael-pc JDK 1.6.0_18 Tomcat 6.0.29 CAS-server-3.4.11.CAS-client-3.2.1 根据演示需求, 用修改hosts

cas单点登录实现

前言 此文为记录单点登录实现过程,包括cas服务端和客户端的定制扩展 服务端 单点登录服务端采用cas,以cas-server-webapp版本号为3.5.2.1为基础进行定制扩展实现. 定制实现的源码功能以上传至svn代码库,路径为:svn://192.168.9.16/minxin/Repositories/minxinloan/trunk/mxcas-server-webapp. 此版本的定制扩展实现采用http协议(关闭了https协议),下面对此版的定制扩展进行详细的描述. 关闭htt

微信网页开发之获取用户unionID的两种方法--基于微信的多点登录用户识别

假设网站A有以下功能需求:1,pc端微信扫码登录:2,微信浏览器中的静默登录功能需求,这两种需求就需要用到用户的unionID,这样才能在多个登录点(终端)识别用户.那么这两种需求下用户的unionID该如何获取呢? 1,先看pc端的解决方案 以snsapi_login为scope发起网页授权,先拿网站应用的appid和secret用授权接口获取"网页授权access_token",再利用"网页授权access_token"通过"拉取用户信息的api接口&

SSO之CAS单点登录详细搭建教程

本教程是我个人编写,花费几个小时的时间,给需要学习的人员学习使用,希望能帮助到你们. [环境说明]:本文演示过程在同一个机器上的(也可以在三台实体机器或者三个的虚拟机上),环境如下: windows7 64位 jdk1.7.0_51 apache-tomcat-7.0.57-windows-x64 cas-server-webapp-4.0.0.war.cas-client-core-3.2.1.jar.commons-logging.jar 确保本地jdk环境已经搭建好 [软件说明]:文档中涉