Cas 5.2.x版本使用 —— Restful API 方式验证Ticket(十七)

Rest API 原理

首先客户端提交用户名、密码、及Service三个参数, 如果验证成功便返回用户的TGT(Ticket Granting Ticket)至客户端, 然后客户端再根据 TGT 获取用户的 ST(Service Ticket)来进行验证登录。 故名思意,TGT是用于生成一个新的Ticket(ST)的Ticket,而ST则是提供给客户端用于登录的Ticket,两者最大的区别在于, TGT是用户名密码验证成功之后所生成的Ticket,并且会保存在Server中及Cookie中,而ST则必须是是根据TGT来生成,主要用于登录,并且当登录成功之后 ST 则会失效。通过访问服务地址 http://xxx?ticket=xx

一个典型的调用流程

以下是某个应用系统使用cas_service包接口的典型流程,通过rest访问流程,:

1、某用户登录应用A,因为是首次登录,需提供用户名、密码;
2、应用A根据用户名、密码,调用getTicketGrantingTicket接口获取TGT;
3、TGT多次使用,需保存在session或其它存储对象中;
4、应用A使用TGT,调用getServiceTicket接口获取am服务的ST;
5、应用A可使用刚获取的ST,作为参数访问am服务;
6、ST因有效期短暂且使用次数有限制,一般是一次性使用,不必保存;
7、用户欲访问应用B的bn服务,先从session或其它存储对象中查找到TGT;
8、应用A(或应用B)TGT,调用getServiceTicket接口获取bn服务的ST;
9、应用B接收ST,调用verifySeviceTicket接口,返回不为null则该ST有效;
10、验证通过后,应用B使用该ST访问bn服务;
11、应用B可调用接口getCasUserName和getCasAttributes,获取登录用户及相关属性;
12、欲根据ST查找当前登录用户,调用getUsernameSeviceTicket接口,返回值即是;
13、用户从某应用注销时,需调用deleteTicketGrantingTicket接口从Cas Server删除TGT。

cas可供调用的接口

可供调用的接口如下:

1)  获取TGT

String getTicketGrantingTicket(String Server, String username, String password);

  •  参数server为CAS Server的访问URL;

  •  参数username为登录用户名;

  •  参数password为验证用的密码;

返回:验证通过则返回TGT的值,否则抛出异常;

示例:

String tgt = casService.getTicketGrantingTicket("https://cas.hisign.com.cn:8443/cas", "casuser", "Mellon");

2)  根据TGT获取ST

String getServiceTicket(String Server, String ticketGrantingTicket, String service);

  •  参数server为CAS Server的访问URL;

  •  参数ticketGrantingTicket为已获得的TGT;

  •  参数service为欲访问的service的URL;

 返回:验证通过则返回ST的值,否则抛出异常;

 示例:

String st = casService.getTicketGrantingTicket("https://cas.hisign.com.cn:8443/cas",  "TGT-2-6eTFeygWirXfgbQWdOitzwAFcuIJyYfmIRNeMELaqKiLSw9zOY-cas01.example.org", "https://app.hisign.com.cn:8443/app1");

3) 判别ST是否有效

String verifySeviceTicket(String server, String serviceTicket, String service);

  •  参数server为CAS Server的访问URL;

  •  参数serviceTicket为已获得的ST;

  •  参数service为欲访问的service的URL;

 返回:ST有效返回登录用户名,无效返回null,若出错抛出异常;

 示例:

boolean String = casService.verifyServiceTicket("https://cas.hisign.com.cn:8443/cas", "ST-2-5kEeqQuPsnB1b4UyUHFW-cas01.example.org", "https://app.hisign.com.cn:8443/app1");

4) 删除TGT(相当于在CAS Server端注销)

boolean deleteTicketGrantingTicket(String Server, String ticketGrantingTicket);

  •  参数server为CAS Server的访问URL;

  •  参数ticketGrantingTicket为已获得的TGT;

 返回:成功返回true,否则抛出异常;

 示例:

boolean bool = casService.deleteTicketGrantingTicket("https://cas.hisign.com.cn:8443/cas",  "TGT-2-6eTFeygWirXfgbQWdOitzwAFcuIJyYfmIRNeMELaqKiLSw9zOY-cas01.example.org");

注意事项:参数server必须真实有效,可从配置文件获取;而参数service可以是虚构的、符合规范的格式。

PostMan模拟接口演示

1、获取TGT

WX20180423-111517@2x.png

2、获取ST

WX20180423-111724@2x.png

3、验证ST

WX20180423-111839@2x.png

4、查看ST状态

WX20180423-134222@2x.png

5、登出,删除TGT

WX20180423-134257@2x.png

java流程模拟

pom.xml

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.10.0</version>
</dependency>

CasServerUtil.java

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

/**
 * 首先客户端提交用户名、密码、及Service三个参数,
 * 如果验证成功便返回用户的TGT(Ticket Granting Ticket)至客户端,
 * 然后客户端再根据 TGT 获取用户的 ST(Service Ticket)来进行验证登录。
 * 故名思意,TGT是用于生成一个新的Ticket(ST)的Ticket,
 * 而ST则是提供给客户端用于登录的Ticket,两者最大的区别在于,
 * TGT是用户名密码验证成功之后所生成的Ticket,并且会保存在Server中及Cookie中,
 * 而ST则必须是是根据TGT来生成,主要用于登录,并且当登录成功之后 ST 则会失效。
 */
public class CasServerUtil {

    // 登录服务器地址
    private static final String CAS_SERVER_PATH = "https://cas.server.com:8443";

    // 登录地址的token
    private static final String GET_TOKEN_URL = CAS_SERVER_PATH + "/v1/tickets";


    public static void main(String[] args) {

        try {
            String tgt = getTGT("tingfeng", "tingfeng");
            System.out.println("TGT:" + tgt);

            String service = "http://app1.com:8181/fire/users.html";
            String st = getST(tgt, service);
            System.out.println("ST:" + st);

            System.out.println(service + "?ticket=" + st);

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

    /**
     * 获取TGT
     */
    public static String getTGT(String username, String password) {
        try{
            CookieStore httpCookieStore = new BasicCookieStore();
//        CloseableHttpClient client = createHttpClientWithNoSsl(httpCookieStore);

            CloseableHttpClient client = HttpClients.createDefault();

            HttpPost httpPost = new HttpPost(GET_TOKEN_URL);
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("username", username));
            params.add(new BasicNameValuePair("password", password));
            httpPost.setEntity(new UrlEncodedFormEntity(params));
            HttpResponse response = client.execute(httpPost);

//        System.out.println("\n 获取TGT,Header响应");
//        Header[] allHeaders = response.getAllHeaders();
//        for (int i = 0; i < allHeaders.length; i++) {
//            System.out.println("Key:" + allHeaders[i].getName() + ",Value:" + allHeaders[i].getValue() + ",Elements:" + Arrays.toString(allHeaders[i].getElements()));
//        }

            Header headerLocation = response.getFirstHeader("Location");
            String location = headerLocation == null ? null : headerLocation.getValue();

            System.out.println("Location:" + location);

            if (location != null) {
                return location.substring(location.lastIndexOf("/") + 1);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
//
        return null;
    }


    /**
     * 获取ST
     */
    public static String getST(String TGT, String service){
        try {

//        CookieStore httpCookieStore = new BasicCookieStore();
//        CloseableHttpClient client = createHttpClientWithNoSsl(httpCookieStore);

            CloseableHttpClient client = HttpClients.createDefault();


            // service 需要encoder编码
            // service = URLEncoder.encode(service, "utf-8");

            HttpPost httpPost = new HttpPost(GET_TOKEN_URL + "/" + TGT);
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("service", service));
            httpPost.setEntity(new UrlEncodedFormEntity(params));
            HttpResponse response = client.execute(httpPost);

//        System.out.println("\n 获取ST,Header响应");
//        Header[] allHeaders = response.getAllHeaders();
//        for (int i = 0; i < allHeaders.length; i++) {
//            System.out.println("Key:" + allHeaders[i].getName() + ",Value:" + allHeaders[i].getValue() + ",Elements:" + Arrays.toString(allHeaders[i].getElements()));
//        }
//
//
//        List<Cookie> cookies = httpCookieStore.getCookies();
//        System.out.println("Cookie.size:" + cookies.size());
//        for (Cookie cookie : cookies) {
//            System.out.println("Cookie: " + new Gson().toJson(cookie));
//        }

            String st = readResponse(response);
            return st == null ? null : (st == "" ? null : st);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 读取 response body 内容为字符串
     *
     * @param response
     * @return
     * @throws IOException
     */
    private static String readResponse(HttpResponse response) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        String result = new String();
        String line;
        while ((line = in.readLine()) != null) {
            result += line;
        }
        return result;
    }


    /**
     * 创建模拟客户端(针对 https 客户端禁用 SSL 验证)
     *
     * @param cookieStore 缓存的 Cookies 信息
     * @return
     * @throws Exception
     */
    private static CloseableHttpClient createHttpClientWithNoSsl(CookieStore cookieStore) throws Exception {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        // don't check
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        // don't check
                    }
                }
        };

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, trustAllCerts, null);
        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
        return HttpClients.custom()
                .setSSLSocketFactory(sslSocketFactory)
                .setDefaultCookieStore(cookieStore == null ? new BasicCookieStore() : cookieStore)
                .build();
    }

}

输出

Location:https://cas.server.com:8443/cas/v1/tickets/TGT-25-K5cR4grRhR7-LtYxAWwoKcUJG6SlV4UaAiQ1vLzQwIgsJCNG0HekZYRCgVouPfaC7eA-liurenkuideMacBook-Pro
TGT:TGT-25-K5cR4grRhR7-LtYxAWwoKcUJG6SlV4UaAiQ1vLzQwIgsJCNG0HekZYRCgVouPfaC7eA-liurenkuideMacBook-Pro
ST:ST-27-dH8oYI0H9aMMP8WMdvtG-hnRIlg-liurenkuideMacBook-Pro
http://app1.com:8181/fire/users.html?ticket=ST-27-dH8oYI0H9aMMP8WMdvtG-hnRIlg-liurenkuideMacBook-Pro

将最终输出的结果:http://app1.com:8181/fire/users.html?ticket=ST-27-dH8oYI0H9aMMP8WMdvtG-hnRIlg-liurenkuideMacBook-Pro  直接在浏览器访问,就会发现直接通过。

你可以在自己的Controller中加以更改。将用户名和密码方式实现参数传递。

TGT与ST的时效设置

TGT和ST有时效和限制,默认是TGT有2小时时效、保留8小时,而ST是 10秒时效且只能使用一次。

如需改变ST的时效和次数限制,可通过修改CAS Server的配置文件cas.propertities中的st.numberOfUses和st.timeToKillInSeconds项加以更改。

如:

# Service Ticket Timeout
st.timeToKillInSeconds=10
st.numberOfUses=1

参考链接

https://apereo.github.io/cas/5.2.x/protocol/REST-Protocol.html

http://makaidong.com/wggj/0/39620_9121845.html


未经允许请勿转载:程序喵 » Cas 5.2.x版本使用 —— Restful API 方式验证Ticket(十七)

点  赞 (12) 打  赏
分享到: