C:\Windows\System32\drivers\etc\hostscss
SSO:單點登陸html
技術點:java
1、設置Cookie的路徑爲setPath("/") .即Tomcat的目錄下都有效mysql
2、設置Cookie的域setDomain(".itcast.com");即bbs.itcast.com,或是mail.itcast.com有效。即跨域。web
3、設置Cookie的時間。即便用戶不選擇在幾天內自動登陸,也應該保存Cookie以保存在當前瀏覽器沒有關閉的狀況下有效。spring
4、使用Filter自動登陸。sql
實現步驟:數據庫
配置虛擬主機,主要經過修改tomcat_home/conf/server.xml文件完成:跨域
增長几個Host節點,經過Cookie實現自動登陸,必須配置的虛擬主頁知足xxx.itcast.cn,即主域名必須保持一致。瀏覽器
1、登陸的主頁以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
</head>
<body>
<p>在同一臺服務器上,多個站點自動登陸....>>:<%=session.getId()%></p>
<c:if test="${empty sessionScope.user}">
<form name="f" method="post" action="<c:url value='/login'/>">
Name:<input type="text" name="name"/><br/>
Pwd:<input type="text" name="pwd"/><br/>
<input type="checkbox" name="chk" value="7">一週內自動登陸<br/>
<input type="submit" value="登陸"/>
</form>
</c:if>
<c:if test="${not empty sessionScope.user}">
歡迎你:${user}。<a href="<c:url value='/loginout'/>">安全退出</a>
</c:if>
<br/>
相關站點:(只要在一邊登陸成功,便可以自動登陸到另外一個程序)<br/>
<a href="http://mail.itcast.com:7777">mail.itcast.com</a><br/>
<a href="http://bbs.itcast.com:7777">bbs.itcast.com</a><br/>
</body>
</html>
/**
* 用戶登陸
*/
public class LoginServlet extends HttpServlet{
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String nm = req.getParameter("name");
String pwd = req.getParameter("pwd");
String chk = req.getParameter("chk"); //是否選中了7天自動登陸
String forward = "/index.jsp";
if(nm!=null && !nm.trim().equals("") && nm.startsWith("it")//用戶名是it開始,且密碼是pwd開始的能夠登陸
&& pwd !=null && !pwd.trim().equals("") &&
pwd.startsWith("pwd")){
System.err.println("登陸成功。。。。。");
forward = "/jsps/welcome.jsp";
//不管如何,都要設置cookie,若是沒有選擇自動登陸,則只在當前頁面的跳轉時有效,不然設置有效期間爲7天。
Cookie cookie = new Cookie("autologin",nm+"@"+pwd);
cookie.setPath("/"); //若是路徑爲/則爲整個tomcat目錄有用
cookie.setDomain(".itcast.com"); //設置對全部*.itcast.com爲後綴的域名效
if(chk!=null){
int time = 1*60*60*24*7; //1秒*60=1分*60分=1小時*24=1天*7=7天
cookie.setMaxAge(time);
}
resp.addCookie(cookie);
req.getSession().setAttribute("user", nm);
}else{
System.err.println("登陸不成功。。。。。。");
}
req.getRequestDispatcher(forward).forward(req, resp);
}
}
/**
* 自動登陸
*/
public class AutoLogin implements Filter {
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
System.err.println("開始自動登陸驗證.....");//此類中應該對登陸的servlet直接放行。根據判斷url決定。
HttpServletRequest requ = (HttpServletRequest) req;
HttpSession s = requ.getSession();
if (s.getAttribute("user") != null) {//若是用戶已經登陸則直接放行
System.err.println("用戶已經登陸,沒有必需要再作自動登陸。。。。");
} else {
Cookie[] cookies = requ.getCookies();
if (cookies != null) {
for (Cookie ck : cookies) {
if (ck.getName().equals("autologin")) {// 是不是自動登陸。。。。
System.err.println("自動登陸成功。。。。。");
String val = ck.getValue();
String[] vals = val.split("@");
s.setAttribute("user", vals[0]);
}
}
}
}
chain.doFilter(req, resp);
}
public void init(FilterConfig filterConfig) throws ServletException {}
}
/**
* 安全退出刪除Cookie
*/
public class LoginOutServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession s = req.getSession(); //獲取Session
Cookie cookie = new Cookie("autologin","");//必須聲明一個徹底相同名稱的Cookie
cookie.setPath("/");//路徑也要徹底相同
cookie.setDomain(".itcast.com");//域也要徹底相同
cookie.setMaxAge(0);//設置時間爲0,以直接刪除Cookie
resp.addCookie(cookie);
s.removeAttribute("user");
System.err.println("安全退出。。。。。");
resp.sendRedirect(req.getContextPath()+"/index.jsp");
}
}
爲了讓單點登陸,變成可配置的功能,能夠將保存Cookie的代碼,放到自動登陸的Filter中實現。
若是配置自動登陸的Filter則同時實現閃單點登陸,不然不加實現。
在CAS的主頁上,能夠看到CAS服務器,和客戶端配置的完整過程,根據提示,徹底能夠配置成功服務器和客戶端。同時,在CAS上也能夠找到服務器端的程序和客戶端的程序,都是已經配置好的,對於初步學習來講,徹底能夠直接取來配置測試。如下就是直接使用CAS官方提供的示例服務器和示例客戶端配置一個單點登陸的示例:
Jasig主頁:http://www.jasig.org/cas/download
如下是下載的超鏈接:
https://wiki.jasig.org/display/CASC/JA-SIG+Java+Client+Simple+WebApp+Sample
服務器端程序通常不用咱們完成,但須要作一點小小的修改,cas的服務器端程序由spring+Spring web flow+cas寫成。所有使用spring配置文件。它就是一個用戶登陸驗證、發售票據的中心。至關於Ticket Office(售票處)。
一個是服務器主機,和兩個客戶端虛擬主機。
1、修改etc/hosts文件:
2、修改tomcat/conf/server.xml配置文件,增長虛擬主機目錄,同時將服務器的端口修改爲80。
3、在tomcat的根目錄下,分別創建三個目錄,即server、bbs、news。
在三個目錄下,分別都創建一個ROOT(ROOT是tomcat的主默認主頁目錄)文件夾。
將cas-server.xx.war解壓後散放到/tomcat/server/ROOT目錄下。以下圖:
注意目錄結構,是散放到ROOT的目錄下。
啓動tomcat,在地址欄輸入:
登陸:
請先保證在單個服務器上登陸能夠登陸成功。若是不能登陸成功,請重複前面的配置。
將下載的文件分別解壓到tomcat/bbs/ROOT目錄下和tomcat/news/ROOT目錄下。注意是散放到ROOT目錄下。
因爲在mywebapp.war中並無放置依賴的jar文件,因此,還須要咱們添加它所依賴的jar文件,爲此我爲你們準備了已經放放置好的
mywebapp.war文件。
放置好的目錄結構以下:
(bbs程序同,再也不上圖)
WEB-INF/lib目錄下的包以下:
這兩個包,在cas-client.rar文件中均可以找到。
此處,你能夠啓動一個tomcat,若是啓動成功,則進入下一步。
當使用登陸客戶端受保護的資源時,若是發現尚未登陸,則會重定向到服務器(售票處)請求登陸驗證,登陸成功後即會獲取一張票據,服務器會攜帶這張票據再重定向到客戶端頁面。
修改客戶端的web.xml配置文件,讓它在登陸時,知道去哪臺服務器:
注意將裏面的https所有修改爲http。
修改的部分主要分爲兩塊:
1:修改登陸重定向過慮器,它用於保護受保護的資源,若是發麪用戶在訪問受保護的資源時,用戶尚未登陸,則會重定向到服務器,要求用戶登陸:
2、修改驗證過慮器,它的主要做用是接收服務器發送的Ticket,進行驗證
其餘沒有說明的部分,請不要修改。
請參考上面的實現配置另外一個項目的web.xml文件。
目前還不能實現單點登陸。但能夠對任意的一個客戶端進行登陸驗證。
一、 在地址欄輸入
點擊訪問受保護的頁面:got to protected area
將重定向到服務器請求登陸:
登陸成功後即重定回原請求頁面:
Cas服務器都是用spring配置文件配置而成。且使用了cookie技術。在ticketGrantingTicketCookieGenerator.xml文件中,保存了cookie的生成方式及有效時間。
注意,這是在server服務器上的spring配置文件。
打開此文件,修改爲如下內容:
說明:false是指支持http協議登陸。默認爲true,支持https登陸。
3600中cookie保存在本地的時間,默認爲-1即瀏覽器緩存。
cookiePath是cookie的path設置。
修改了上面文件後,便可測試是否能夠從一個點的登陸,便可以訪問兩個網站時都顯示先登陸的姓名:
登陸
登陸成功:
在地址欄直接輸入:www.bbs.com
能夠看到,顯示的是news用戶名,即以前在www.news.com上登陸的用戶名,即實現單點登陸。
好了,以上步驟,同窗們先本身完成,若是能夠配置成功,再進入下一步。
爲了文件書寫代碼,咱們須要將cas-server導入到eclipse環境中:
注意:src下的文件須要到cas-server/WEB-INF/classes下單獨copy.
將原來的虛擬主機目錄刪除。配置新的虛擬目錄,並經過<Context/>形式指定Eclipse下的項目爲項目的根目錄,以下:
Host的name保先原地址不變,這樣就沒有必要修改hosts文件了。在tomcat的根目錄下,建立一個casServer目錄,裏面什麼也不用放便可。
經過<Context path=」/」/>指定根目錄爲Eclipse中的項目。
測試是否能夠訪問。
項目中使用了jstl.jar包,應該放到WEB-INF/lib目錄下。
將原來配置的兩個虛擬主機目錄刪除,而後使用<Context/>配置新的項目。以下:
此處能夠徹底參考原有客戶端項目的配置。(略)
運行測試進入下一步。
1、Cas在登陸成功後會經過socket向客戶端傳遞用戶信息。通常狀況下就是用戶名而已。
2、cas客戶端的request對象是通過包裝的org.jasig.cas.client.util.HttpServletRequestWrapperFilter$CasHttpServletRequestWrapper。
3、在用戶成功後,客戶端的過慮器,會將用戶的信息封裝成java.security.Principal的子封裝到Assertion後放到Session中。
關於Assertion對象的結構圖,請見後面的部分。
如下是在客戶端的頁面上如何獲取用戶名信息的多種方式:
那麼,如何才能夠返回更多用戶的信息呢。如返回用戶和用戶名和id?後面的章節將會講到。
目前服務器的驗證方式爲用戶和密碼相同即爲登陸成功。這並不符合咱們的業務需求,大多數應用都是經過查詢數據庫獲取用戶名和密碼的。那咱們又如何設置從數據庫獲取用戶名和密碼呢?
爲了便於理解,我先將服務器的登陸方式修改成配置的。然後再修改爲經過數據庫進行驗證的。
修改配置文件:/WEB-INF/deployerConfigContext.xml。 -
在這個配置文件中,保存了多個用戶認證登陸的驗證方式,只要有一種驗證經過便可以登陸成功。
在<property name= "authenticationHandlers">...屬性內部,經過配置若干的AuthenticationHandler的子類,能夠改變登陸認證方式。也能夠增長認證方式,只要在用戶登陸時,有一種登陸方式是可行的,便可以登陸成功。
認證類的繼承關係以下:
默認已經配置了最後一個類即SimpleTestUsernamePasswordAuthenticationHandler。此類驗證用戶名和密碼是否一致。
Cas爲咱們提供了能夠直接配置用戶名和密碼類:即org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler。此類並無包含到默認的服務器代碼中,須要到cas-server/models/中查找名爲:cas-server-support-generic-3.4.11.jar的jar文件,並添加到WEB-INF/lib目錄下。
以下圖所示,正是名爲generic的jar包。(後面若是須要數據庫鏈接的,還須要jdbc的jar包)
將此jar包放到lib目錄下後,類的層次關係以下:
此類接收一個map看成用戶名和密碼的集合列表:
在/WEB-INF/deployerConfigContext.xml配置文件中的<property name= "authenticationHandlers">元素中,刪除原來的用戶名與密碼相同的認證,即:
<!-- <bean
class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> -->
而後在相同的位置配置:AcceptUsersAuthenticationHandler類:
配置以下:
登陸測試:
直接在配置文件中配置用戶名和密碼:可選的加密
<!-- 通用的認證管理器,經過一個文件或是一個map配置用戶名和密碼 -->
<bean class="org.jasig.cas.adaptors.generic.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<!-- 1234 MD5:4321 -->
<entry key="tom" value="81dc9bdb52d04dc20036dbd8313ed055"/>
<!-- 4321: md5 : -->
<entry key="Jack" value="d93591bdf7860e1e4ee2fca799911215"></entry>
</map>
</property>
<!-- 可選的使用md5進行加密 -->
<property name="passwordEncoder">
<bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="MD5"></constructor-arg>
<property name="characterEncoding" value="UTF-8"></property>
</bean>
</property>
</bean>
1、全部的認證類都是AuthenticationHandler的子類,因而咱們能夠本身開發基於任何規則的驗證。
2、基於用戶名和密碼的認證,則應該繼承AbstractUsernamePasswordAuthenticationHandler。
3、代碼以下:
package cn.itcast.handler;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
/**
* 顛倒的用戶名和密碼便可以登陸
* @author 傳智播客
* credentials:憑據
*/
public class MyReverseUsernamePasswordHandler
extends AbstractUsernamePasswordAuthenticationHandler {
protected boolean authenticateUsernamePasswordInternal(
UsernamePasswordCredentials credentials)
throws AuthenticationException {
String name = credentials.getUsername();
String pwd = credentials.getPassword();
if(name.equals(new StringBuffer(pwd).reverse().toString())){
System.err.println("登陸成功.....");
return true;
}
System.err.println("登陸不成功。。。");
return false;
}
}
4、配置以下:
將原有的配置用戶名和密碼的方式刪除,而後只配置上面的MyReverseUsernamePasswordHandler類:
這兒須要添加新的包:,須要同時添加mysql-connection.jar文件,以鏈接數據庫。
添加此包之後,AuthenticationHandler的層次結構爲:
經過配置能夠,修改CAS的登陸認證方式,默認的登陸方式爲用戶名與密碼一致便可以登陸。
在<property name= "authenticationHandlers">...屬性內部,經過配置若干的AuthenticationHandler的子類,能夠改變登陸認證方式。也能夠增長認證方式,只要在用戶登陸時,有一種登陸方式是可行的,便可以登陸成功。
全部的配置方式,在CAS的官方網站上均有詳細的說明。
修改配置文件:/WEB-INF/deployerConfigContext.xml。
在這個配置文件中,保存了多個用戶認證登陸的驗證方式,只要有一種驗證經過便可以登陸成功。
如下是在配置文件中增長用戶名和密碼的登陸的方式,其中設置了MD5對密碼進行加密。默認的加密方式爲PlainTextPasswordEncoder.即不加密。
A:建立數據庫:
create database sso character set UTF8;
use sso;
create table users(
id varchar(32),
name varchar(30),
pwd varchar(32)
);
insert into users values('U001','Jack','1234');
insert into users values('U002','Rose','4321');
B:在/WEB-INF/deployerConfigContext.xml文件的最下面,建立數據鏈接:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///sso?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
C:使用dataSource數據源,由於後面的查詢須要一個數據源的支持:
<!-- 使用數據庫驗證,且數據庫的密碼是通過md5加密的 -->
<bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
<property name="tableUsers" value="users"/>
<property name="fieldUser" value="name"/>
<property name="fieldPassword" value="pwd"/>
<property name="passwordEncoder"> <!--可選的加密-->
<bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="MD5"/>
<property name="characterEncoding" value="UTF-8"></property>
</bean>
</property>
</bean>
MySql的表結構以下:
<!-- 使用sql語句 -->
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="select pwd from users where name=?"/>
<property name="passwordEncoder">
<bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="MD5"/>
<property name="characterEncoding" value="UTF-8"></property>
</bean>
</property>
</bean>
表結構以下:
其實,上面的MD5驗證,徹底可使用同一個。
上面的程序很是簡單,徹底能夠經過查看源代碼和繼承結構圖來了解認證和登陸方式。且不須要書寫任何Java代碼。
配置返回用戶的ID而不是用戶名,用戶憑據:Credentials和用戶對象(被代理人) :Principal。
Credentials :用於定義用戶以什麼樣的憑據登陸,普通的憑據爲用戶名和密碼憑據。
Principal :用戶登陸成功之後,使用Credentials轉換成Principal。Principal中包含了用戶的用戶名(默認)及一些其餘屬性信息。
Credentials及Pincipal關係的圖示,及Principal內部結構圖示:
Principal的源代碼以下:(注意此Principal不是java.security.Principal類。而是由cas本身定義的一個接口)
package org.jasig.cas.authentication.principal;
import java.io.Serializable;
import java.util.Map;
public interface Principal extends Serializable {
String getId();
Map<String, Object> getAttributes();
}
爲了能夠返回用戶的ID,咱們能夠修改deployerConfigContext.xml文件中的<property name=’credentialsToPrincipalResolvers’>…中的<list/>元素中的Bean。
此時,爲了能夠返回用戶的ID,咱們必需要手工建立一個CredentialsToPrincipalResolver的子類。
具體代碼以下:
package cn.itcast.pubs;
import javax.sql.DataSource;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.jdbc.core.JdbcTemplate;
public class MyCredentialsToPrincipalResolver implements CredentialsToPrincipalResolver {
private DataSource dataSource;//查詢數據庫用
@Override
public Principal resolvePrincipal(Credentials credentials) {
UsernamePasswordCredentials up = //強制類型轉換
(UsernamePasswordCredentials) credentials;
String name = up.getUsername();
String pwd = up.getPassword();
String sql = "select id from users2 where u2_name=? and u2_pwd=?"; //查詢id - 通常只根據用戶查詢便可
String id = null;
try{
id=new JdbcTemplate(getDataSource()).queryForObject(sql, String.class, name,pwd);
if(id!=null){
Principal p = new SimplePrincipal(id);//封裝成包含id的Principal對象
return p;
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}
@Override
public boolean supports(Credentials credentials) {
boolean boo = //判斷是不是用戶和密碼憑據
UsernamePasswordCredentials.class.isAssignableFrom(credentials.getClass());
return boo;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
(在使用了ID的查詢之後,應該只使用數據庫進行驗證。)
在配置文件中配置以下:
配置文件爲:/WEB-INF/deployerConfigContext.xml
<property name="credentialsToPrincipalResolvers">
<list>
<bean class="cn.itcast.pubs.MyCredentialsToPrincipalResolver">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
<bean
class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
-->
<bean
class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
</list>
</property>
測試並運行,此時返回的應該是用戶的id了。
服務器,在返回給客戶端用戶信息時,默認只返回用戶名(咱們已經修改爲ID).但有時咱們須要更多的屬性信息,如用戶名。
則應作以下修改:
用戶登陸成功之後,CAS使用一個credentialsToPrincipalResolvers將credentials轉成Principal對象,此對象只有一個實現類以下:
SimplePrincipal的構造方法接收兩個參數,一個是用戶的id,一個爲用戶的其餘屬性。用戶的ID默認爲用戶登陸時使用的用戶名,前面第4點已經講過如何將用戶的name換成用戶的id返回給客戶端。爲了給客戶端返回更多的屬性,咱們必需要給Principal的構造方法傳遞第二個參數,它是一個Map<String,Object>類型。
具體代碼以下:
上圖經過給SimplePrincipal傳遞第二個構造參數設置了更多的屬性。
但,它並不會立刻顯示到客戶端,若是要顯示到客戶端,由於服務器驗證成功之後,是經過xml形式將結果傳遞給客戶端的,xml的生成由casServiceValidationSuccess.jsp文件負責。它的具體構造應該是如下形式:
<cas:serviceResponse
xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>U001</cas:user>
<cas:attributes>
<cas:pwd>1234</cas:pwd>
<cas:username>Jack</cas:username>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
在上面的代碼中,cas:attributes元素是我本身添加的,客戶端的的Filter在接收到上述的XML之後,會將css:attributes中的屬性解析出來,放到AttirubtePrincipal的attributes屬性中去(或是放到Asseration的attributes中去,兩個只會放一個)。
默認狀況下,將全部屬性信息放到AttributePrincipal中去,因此在客戶端的頁面上能夠經過如下方式獲取值:
因此,組成上面的<cas :attributes>元素中的內容,就成了如何傳遞更多屬性的關鍵,在修改了MyCredentialsToPrincipalResolver的代碼之後,而後還必需要修改casServiceValidationSuccess.jsp的代碼以下:
如下是源代碼:
<%@ page session="false" contentType="text/xml; charset=UTF-8"%><%@ taglib
prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ taglib
uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%><cas:serviceResponse
xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}"
varStatus="loopStatus" begin="0"
end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
<cas:attributes>
<c:forEach
items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"
var="attr">
<cas:${attr.key}>${attr.value}</cas:${attr.key}>
</c:forEach>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
而後修改deployerConfigContext.xml文件,將最後一個配置項:serviceRegistryDao中的全部屬性所有刪除或是註銷。
這個bean中的RegisteredServiceImpl的ignoreAttributes屬性將決定是否添加attributes屬性內容,默認爲false:不添加,只有去掉這個配置,
cas server纔會將獲取的用戶的附加屬性添加到認證用的Principal的attributes中去。
而後便可以在頁面上經過如下方式獲取用戶的其餘屬性:
<%
Assertion assertion = AssertionHolder.getAssertion();
AttributePrincipal ap = assertion.getPrincipal(); //獲取AttributePrincipal對象,這是客戶端對象
String name = ap.getName();
Map<String,Object> att = ap.getAttributes(); //獲取屬性值,爲一個Map類型。
out.print("<br/>"+name);
out.print("<br/>"+att);
%>
casServiceValidationSuccess.jsp頁面默認編碼格式爲ISO-8859-1,且在表單提交到客戶端頁面時,也使用IS0進行編碼,爲了處理中文,能夠在頁面上使用URLEncoder對須要傳遞的中文時行UTF-8編碼,而後從客戶端取得數據時,再作URLDecoder解碼:
casServiceValidationSuccess.jsp頁面,真是一個奇怪的頁面,因爲cas使用手工解析(沒有使用任何dom解析,硬編碼識別標標籤的開始和標籤的結束)xml的方式解析xml文件,全部,在修改此文件時,必定要加以注意:
上圖的紅框部分,必需要緊湊一些,不然會出現解析錯誤。
如下是源代碼:
<%@ page session="false" contentType="text/xml; charset=UTF-8" import="java.net.URLEncoder"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}"
varStatus="loopStatus" begin="0"
end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
<cas:attributes>
<c:forEach items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}" var="attr">
<c:set var="val" value="${attr.value}"/>
<cas:${attr.key}><%=URLEncoder.encode((String)pageContext.getAttribute("val"),"UTF-8")%></cas:${attr.key}>
</c:forEach>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
通過編碼之後的XML數據以下:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>U003</cas:user>
<cas:attributes>
<cas:pwd>1111</cas:pwd>
<cas:username>%E5%BC%A0%E4%B8%89</cas:username>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
可見,對中文進行了UTF-8編碼。
在客戶端使用URLDecoder進行解碼:
如下:
<%
Assertion assertion = AssertionHolder.getAssertion();
AttributePrincipal ap = assertion.getPrincipal();
String id = ap.getName();
Map<String,Object> att = ap.getAttributes();
out.print("<br/>"+id);
out.print("<br/>"+att);
String name = URLDecoder.decode(""+att.get("username"), "UTF-8");
out.println("<br/>"+name);
%>
顯示效果以下:
從服務器返回信息成功後,將以_const_cas_assertion_爲key將Assertion對象放到Session中去。知道了這一點,便可以監聽Session的屬性添加事件:
源代碼:
package cn.itcast.listener;
import java.net.URLDecoder;
import java.util.Map;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
/**
* 經過監聽器將從服務器上返回的信息放到Session中
* @author 傳智播客
*/
public class AssertionListener implements HttpSessionAttributeListener {
public void attributeAdded(HttpSessionBindingEvent se) {
if (se.getName().equals(AbstractCasFilter.CONST_CAS_ASSERTION)) {
System.err.println("添加了某屬性.....");
//不可使用工具類,只可使用session獲取對象
Assertion ass = (Assertion)se.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
Map<String, Object> user = ass.getPrincipal().getAttributes();
se.getSession().setAttribute("user", user);
try {
for (String key : user.keySet()) {
//由於服務器上轉碼,因此此外解碼
user.put(key,
URLDecoder.decode("" + user.get(key), "UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void attributeRemoved(HttpSessionBindingEvent se) {
}
public void attributeReplaced(HttpSessionBindingEvent se) {
}
}
配置到客戶端的web.xml中去:
<listener>
<listener-class>cn.itcast.listener.AssertionListener</listener-class>
</listener>
而後便可以在客戶端的頁面上經過如下方式獲取值:
裏面又包含了Principal對象及建立時間、過時時間、和用戶的ID或是名稱。
首先,用戶的ID或是用戶名,在CAS的客戶端程序中,能夠經過request.getRemoteUser()的方式獲取獲得。Spring是經過在過慮器中,包裝HttpRequest的方式實現的,其實,仍然是從Assertion中獲取獲得的數據。
在CAS服務端,用戶註冊成功之後,CAS服務器端是經過POST方式給客戶端傳遞一個XML數據的方式獲取獲得數據的。
Assertion的源代碼以下:
public interface Assertion extends Serializable {
/**
* The date from which the assertion is valid from,有效時間從什麼時間開始
*/
Date getValidFromDate();
/**
* The date which the assertion is valid until,在效時間,到什麼時間結束。
*/
Date getValidUntilDate();
/**
* The key/value pairs associated with this assertion,一組屬性值
*/
Map getAttributes();
/**
* The principal for which this assertion is valid,被代理的對象,此對象中,又包含了一個ID和一組屬性值
*/
AttributePrincipal getPrincipal();
}
經過上面的源代碼,能夠知道Assertion和Principal的關係以下:
從Assertion中獲取信息,能夠查看示例客戶端的getpt.jsp頁面上的代碼:
方法1、從Session中獲取Assertion對象:
Assertion assertion1 = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
方法2、經過AssertionHolder的靜態方法獲取Assertion
Assertion assertion2 = AssertionHolder.getAssertion();
在一個客戶端註銷之後,應該將其餘全部站點的登陸Session所有註銷:
1、發出註銷申請
在CAS的客戶端,CAS使用一個Map維護了全部登陸用戶的Session和TG(憑據)。CAS服務器將依次將客戶端發送請求,被CAS客戶端的註銷過慮器攔截到,註銷過慮器完成客戶端Session的註銷工做。
因此,爲了實現單點註銷,必需要將客戶端的單點註銷過慮器也打開:
找到cas客戶端web.xml文件,啓用如下代碼:
<!-- Sign out not yet implemented,單點註銷 -->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
只要打開上面的過慮器,便可以實現單點註銷。
測試:在CAS服務器端輸入 :http://localhost/casServer/logout便可以完成單點註銷。
注意,應該是服務器的地址,即http://服務器地址/logout
單點註銷:
TicketGrantingTicketImpl.logOutOfService方法將會獲取全部Ticket,經過遍歷,而後調用下面的方法。
AbstractWebApplicationService. logOutOfService方法將會出送一段XML文本給每個客戶端。
如下是TicketGrantingTicketImpl的片斷代碼:
private void logOutOfServices() {
for (final Entry<String, Service> entry : this.services.entrySet()) {//遍歷全部註冊過的客戶端
if (!entry.getValue().logOutOfService(entry.getKey())) { //調用註銷服務
LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
}
}
}
如下是AbstractWebApplicationService的片斷代碼:
public synchronized boolean logOutOfService(final String sessionIdentifier) {
if (this.loggedOutAlready) {
return true;
}
LOG.debug("Sending logout request for: " + getId());
//組織一段XML文本
final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""
+ GENERATOR.getNewTicketId("LR")
+ "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()
+ "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"
+ sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>";
this.loggedOutAlready = true;
if (this.httpClient != null) {
return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest, true); //發送請求
}
return false;
}
在開始以前,讀者必須瞭解Spring的MVC框架和Spring WebFlow(頁面流)。
而後將完整的cas-server.war包,部署到MyEclipe工做區中,以便於更新和修改。爲此,應該將原來classes目錄下的cas的原類打包,放到lib目錄下。
修改如下頁面,能夠實現自定義登陸頁面的需求。具體jsp的寫法略。