SSO單點登錄—CAS統一身份認證


單點登錄SSO(Single Sign ON) 

如:在學校登錄了OA系統,再打開考試系統、教務系統,都會實現自動登錄。

統一身份認證CAS(Central Authentication Service)

CAS 是由耶魯大學發起的企業級開源項目,歷經20多年的完善,具有較高的穩定性、安全性。國內多數高校的SSO都基於CAS。
 
package com.zxz.sso.controller;

/**
 * CAS配置類
 * 業務單點登錄接口url : http://192.168.1.76/sso/myLogin.do
 * targetUrl(登錄成功后進入系統的頁面) : http://192.168.1.76/main.html
 * base64編碼targetUrl: aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw=
 * service(CAS服務器登錄需要的service參數) : http://192.168.1.76/sso/myLogin.do?targetUrl=base64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw=
 * loginUrl(拼接成的CAS服務登錄url) : http://localhost:8080/cas/login?service=http%3A%2F%2F192.168.1.76%2Fsso%2FmyLogin.do%3FtargetUrl%3Dbase64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw%3D
 * 驗證票據的地址(獲取CAS賬戶信息的地址) : http://localhost:8080/cas/serviceValidate?ticket=ST-11-N1w7Z-WjrjQDRFl5Y120MmgBZa0DESKTOP-KMSEFVL&service=http%3A%2F%2F192.168.1.76%2Fsso%2FmyLogin.do%3FtargetUrl%3Dbase64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw%3D
 */
public class CasConfig {
    // CAS根地址
    public static String CAS_BASE_PATH = "http://localhost:8080/cas/";
    // 業務系統登錄入口
    public static String MY_LOGIN_URI = "sso/myLogin.do";
    // CAS票據驗證地址
    public static String CAS_VALIDATE_URL = CAS_BASE_PATH + "serviceValidate";
    // CAS登錄地址
    public static String CAS_LOGIN_URL = CAS_BASE_PATH + "login";
    //登錄成功默認跳轉地址
    public static String DEF_TARGET_URI = "main.html";
    // 默認編碼字符串格式
    public static String UTF_8 = "UTF-8";
    // SESSION中判斷是否登錄的KEY
    public static String LOGIN_KEY = "isCasLogin";
    // 業務系統認證集成失敗提示頁
    public static String SSO_ERROR_URI = "error.html";
}

  

 
 1 package com.zxz.sso.controller;  2 
 3 import org.springframework.stereotype.Controller;  4 import org.springframework.web.bind.annotation.RequestMapping;  5 
 6 import javax.servlet.http.HttpServletRequest;  7 import javax.servlet.http.HttpServletResponse;  8 
 9 @Controller 10 @RequestMapping("/sso") 11 public class SSOController { 12 
13     @RequestMapping("/myLogin.do") 14     public void ssoLogin(HttpServletRequest request, HttpServletResponse response) throws Exception{ 15         CasUtil casUtil = new CasUtil(); 16         casUtil.login(request, response, new CasUtil.ClientSystem() { 17  @Override 18             public boolean doLogin(CasVO casVO) { 19                 // 獲取CAS服務賬戶信息
20                 String account = casVO.getAccount(); 21                 // TODO 根據CAS登錄業務系統
22 
23                 return true; 24  } 25  }); 26  } 27 }

 

 
 1 package com.zxz.sso.controller;  2 
 3 /**
 4  * CAS配置類  5  * 業務單點登錄接口url : http://192.168.1.76/sso/myLogin.do
 6  * targetUrl(登錄成功后進入系統的頁面) : http://192.168.1.76/main.html
 7  * base64編碼targetUrl: aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw=  8  * service(CAS服務器登錄需要的service參數) : http://192.168.1.76/sso/myLogin.do?targetUrl=base64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw=
 9  * loginUrl(拼接成的CAS服務登錄url) : http://localhost:8080/cas/login?service=http%3A%2F%2F192.168.1.76%2Fsso%2FmyLogin.do%3FtargetUrl%3Dbase64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw%3D 10  * 驗證票據的地址(獲取CAS賬戶信息的地址) : http://localhost:8080/cas/serviceValidate?ticket=ST-11-N1w7Z-WjrjQDRFl5Y120MmgBZa0DESKTOP-KMSEFVL&service=http%3A%2F%2F192.168.1.76%2Fsso%2FmyLogin.do%3FtargetUrl%3Dbase64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw%3D 11  */
