CAS单点登录

https://www.ibm.com/developerworks/cn/opensource/os-cn-cas/
http://minjiechenjava.iteye.com/blog/1480263

资源与环境

  • apache-tomcat-6.0.44
  • jdk1.7.0_79
  • as-server-3.5.2-release
  • cas-client-3.2.1
  • cenots 6.5 简体中文

步骤

解压tomcat,安装到/usr/local/目录中
>命令:tar -zxvf apache-tomcat-6.0.44.tar.gz -C /usr/local/

配置 Tomcat 使用 Https 协议

  1. 生成服务器端库文件
    keytool 是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书
    Keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中
    在keystore里,包含两种数据: 密钥实体(Key entity)——密钥(secret key)又或者是私钥和配对公钥(采用非对称加密)
    可信任的证书实体(trusted certificate entries)——只包含公钥

    命令:keytool -genkey -alias tomcat-server -keyalg RSA -keystore server.keystore

    输入密钥库口令: centos

    再次输入新口令: centos

    您的名字与姓氏是什么?ljh2

    您的组织单位名称是什么?ljh2

    您的组织名称是什么?ljh2

    您所在的城市或区域名称是什么?ljh2

    您所在的省/市/自治区名称是什么?ljh2

    该单位的双字母国家/地区代码是什么?ljh2

    CN=ljh2, OU=ljh2, O=ljh2, L=ljh2, ST=ljh2, C=ljh2是否正确?

    输入 的密钥口令
    (如果和密钥库口令相同, 按回车):
    生成的时候,密码输入后的第一项name一定要写你的完整计算机名称,我的是ljh2

    参数说明:
    genkey:生成库文件,在没有指定生成位置的情况下,keystore会存在用户系统默认目录
    alias:别名
    keyalg:指定密钥的算法 (如 RSA DSA(如果不指定默认采用DSA))
    keystore:指定密钥库的名称(产生的各类信息将不在.keystore文件中)
    其他常用参数说明:
    storepass:证书库的访问密码(获取keystore信息所需的密码)
    keypass:指定证书的私钥(私钥的密码)
    list:显示密钥库中的证书信息
    v:显示密钥库中的证书详细信息
    export:将别名指定的证书导出到文件
    export格式:keytool -export -alias 需要导出的别名 -keystore 指定keystore -file 指定导出的证书位置及证书名称 -storepass 密码
    file:参数指定导出到文件的文件名
    delete:删除密钥库中某条
    keytool -delete -alias 指定需删除的别 -keystore 指定keystore -storepass 密码
    printcert:查看导出的证书信息
    keytool -printcert -file yushan.crt
    keypasswd:修改密钥库中指定条目口令
    import:将已签名数字证书导入密钥库
    keytool -import -alias 指定导入条目的别名 -keystore 指定keystore -file 需导入的证书

  2. 配置tomcat

    cd /usr/local/apache-tomcat-6.0.44

    vim conf/server.xml

    修改83-89行数据
    去掉注释,改成如下

    <Connector
    protocol="org.apache.coyote.http11.Http11Protocol"
    port="8443" maxThreads="200"
    scheme="https" secure="true" SSLEnabled="true"
    keystoreFile="/root/server.keystore" keystorePass="centos"
    clientAuth="false" sslProtocol="TLS"/>

    说明:
    keystoreFile:服务器端库文件位置
    keystorePass:证书库的访问密码

  3. 布署CAS服务端
    下载并解压解压cas-server-3.5.2-release.zip,将cas-server-3.5.2\modules\cas-server-webapp-3.5.2.war,拷贝到tomcat目录下webapp下,重命名为cas.war
    启动tomcat
    bin/startup.sh

  4. 打开浏览器,访问https://ljh2:8443/cas
    (ljh2为系统hosts文件中ip映射)
    192.168.8.88 ljh1
    192.168.8.98 ljh2
    192.168.8.108 ljh3
    如能正常显示登录页面,表示服务端配置成功,虽然 CAS Server 已经部署成功,但这只是一个缺省的实现,默认用户名与密码一样则登录成功。在实际使用的时候,还需要根据实际概况做扩展和定制,最主要的是扩展认证 (Authentication) 接口和 CAS Server 的界面

服务端扩展认证接口

CAS Server 负责完成对用户的认证工作,它会处理登录时的用户凭证 (Credentials) 信息,用户名/密码对是最常见的凭证信息。CAS Server 可能需要到数据库检索一条用户帐号信息,也可能在 XML 文件中检索用户名/密码,还可能通过 LDAP Server 获取等,在这种情况下,CAS 提供了一种灵活但统一的接口和实现分离的方式,实际使用中 CAS 采用哪种方式认证是与 CAS 的基本协议分离开的,用户可以根据认证的接口去定制和扩展。

扩展 AuthenticationHandler


public interface AuthenticationHandler {
    //返回 boolean 类型的值,true 表示验证通过,false 表示验证失败
    boolean authenticate(Credentials credentials) throws AuthenticationException;
    //supports ()方法用于检查所给的包含认证信息的Credentials 是否受当前 AuthenticationHandler 支持
    boolean supports(Credentials credentials);
}

CAS3中还提供了对AuthenticationHandler 接口的一些抽象实现,比如,可能需要在执行authenticate() 方法前后执行某些其他操作,那么可以让自己的认证类扩展自抽象类:AbstractPreAndPostProcessingAuthenticationHandler


public abstract class AbstractPreAndPostProcessingAuthenticationHandler 
                                       implements AuthenticateHandler{
    protected Log log = LogFactory.getLog(this.getClass());
    protected boolean preAuthenticate(final Credentials credentials) {
        return true;
    }
    protected boolean postAuthenticate(final Credentials credentials,
    final boolean authenticated) {
        return authenticated;
    }
    public final boolean authenticate(final Credentials credentials)
        throws AuthenticationException {
        if (!preAuthenticate(credentials)) {
            return false;
        }
    final boolean authenticated = doAuthentication(credentials);
    return postAuthenticate(credentials, authenticated);
    }
    protected abstract boolean doAuthentication(final Credentials credentials) 
    throws AuthenticationException;
}

AbstractPreAndPostProcessingAuthenticationHandler 类新定义了 preAuthenticate() 方法和 postAuthenticate() 方法,而实际的认证工作交由 doAuthentication() 方法来执行。因此,如果需要在认证前后执行一些额外的操作,可以分别扩展 preAuthenticate()和 ppstAuthenticate() 方法,而 doAuthentication() 取代 authenticate() 成为了子类必须要实现的方法
由于实际运用中,最常用的是用户名和密码方式的认证,CAS3 提供了针对该方式的实现
AbstractUsernamePasswordAuthenticationHandler 

public abstract class AbstractUsernamePasswordAuthenticationHandler extends 
AbstractPreAndPostProcessingAuthenticationHandler{
protected final boolean doAuthentication(final Credentials credentials)
throws AuthenticationException {
return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials);
}
protected abstract boolean authenticateUsernamePasswordInternal(
final UsernamePasswordCredentials credentials) throws AuthenticationException; 
protected final PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
}

基于用户名密码的认证方式可直接扩展自 AbstractUsernamePasswordAuthenticationHandler,验证用户名密码的具体操作通过实现 authenticateUsernamePasswordInternal() 方法达到,另外,通常情况下密码会是加密过的,setPasswordEncoder() 方法就是用于指定适当的加密器。

doAuthentication() 方法的参数是 Credentials 类型,这是包含用户认证信息的一个接口,对于用户名密码类型的认证信息,可以直接使用 UsernamePasswordCredentials,如果需要扩展其他类型的认证信息,需要实现Credentials接口,并且实现相应的 CredentialsToPrincipalResolver 接口,其具体方法可以借鉴 UsernamePasswordCredentials 和 UsernamePasswordCredentialsToPrincipalResolver。

JDBC 认证方法

用户的认证信息通常保存在数据库中,前面解压后的cas-server-3.5.2的modules 目录下可以找到包 cas-server-cas-server-support-jdbc-3.5.2.jar,其提供了通过 JDBC 连接数据库进行验证的缺省实现,基于该包的支持,我们只需要做一些配置工作即可实现 JDBC 认证

JDBC 认证方法支持多种数据库

  1. 配置 DataStore

    打开文件 %CATALINA_HOME%/webapps/cas/WEB-INF/deployerConfigContext.xml,添加一个新的 bean 标签
    <bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
    </property>
    <property name="url">
    <value>jdbc:mysql://ljh:3306/cas</value>
    </property>
    <property name="username">
    <value>root</value>
    </property>
    <property name="password">
    <value>root</value>
    </property>
    </bean>

  2. 配置 AuthenticationHandler

    cas-server-support-jdbc-3.5.2.jar提供了 3 个基于 JDBC 的 AuthenticationHandler,分别为 BindModeSearchDatabaseAuthenticationHandler, QueryDatabaseAuthenticationHandler, SearchModeSearchDatabaseAuthenticationHandler。其中 BindModeSearchDatabaseAuthenticationHandler 是用所给的用户名和密码去建立数据库连接,根据连接建立是否成功来判断验证成功与否;
    QueryDatabaseAuthenticationHandler 通过配置一个 SQL 语句查出密码,与所给密码匹配;SearchModeSearchDatabaseAuthenticationHandler 通过配置存放用户验证信息的表、用户名字段和密码字段,构造查询语句来验证。
    使用哪个 AuthenticationHandler,需要在 deployerConfigContext.xml 中设置,默认情况下,CAS 使用一个简单的 username=password 的 AuthenticationHandler,在文件中可以找到如下一行:<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePassword
    AuthenticationHandler" />
    ,我们可以将其注释掉,换成我们希望的一个 AuthenticationHandler,比如,使用QueryDatabaseAuthenticationHandler
    <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
    <property name="dataSource" ref="casDataSource" />
    <property name="sql" 
    value="select password from user where username = ? " />
    </bean>

    另外,由于存放在数据库中的密码通常是加密过的,所以 AuthenticationHandler 在匹配时需要知道使用的加密方法,在 deployerConfigContext.xml 文件中我们可以为具体的 AuthenticationHandler 类配置一个 property,指定加密器类.

  3. 部署依赖包
    在以上配置完成以后,需要拷贝几个依赖的包到 cas 应用lib下,包括:
    cas-server-support-jdbc-3.1.1.jar,数据库驱动包,DataStore与依赖包

  4. 扩展 CAS Server 界面
    在部署 CAS 之前,我们可能需要定制一套新的 CAS Server 页面,添加一些个性化的内容。最简单的方法就是拷贝一份 default 文件到“cas/WEB-INF/view/jsp ”目录下,比如命名为 new,接下来是实现和修改必要的页面,有 4 个页面是必须的:
    casConfirmView.jsp: 当用户选择了“ warn ”时会看到的确认界面
    casGenericSuccess.jsp: 在用户成功通过认证而没有目的Service时会看到的界面
    casLoginView.jsp: 当需要用户提供认证信息时会出现的界面
    casLogoutView.jsp: 当用户结束 CAS 单点登录系统会话时出现的界面

    页面定制完过后,还需要做一些配置从而让 CAS 找到新的页面。
    拷贝“cas/WEB-INF/classes/default_views.properties”,重命名为“ cas/WEB-INF/classes/new_views.properties ”,并修改其中所有的值到相应新页面。最后是更新“cas/WEB-INF/cas-servlet.xml ”文件中的 viewResolver
    修改cas/WEB-INF/cas.properties文件
    将cas.viewResolver.basename=default_views改为cas.viewResolver.basename=new_views

  5. 扩展Cas服务端登录成功返回更多内容
    默认cas服务端在返回给客户端用户信息时,默认只返回用户名,CAS使用一个credentialsToPrincipalResolvers将credentials转成Principal对象,此对象只有一个实现类SimplePrincipal,SimplePrincipal的构造方法接收两个参数,一个是用户的id,一个为用户的其他属性。用户的ID默认为用户登录时使用的用户名。
    public SimplePrincipal(final String id, final Map<String, Object> attributes)
    为了给客户端返回更多的属性,我们必须要给Principal的构造方法传递第二个参数。
    public class MyCredentialsToPrincipalResolver implements CredentialsToPrincipalResolver {
    private DataSource dataSource;//查询数据库用
    public Principal resolvePrincipal(Credentials credentials) {
    UsernamePasswordCredentials up = (UsernamePasswordCredentials) credentials;
    String name = up.getUsername();
    String pwd = up.getPassword();
    //User user = "";查询数据库获取更多信息
    Map<String,Object> attrs = new HashMap<String,Object>();
    attrs.put("username",name);
    attrs.put("pwd",pwd);
    Principal p = new SimplePrincipal(id,attrs);//封装成包含id的Principal对象
    return p;
    }
    }

    服务器验证成功以后,是通过xml形式将结果传递给客户端的,xml的生成由casServiceValidationSuccess.jsp文件负责
    <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
            <cas:authenticationSuccess>
               <cas:user>U001</cas:user>
               <cas:attributes>
                  <cas:pwd>1234</cas:pwd>
                  <cas:username>Jack</cas:username>
               </cas:attributes>
        </cas:authenticationSuccess>
    </cas:serviceResponse>

部署客户端应用

  1. 导出服务端证书

    命令: keytool -export -alias tomcat-server -storepass centos -file server.crt -keystore server.keystore

    参数说明:
    export:将别名指定的证书导出到文件
    file:导出到文件的文件名 
    keypass:证书库的密私口令(centos)
    keystore:指定密钥库文件名称
    生成文件 中的证书

  2. 将服务端证书导入的客户端JDK的证书信任库中

    命令:keytool -import -trustcacerts -alias tomcat-server -file server.crt -keystore /usr/local/jdk1.7.0_79/jre/lib/security/cacerts
    输入密钥库口令:changeit
    是否信任此证书? [否]: y

    说明:changeit为java的默认密私,导入成功可以看到证书的详细信息

  3. 配置客户端web.xml

    <context-param>
    <param-name>serverName</param-name>
    <param-value>http://ljh:8080</param-value>
    </context-param>

    <filter>
    <filter-name>CAS Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://ljh2:8443/cas/login</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>CAS Filter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
    <filter-name>casTicketValidationFilter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>https://ljh2:8443/cas/</param-value>
    </init-param>
    <init-param> 
    <param-name>useSession</param-name> 
    <param-value>true</param-value> 
    </init-param> 
    <init-param> 
    <param-name>redirectAfterValidation</param-name> 
    <param-value>true</param-value> 
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>casTicketValidationFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
    <filter-name>casHttpServletRequestWrapperFilter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>casHttpServletRequestWrapperFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
    <filter-name>casAssertionThreadLocalFilter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>casAssertionThreadLocalFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

AuthenticationFilter

用来拦截所有的请求,用以判断用户是否需要通过Cas Server进行认证,
如果需要则将跳转到Cas Server的登录页面。如果不需要进行登录认证,则请求会继续往下执行。
AuthenticationFilter有两个用户必须指定的参数, 
casServerLoginUrl:指定Cas Server登录地址, 
serverName或service:指定认证成功后需要跳转地址
service和serverName只需要指定一个就可以了。 
当两者都指定了,参数service将具有更高的优先级,即将以service指定的参数值为准。 
service和serverName的区别在于service指定的是一个确定的URL,认证成功后就会确切的跳转到service指定的URL; 而serverName则是用来指定主机名,其格式为{protocol}:{hostName}:{port},如:https://localhost:8443,
当指定的是serverName时AuthenticationFilter将会把它附加上当前请求的URI,以及对应的查询参数来构造一个确定的URL 。如指定serverName为“http://localhost”,
而当前请求的URI为“/app”,查询参数为“a=b&b=c”,则对应认证成功后的跳转地址将为“http://localhost/app?a=b&b=c” 
除了上述必须指定的参数外,AuthenticationFilter还可以指定如下可选参数: 
l renew:当指定renew为true时,在请Cas Server时将带上参数“renew=true”,默认为false。 l gateway:指定gateway为true时,在请求Cas 
Server时将带上参数“gateway=true”,默认为false。 l artifactParameterName:指定ticket对应的请求参数名称,默认为ticket。 
l serviceParameterName:指定service对应的请求参数名称,默认为service

TicketValidationFilter

在请求通过AuthenticationFilter的认证之后,如果请求中携带了参数ticket则将会由TicketValidationFilter来对携带的ticket进行校验。
TicketValidationFilter只是对验证ticket的这一类Filter的统称,其并不对应Cas Client中的一个具体类型。
Cas Client中有多种验证ticket的Filter,都继承自AbstractTicketValidationFilter,它们的验证逻辑都是一致的,
都由AbstractTicketValidationFilter实现,所不同的是使用的TicketValidator不一样 
必须指定的参数: 
l casServerUrlPrefix:用来指定Cas Server对应URL地址的前缀,如上面示例的“https://ljh2:8443/cas”。 
l serverName或service:语义跟前面介绍的一致 可选参数: 
l redirectAfterValidation :表示是否验证通过后重新跳转到该URL,但是不带参数ticket,默认为true。 
l useSession :在验证ticket成功后会生成一个Assertion对象,
如果useSession为true,则会将该对象存放到Session中。
如果为false,则要求每次请求都需要携带ticket进行验证,显然useSession为false跟redirectAfterValidation为true是冲突的。默认为true。 
l exceptionOnValidationFailure :表示ticket验证失败后是否需要抛出异常,默认为true。 
l renew:当值为true时将发送“renew=true”到Cas Server,默认为false。

HttpServletRequestWrapperFilter

HttpServletRequestWrapperFilter用于将每一个请求对应的HttpServletRequest封装为其内部定义的CasHttpServletRequestWrapper,该封装类将利用之前保存在Session或request中的Assertion对象重写HttpServletRequest的getUserPrincipal()、getRemoteUser()和isUserInRole()方法。这样在我们的应用中就可以非常方便的从HttpServletRequest中获取到用户的相关信息

AssertionThreadLocalFilter

AssertionThreadLocalFilter是为了方便用户在应用的其它地方获取Assertion对象,其会将当前的Assertion对象存放到当前的线程变量中,那么以后用户在程序的任何地方都可以从线程变量中获取当前Assertion,无需再从Session或request中进行解析。该线程变量是由AssertionHolder持有的,我们在获取当前的Assertion时也只需要通过AssertionHolder的getAssertion()方法获取即可,如: 
Assertion assertion = AssertionHolder.getAssertion();

传递登录用户名

CAS 在登录成功过后,会给浏览器回传 Cookie,设置新的到的 Service Ticket。但客户端应用拥有各自的 Session,我们要怎么在各个应用中获取当前登录用户的用户名呢?CAS Client 的 Filter 已经做好了处理,在登录成功后,
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(); 
Map attributes = principal.getAttributes();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值