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