12 public class CasConfig { 13     // CAS根地址
14     public static String CAS_BASE_PATH = "http://localhost:8080/cas/"; 15     // 業務系統登錄入口
16     public static String MY_LOGIN_URI = "sso/myLogin.do"; 17     // CAS票據驗證地址
18     public static String CAS_VALIDATE_URL = CAS_BASE_PATH + "serviceValidate"; 19     // CAS登錄地址
20     public static String CAS_LOGIN_URL = CAS_BASE_PATH + "login"; 21     //登錄成功默認跳轉地址
22     public static String DEF_TARGET_URI = "main.html"; 23     // 默認編碼字符串格式
24     public static String UTF_8 = "UTF-8"; 25     // SESSION中判斷是否登錄的KEY
26     public static String LOGIN_KEY = "isCasLogin"; 27     // 業務系統認證集成失敗提示頁
28     public static String SSO_ERROR_URI = "error.html"; 29 }

 

 
 1 package com.zxz.sso.controller;  2 
 3 import org.w3c.dom.Document;  4 import org.w3c.dom.Node;  5 import org.w3c.dom.NodeList;  6 import sun.misc.BASE64Decoder;  7 import sun.misc.BASE64Encoder;  8 
 9 import javax.net.ssl.*;  10 import javax.servlet.http.HttpServletRequest;  11 import javax.servlet.http.HttpServletResponse;  12 import javax.servlet.http.HttpSession;  13 import javax.xml.parsers.DocumentBuilder;  14 import javax.xml.parsers.DocumentBuilderFactory;  15 import java.io.*;  16 import java.lang.reflect.Field;  17 import java.net.HttpURLConnection;  18 import java.net.URL;  19 import java.net.URLConnection;  20 import java.net.URLEncoder;  21 import java.nio.charset.Charset;  22 import java.security.SecureRandom;  23 import java.security.cert.CertificateException;  24 import java.security.cert.X509Certificate;  25 
 26 
 27 /**
 28  * CAS服務器認證工具類  29  * 單點登錄只需要調用login(request, response, clientSystem);  30  */
 31 public class CasUtil {  32     /**
 33  * 單點登錄接口調用方法  34  * @param request  35  * @param response  36  * @throws IOException  37      */
 38     public void login(HttpServletRequest request, HttpServletResponse response, ClientSystem clientSystem) throws IOException {  39         // 1.首先驗證客戶是否登錄CAS服務器
 40         HttpSession session = request.getSession();  41         boolean isLogin = checkLogin(session);  42         String targetUrl = getTargetUrl(request);  43         if (isLogin) {  44             // 2.如果已經登錄了CAS服務器,則跳轉到 targetUrl
 45  response.sendRedirect(targetUrl);  46         } else {  47             // 3.如果沒有登錄CAS服務器,則去驗證Ticket
 48             boolean hasTicket = checkHasTicket(request);  49             if (hasTicket) {  50                 // 3.1如果有票據,則進行驗證
 51                 CasVO casVO = checkTicket(request);  52                 if (casVO.isLogin() && clientSystem.doLogin(casVO)){  53                     // 3.2給session中寫入登錄標識(用於checkLogin)
 54                     session.setAttribute(CasConfig.LOGIN_KEY, true);  55                     // 3.3登錄成功跳轉至業務系統url
 56  response.sendRedirect(targetUrl);  57                 } else {  58                     // cas賬戶信息異常,跳轉配置的錯誤頁面
 59                     String errorUrl = getErrorUrl(request);  60  response.sendRedirect(errorUrl);  61  }  62             } else {  63                 // 3.2如果ticket不存在
 64                 String loginUrl = getLoginUrl(request);  65                 System.err.println("loginUrl:"+loginUrl);  66  response.sendRedirect(loginUrl);  67  }  68  }  69  }  70 
 71 
 72 
 73     private CasVO checkTicket(HttpServletRequest request) throws IOException {  74         // 1.獲取票據驗證的url
 75         String serviceValidateUrl = getServiceValidateUrl(request);  76         System.out.println("驗證票據的地址:" + serviceValidateUrl);  77         // 2.get請求獲取CAS服務器的登錄信息
 78         String casUserInfoXml = doGet(serviceValidateUrl);  79         casUserInfoXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + casUserInfoXml;  80         // 3.解析返回的xml結果
 81  System.out.println(casUserInfoXml);  82         CasVO casVO = resolveCasXml(casUserInfoXml);  83         return casVO;  84  }  85 
 86     /**
 87  * 業務系統的登錄接口  88      */
 89     interface ClientSystem {  90         boolean doLogin(CasVO casVO);  91  }  92 
 93     /**
 94  * 解析CAS賬戶信息  95  * @param casUserInfoXml  96  * @return
 97      */
 98     private CasVO resolveCasXml(String casUserInfoXml) {  99         final String CAS_PREFIX = "cas:"; 100         final String LOGIN_SUCCESS_KEY = CAS_PREFIX + "authenticationSuccess"; 101         final String ACCOUNT_KEY = CAS_PREFIX + "user"; 102         final String ATTRIBUTES_KEY = CAS_PREFIX + "attributes"; 103         CasVO casVO = new CasVO(); 104         if (casUserInfoXml == null || "".equals(casUserInfoXml)) { 105             return casVO; 106  } 107         DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); 108         InputStream in = null; 109         try { 110             DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); 111             in = IOUtils.toInputStream(casUserInfoXml, Charset.forName(CasConfig.UTF_8)); 112             Document rootDoc = docBuilder.parse(in); 113             NodeList successNodeList = rootDoc.getElementsByTagName(LOGIN_SUCCESS_KEY); 114             if (successNodeList.getLength() > 0) { 115                 Node successNode = successNodeList.item(0); 116                 Document successDocument = successNode.getOwnerDocument(); 117                 NodeList accountNodeList = successDocument.getElementsByTagName(ACCOUNT_KEY); 118                 if (accountNodeList != null
119                         && accountNodeList.getLength() > 0) { 120                     Node accountNode = accountNodeList.item(0); 121                     Node accountText = accountNode.getFirstChild(); 122                     String nodeValue = accountText.getNodeValue(); 123  casVO.setAccount(nodeValue); 124  } 125                 NodeList attrsNodeList = successDocument.getElementsByTagName(ATTRIBUTES_KEY); 126                 if (attrsNodeList.getLength() > 0) { 127                     Node attrsNode = attrsNodeList.item(0); 128                     if (attrsNode.hasChildNodes()) { 129                         Document attrsDoc = attrsNode.getOwnerDocument(); 130                         Field[] fields = casVO.getClass().getDeclaredFields(); 131                         for (Field field : fields) { 132                             String fieldName = field.getName(); 133                             String attrTagName = CAS_PREFIX + fieldName; 134                             NodeList attrNodeList = attrsDoc.getElementsByTagName(attrTagName); 135                             if (attrNodeList.getLength() > 0) { 136                                 Node attrNode = attrNodeList.item(0); 137                                 Node attrText = attrNode.getFirstChild(); 138                                 if (attrText != null) { 139  field.set(casVO, attrText.getNodeValue().trim()); 140  } 141  } 142  } 143  } 144  } 145  } 146         } catch (Exception e) { 147             // 解析用戶信息失敗!
148  e.printStackTrace(); 149         } finally { 150  IOUtils.closeQuietly(in); 151  } 152         return casVO; 153  } 154 
155     /**
156  * 獲取驗證票據的url 157  * @param request 158  * @return
159      */
160     private String getServiceValidateUrl(HttpServletRequest request) throws IOException { 161         // 1.獲取targetUrl
162         String targetUrl = getTargetUrl(request); 163         System.err.println("targetUrl:"+targetUrl); 164         // 2.targetUrl進行base64編碼
165         String base64TargetUrl = new BASE64Encoder().encode(targetUrl.getBytes()); 166         System.err.println("base64編碼的targetUrl:"+base64TargetUrl); 167         // 3.獲取業務service的根路徑
168         String serviceUrlRoot = getBasePath(request) + CasConfig.MY_LOGIN_URI; 169         // 4.組裝service參數
170         String serviceUrl = serviceUrlRoot + "?targetUrl=base64" + base64TargetUrl; 171         System.err.println("原service:"+serviceUrl); 172         String service = URLEncoder.encode(serviceUrl, CasConfig.UTF_8); 173         // 5.獲取ticket
174         String ticket = request.getParameter("ticket"); 175         // 6.組裝CAS服務器驗證票據的地址
176         String ticketUrl = CasConfig.CAS_VALIDATE_URL + "?ticket=" + ticket + "&service="+service; 177         return ticketUrl; 178  } 179 
180     /**
181  * 獲取登錄成功后跳轉的url 182  * @param request 183  * @return http://192.168.1.76/main.html
184  * @throws IOException 185      */
186     private String getTargetUrl(HttpServletRequest request) throws IOException { 187         String basePath = getBasePath(request); 188         String targetUrl = request.getParameter("targetUrl"); 189         if (targetUrl == null || "".equals(targetUrl)) { 190             targetUrl = basePath + CasConfig.DEF_TARGET_URI; 191         } else { 192             if (targetUrl.startsWith("base64")){ 193                 targetUrl = targetUrl.substring("base64".length()); 194                 byte[] bytes = new BASE64Decoder().decodeBuffer(targetUrl); 195                 targetUrl = new String(bytes, CasConfig.UTF_8); 196                 System.err.println("解碼之后的target:"+targetUrl); 197  } 198  } 199         return targetUrl; 200  } 201 
202     /**
203  * 獲取cas賬戶信息錯誤的異常界面 204  * @param request 205  * @return
206      */
207     private String getErrorUrl(HttpServletRequest request) { 208         String basePath = getBasePath(request); 209         String errorUrl = basePath + CasConfig.SSO_ERROR_URI; 210         return errorUrl; 211  } 212 
213     /**
214  * 獲取項目的根路徑 215  * @param request 216  * @return
217      */
218     private String getBasePath(HttpServletRequest request) { 219         String scheme = request.getScheme(); // 協議
220         String serverName = request.getServerName(); // 域名或者ip
221         int serverPort = request.getServerPort(); // 端口
222         String contextPath = request.getContextPath(); 223         String url = ""; 224         if ((serverPort == 80) || (serverPort == 443)) { 225             url = scheme + "://" + serverName + contextPath + "/"; 226         } else { 227             url = scheme + "://" + serverName + ":" + serverPort + contextPath + "/"; 228  } 229         return url; 230  } 231 
232     /**
233  * 獲取CAS服務器登錄地址 234  * @param request 235  * @return http://localhost:8080/cas/login?service=http%3A%2F%2F192.168.1.76%2FmyLogin.do%3FtargetUrl%3Dbase64aHR0cDovLzE5Mi4xNjguMS43Ni9tYWluLmh0bWw%3D 236  * @throws IOException 237      */
238     private String getLoginUrl(HttpServletRequest request) throws IOException { 239         // 1.獲取targetUrl
240         String targetUrl = getTargetUrl(request); 241         System.err.println("targetUrl:"+targetUrl); 242         // 2.targetUrl進行base64編碼
243         String base64TargetUrl = new BASE64Encoder().encode(targetUrl.getBytes()); 244         System.err.println("base64編碼的targetUrl:"+base64TargetUrl); 245         // 3.獲取業務service的根路徑
246         String serviceUrlRoot = getBasePath(request) + CasConfig.MY_LOGIN_URI; 247         // 4.組裝service參數
248         String serviceUrl = serviceUrlRoot + "?targetUrl=base64" + base64TargetUrl; 249         String service = URLEncoder.encode(serviceUrl, CasConfig.UTF_8); 250         // 5.組裝CAS登錄的url
251         String loginUrl = CasConfig.CAS_LOGIN_URL+"?service="+service; 252         return loginUrl; 253  } 254 
255     /**
256  * 檢查ticket 257  * @param request 258  * @return
259      */
260     private boolean checkHasTicket(HttpServletRequest request) { 261         Object ticket = request.getParameter("ticket"); 262         if (ticket == null) { 263             return false; 264         } else { 265             return !String.valueOf(ticket).isEmpty(); 266  } 267  } 268 
269     /**
270  * 檢查登錄 271  * @param session 272  * @return
273      */
274     private boolean checkLogin(HttpSession session) { 275         Object isLogin = session.getAttribute(CasConfig.LOGIN_KEY); 276         Boolean login = Boolean.valueOf(String.valueOf(isLogin)); 277         return login; 278  } 279 
280 
281     /**
282  * get請求 283  * @param
284  * @return
285      */
286     private String doGet(String urlStr) throws IOException { 287         URL url = new URL(urlStr); 288         InputStream in = null; 289         HttpURLConnection conn = null; 290         try { 291  skipSSL(); 292             conn = (HttpURLConnection) url.openConnection(); 293             conn.setConnectTimeout(5000); 294  conn.connect(); 295             in = conn.getInputStream(); 296             return IOUtils.toString(in, Charset.forName(CasConfig.UTF_8)); 297         } finally { 298  IOUtils.close(conn); 299  IOUtils.closeQuietly(in); 300  } 301  } 302 
303     /**
304  * 繞過SSL驗證 305      */
306     private void skipSSL() { 307         try { 308             HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { 309                 public boolean verify(String hostname, SSLSession session) { 310                     return true; 311  } 312  }); 313             SSLContext context = SSLContext.getInstance("TLS"); 314             context.init(null, new X509TrustManager[]{new X509TrustManager() { 315                 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 316  } 317                 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 318  } 319                 public X509Certificate[] getAcceptedIssuers() { 320                     return new X509Certificate[0]; 321  } 322             }}, new SecureRandom()); 323  HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); 324         } catch (Exception e) { 325  e.printStackTrace(); 326  } 327  } 328 
329     public static abstract class IOUtils { 330         private static final int EOF = -1; 331         private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; 332         public static void close(URLConnection conn) { 333             if (conn instanceof HttpURLConnection) { 334  ((HttpURLConnection) conn).disconnect(); 335  } 336  } 337         public static void closeQuietly(InputStream input) { 338  closeQuietly((Closeable) input); 339  } 340         public static void closeQuietly(Closeable closeable) { 341             try { 342                 if (closeable != null) { 343  closeable.close(); 344  } 345             } catch (IOException ioe) { 346                 // ignore
347  } 348  } 349         public static String toString(InputStream input, Charset encoding) throws IOException { 350             StringWriter sw = new StringWriter(); 351  copy(input, sw, encoding); 352             return sw.toString(); 353  } 354         public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { 355             encoding = encoding == null ? Charset.defaultCharset() : encoding; 356             InputStreamReader in = new InputStreamReader(input, encoding); 357  copy(in, output); 358  } 359         public static int copy(Reader input, Writer output) throws IOException { 360             long count = copyLarge(input, output); 361             if (count > Integer.MAX_VALUE) { 362                 return -1; 363  } 364             return (int) count; 365  } 366         public static long copyLarge(Reader input, Writer output) throws IOException { 367             return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); 368  } 369         public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { 370             long count = 0; 371             int n; 372             while (EOF != (n = input.read(buffer))) { 373                 output.write(buffer, 0, n); 374                 count += n; 375  } 376             return count; 377  } 378         public static InputStream toInputStream(String input, Charset encoding) { 379             return new ByteArrayInputStream(input.getBytes()); 380  } 381  } 382 }

 

 1 package com.zxz.sso.controller;  2 
 3 /**
 4  * 解析的CAS賬戶信息  5  */
 6 public class CasVO {  7     private String account;  8     private String userName;  9 
10     public boolean isLogin() { 11         return account != null && !"".equals(account); 12  } 13 
14     public String getAccount() { 15         return account; 16  } 17 
18     public void setAccount(String account) { 19         this.account = account; 20  } 21 
22     public String getUserName() { 23         return userName; 24  } 25 
26     public void setUserName(String userName) { 27         this.userName = userName; 28  } 29 
30 }

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM