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