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
2、获取ST
3、验证ST
4、查看ST状态
5、登出,删除TGT
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