美文网首页
CAS笔记: 部署与测试

CAS笔记: 部署与测试

作者: 200cc | 来源:发表于2015-06-01 09:47 被阅读3040次

    CAS 简介

    cas是YALE大学发起的一个开源项目, 旨在为web应用系统提供一种可靠的单点登录方法.

    它分为server和client端, server端负责对用户的认证工作, client端则负责处理对客户端受保护的资源的访问请求.

    CAS的原理,如图:

    cas-01.jpg

    CAS 名词

    • Service Ticket: 简称ST, ST是CAS为用户签发的访问某一service的票据。用户访问service时,service发现用户没有ST,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求,如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。

    • Ticket granting ticket: 简称TGT. 是cas服务器为用户签发的登录票据.拥有了TGT,用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。用户在CAS认证成功后,CAS生成cookie,写入浏览器,同时生成一个TGT对象,放入自己的缓存,TGT对象的ID就是cookie的值。当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值为key查询缓存中有无TGT ,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。

    • Ticket granting cookie: 简称TGC. 这是一个cookie, 是cas服务器放到用户浏览器中用以标识用户身份的cookie.

    CAS REST服务部署

    stackoverflow参考资料

    部署前的准备

    • 服务端创建证书
    keytool -genkey -alias SomeName -keyalg RSA -keystore d:/your/dir/target.keystore
    

    接着根据提示输入相关信息.在最后,提示输入密码时, 务必记住你输入的密码.

    • 服务端导出证书
    keytool -export -file d:/your/dir/target.crt -alias SomeName -keystore d:/your/dir/target.keystore
    

    导出时, 会提示你输入刚才创建keystore时的密码.
    导出完成后, 生成的target.crt就可以分发给客户端的jdk使用了.

    • 客户端导入证书
    keytool -import -keystore %JAVA_HOME%/jre/lib/security/cacerts -file d:/your/dir/target.crt -alias SomeName
    

    提示输入密码. 如果出现keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect错误, 则使用密码changeit.

    • 在服务端tomcat服务器上应用证书
    <!-- 务必注意大小写 -->
    <connector port="8443" protocol="HTTP/1.1" sslenabled="true"
        maxthreads="150" scheme="https" secure="true" 
        clientauth="false" sslprotocol="TLS" 
        keystoreFile="D:/your/dir/target.keystore" 
        keystorePass="yourPassWord">
    </connector>
    
    • 启动tomcat服务器, 验证SSL是否启用

      访问地址https://localhost:8443/

    生成支持rest的cas.war

    新建目录, 编写pom.xml, 使用命令mvn clean package生成cas.war

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
      <parent>
        <groupId>org.jasig.cas</groupId>
        <artifactId>cas-server</artifactId>
        <version>3.4.12</version>
      </parent>
    
      <modelVersion>4.0.0</modelVersion>
      <groupId>h.usm.my</groupId>
      <artifactId>cas</artifactId>
      <packaging>war</packaging>
      <version>1.0</version>
      <name>HUSM CAS Web Application</name>
    
      <properties>
        <cas.version>3.4.12</cas.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.jasig.cas</groupId>
          <artifactId>cas-server-webapp</artifactId>
          <version>${cas.version}</version>
          <type>war</type>
          <scope>runtime</scope>
        </dependency>
    
        <dependency>
         <groupId>org.jasig.cas</groupId>
         <artifactId>cas-server-support-jdbc</artifactId>
         <version>${cas.version}</version>
       </dependency>
    
        <dependency>
          <groupId>org.jasig.cas</groupId>
          <artifactId>cas-server-integration-restlet</artifactId>
          <version>${cas.version}</version>
          <type>jar</type>
          <exclusions>
            <exclusion>
              <groupId>org.springframework</groupId>
              <artifactId>spring-web</artifactId>
            </exclusion>
          </exclusions>
        </dependency>
    
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.187</version>
        </dependency>
    
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-core</artifactId>
          <version>${hibernate.core.version}</version>
          <type>jar</type>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-entitymanager</artifactId>
          <version>3.6.0.Final</version>
        </dependency>
      </dependencies>
    
      <repositories>
        <repository>
          <id>ja-sig</id>
          <url>http://oss.sonatype.org/content/repositories/releases</url>
        </repository>
      </repositories>
    
      <build>
            <plugins>
                <plugin>
                     <artifactId>maven-war-plugin</artifactId>
                                 <configuration>
                                     <warName>cas</warName>
                                 </configuration>
                            </plugin>
            </plugins>
        </build>
    </project>
    

    修改cas.war的web.xml, 填写Rest Servlet

    <!--add a servlet-->
        <servlet>
            <servlet-name>restlet</servlet-name>
            <servlet-class>com.noelios.restlet.ext.spring.RestletFrameworkServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>restlet</servlet-name>
            <url-pattern>/rest/*</url-pattern>
        </servlet-mapping>
    

    修改cas.war的deployerConfigContext.xml, 修改用户名密码的验证方式

    • 注释掉默认的SimpleTestUsernamePasswordAuthenticationHandler

    • 添加新的AuthentitcationHandler

    <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
        <property name="dataSource" ref="dataSource"></property>
        <property name="sql" value="select password from t_admin_user where login_name=?"></property>
        <property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
    </bean>
    

    这里使用了数据库来存储用户的帐号与密码.
    验证时使用sql进行查询,并对查询获得的password字段值,与使用MD5PasswordEncoder进行加密后的输入密码, 进行比对验证.

    相关的dataSource与encoder配置如下:

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <property name="driverClassName" value="com.mysql.jdbc.Driver" />
       <property name="url" value="jdbc:mysql:///wsriademo" />
       <property name="username" value="sa" />
       <property name="password" value="root" />
    </bean>
     
    <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
        <constructor-arg index="0">
            <value>MD5</value>
        </constructor-arg>
    </bean>
    

    通过org.jasig.cas.authentication.handler.PasswordEncoder接口可实现自定义加密类.
    记得添加相应的数据库驱动jar包到lib目录下.

    验证

    访问 https://localhost:8443/cas, 输入账密进行网页验证.

    CAS Rest的java验证代码

    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLConnection;
    import java.net.URLEncoder;
    
    import javax.net.ssl.HttpsURLConnection;
    
    public class TestCasRest {
        
        /**
         * resolve exception:
         *      java.security.cert.CertificateException: No name matching localhost found
         */
        static {
            //for localhost testing only
            javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
            new javax.net.ssl.HostnameVerifier(){
     
                public boolean verify(String hostname,
                        javax.net.ssl.SSLSession sslSession) {
                    if (hostname.equals("localhost")) {
                        return true;
                    }
                    return false;
                }
            });
        }
    
        public static void main(String... args) throws Exception {
            String username = "alpha";
            String password = "123";
            validateFromCAS(username, password);
        }
    
        public static boolean validateFromCAS(String username, String password)
                throws Exception {
    
            String url = "https://localhost:8443/cas/rest/tickets";
            try {
                HttpsURLConnection hsu = (HttpsURLConnection) openConn(url);
                String s = URLEncoder.encode("username", "UTF-8") + "="
                        + URLEncoder.encode(username, "UTF-8");
                s += "&" + URLEncoder.encode("password", "UTF-8") + "="
                        + URLEncoder.encode(password, "UTF-8");
    
                System.out.println(s);
                OutputStreamWriter out = new OutputStreamWriter(
                        hsu.getOutputStream());
                BufferedWriter bwr = new BufferedWriter(out);
                bwr.write(s);
                bwr.flush();
                bwr.close();
                out.close();
    
                String tgt = hsu.getHeaderField("location");
                System.out.println("ResponseCode: " + hsu.getResponseCode());
                if (tgt != null && hsu.getResponseCode() == 201) {
                    System.out.println(tgt);
    
                    System.out.println("==> TGT is : "
                            + tgt.substring(tgt.lastIndexOf("/") + 1));
                    tgt = tgt.substring(tgt.lastIndexOf("/") + 1);
                    bwr.close();
                    closeConn(hsu);
    
                    String serviceURL = "http://localhost:8080/CasClient";
                    String encodedServiceURL = URLEncoder
                            .encode("service", "utf-8")
                            + "="
                            + URLEncoder.encode(serviceURL, "utf-8");
                    System.out.println("Service url is : " + encodedServiceURL);
    
                    String myURL = url + "/" + tgt;
                    System.out.println(myURL);
                    hsu = (HttpsURLConnection) openConn(myURL);
                    out = new OutputStreamWriter(hsu.getOutputStream());
                    bwr = new BufferedWriter(out);
                    bwr.write(encodedServiceURL);
                    bwr.flush();
                    bwr.close();
                    out.close();
    
                    System.out.println("Response code is:  "
                            + hsu.getResponseCode());
    
                    BufferedReader isr = new BufferedReader(new InputStreamReader(
                            hsu.getInputStream()));
                    String line;
                    System.out.println(hsu.getResponseCode());
                    while ((line = isr.readLine()) != null) {
                        System.out.println("==> ST is : " + line);
                    }
                    isr.close();
                    hsu.disconnect();
                    return true;
    
                } else {
                    return false;
                }
    
            } catch (MalformedURLException mue) {
                mue.printStackTrace();
                throw mue;
    
            } catch (IOException ioe) {
                ioe.printStackTrace();
                throw ioe;
            }
    
        }
    
        static URLConnection openConn(String urlk) throws MalformedURLException,
                IOException {
    
            URL url = new URL(urlk);
            HttpsURLConnection hsu = (HttpsURLConnection) url.openConnection();
            hsu.setDoInput(true);
            hsu.setDoOutput(true);
            hsu.setRequestMethod("POST");
            return hsu;
    
        }
    
        static void closeConn(HttpsURLConnection c) {
            c.disconnect();
        }
    
    }
    

    Cas client端(非REST请求方式)的配置

    在client端工程添加cas-client-core.jar包及相关依赖

    <dependency>
        <groupid>org.jasig.cas.client</groupid>
        <artifactid>cas-client-core</artifactid>
        <version>3.1.12</version>
    </dependency>
    

    修改client端工程的web.xml, 添加cas的过滤器

    <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
    <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>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
     
    <!-- 该过滤器负责用户的认证工作,必须启用它 -->
    <filter>
        <filter-name>CASFilter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>https://sso.wsria.com:8443/cas/login</param-value>
        </init-param>
        <init-param>
            <!--这里的server是服务端的IP-->
            <param-name>serverName</param-name>
            <param-value>http://localhost:10000</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CASFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
     
    <!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
    <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>https://sso.wsria.com:8443/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://localhost:10000</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
     
    <!--
    该过滤器负责实现HttpServletRequest请求的包裹,
    比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
    -->
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>
    org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
     
    <!--
    该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
    比如AssertionHolder.getAssertion().getPrincipal().getName()。
    -->
    <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 Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
     
    <!-- 自动根据单点登录的结果设置本系统的用户信息 -->
    <filter>
        <display-name>AutoSetUserAdapterFilter</display-name>
        <filter-name>AutoSetUserAdapterFilter</filter-name>
        <filter-class>com.wsria.demo.filter.AutoSetUserAdapterFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AutoSetUserAdapterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- ======================== 单点登录结束 ======================== -->
    

    其中自定义的AutoSetUserAdapterFilter的代码如下

    package com.wsria.demo.filter;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    
    import org.jasig.cas.client.validation.Assertion;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import com.wsria.demo.entity.account.User;
    import com.wsria.demo.service.account.UserManager;
    import com.wsria.demo.util.UserUtil;
    
    
    /**
     * 自动根据单点登录系统的信息设置本系统的用户信息
     *
     * @author 咖啡兔
     * @site www.wsria.cn
     *
     */
    public class AutoSetUserAdapterFilter implements Filter {
            
            /**
             * Default constructor. 
             */
            public AutoSetUserAdapterFilter() {
            }
    
            /**
             * @see Filter#destroy()
             */
            public void destroy() {
            }
    
            /**
             * 过滤逻辑:首先判断单点登录的账户是否已经存在本系统中,
             * 如果不存在使用用户查询接口查询出用户对象并设置在Session中
             * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
             */
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                            ServletException {
                    HttpServletRequest httpRequest = (HttpServletRequest) request;
                    
                    // _const_cas_assertion_是CAS中存放登录用户名的session标志
                    Object object = httpRequest.getSession().getAttribute("_const_cas_assertion_");
                    
                    if (object != null) {
                            Assertion assertion = (Assertion) object;
                            String loginName = assertion.getPrincipal().getName();
                            User user = UserUtil.getCurrentUser(httpRequest.getSession());
                            
                            // 第一次登录系统
                            if (user == null) {
                                    WebApplicationContext wct = WebApplicationContextUtils.getWebApplicationContext(httpRequest
                                                    .getSession().getServletContext());
                                    UserManager userManager = (UserManager) wct.getBean("userManager");
                                    user = userManager.findUserByLoginName(loginName);
                                    // 保存用户信息到Session
                                    UserUtil.saveUserToSession(httpRequest.getSession(), user);
                            }
                            
                    }
                    chain.doFilter(request, response);
            }
    
            /**
             * @see Filter#init(FilterConfig)
             */
            public void init(FilterConfig fConfig) throws ServletException {
            }
    
    }
    

    附注

    单点退出

    访问https://localhost:8443/cas/logout即可.

    美化CAS服务器界面

    修改cas\WEB-INF\view\jsp\default\ui下相关的jsp文件

    在服务端不使用SSL协议

    • 修改%CATALINA_HOME%\conf\server.xml文件, 关闭Tomcat服务器的SSL端口
    <!-- 关闭SSL端口
    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
                   maxThreads="150" scheme="https" secure="true"  
                   clientAuth="false" sslProtocol="TLS"  
                   keystoreFile="E:/sso/keys/dcssokey" keystorePass="dcfs00"  
                   truststoreFile="D:/ProgramFiles/Java/jdk1.6.0_25/jre/lib/security/cacerts" />  
    -->
    
    • 修改服务端cas\WEB-INF\deployerConfigContext.xml文件
    <!-- 添加非安全协议配置 -->
    <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"  
        p:httpClient-ref="httpClient" 
        p:requireSecure="false" />  
    
    • 修改服务端的cas\WEB-INF\spring-configuration\ticketGrantingTicketCookieGennerator.xml文件
    <!-- 修改cookie非安全协议配置 -->
    <bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"  
            p:cookieSecure="false"  
            p:cookieMaxAge="600"  
            p:cookieName="CASTGC"  
            p:cookiePath="/cas" />  
    

    相关文章

      网友评论

          本文标题:CAS笔记: 部署与测试

          本文链接:https://www.haomeiwen.com/subject/zhcdqttx.html