目 录 | |
---|---|
1 | CAS原理 |
2 | cas-server配置 |
3 | cas-client配置 |
4 | 客户端自定义登录界面 |
一、CAS 原理
访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。
定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。
用户认证:用户身份认证。
发放票据: SSO 服务器会产生一个随机的 Service Ticket 。
验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。
传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。
下面是 CAS 最基本的协议过程:
如上图: CAS Client 与受保护的客户端应用部署在一起,以Filter方式保护Web应用的受保护资源,过滤从客户端过来的每一个 Web 请求,同 时, CAS Client 会分析 HTTP 请求中是否包含请求 Service Ticket( ST 上图中的 Ticket) ,如果没有,则说明该用户是没有经过认证的;于是 CAS Client 会重定向用户请求到 CAS Server ( Step 2 ),并传递 Service (要访问的目的资源地址)。 Step 3 是用户认证过程,如果用户提供了正确的 Credentials , CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket ,并缓存以待将来验证,并且重定向用户到 Service 所在地址(附带刚才产生的 Service Ticket ) , 并为客户端浏览器设置一个 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新产生的 Ticket 过后,在 Step 5 和 Step6 中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。
在该协议中,所有与 CAS Server 的交互均采用 SSL 协议,以确保 ST 和 TGC 的安全性。协议工作过程中会有 2次重定向 的过程。但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的(使用 HttpsURLConnection )。
CAS 请求认证时序图如下:
二、cas-server配置
本文所用测试地址说明:
cas-server地址:http://localhost:8080/cas
cas-client地址:http://localhost:7080/test_consumer
1.下载cas包
https://github.com/apereo/cas/releases/tag/v3.5.2
2.cas-server-3.5.2-release.zip解压,将modules下面的cas-server-webapp-3.5.2.war部署到tomcat服务器,重命名为cas.war并解压。####
3.导入modules中的cas-server-support-jdbc-3.5.2.jar包,导入数据库驱动mysql-connector-java-5.1.29.jar包
4.由于不采用https方式,需要修改配置文件
WEB-INF/DEPLOYERCONFIGCONTEXT.XML
增加参数 p:requireSecure="false" ,是否需要安全验证,即 HTTPS , false 为不采用 如下:
<bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" p:requireSecure= "false"/>
WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml 修改
<bean id = "ticketGrantingTicketCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASTGC"
p:cookiePath = "/cas"/>
WEB-INF\spring-configuration\warnCookieGenerator.xml 修改
<bean id = "warnCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASPRIVACY"
p:cookiePath = "/cas"/>
5.自定义验证,将原来的测试用验证注释掉,然后添加查询数据库方式验证,对于数据库表tb_user
<!--<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/>-->
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"></property>
<property name="sql" value="select password from tb_user where username=?"></property>
<property name="passwordEncoder" ref="myPasswordEncoder"></property>
</bean>
6.自定义MD5验证
<bean id="myPasswordEncoder" class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>
自定义MD5 java代码如下:
package org.jasig.cas.authentication.handler;
import java.security.MessageDigest;
public class MyPasswordEncoder implements PasswordEncoder
{
public static void main(String[] args) {
MyPasswordEncoder t = new MyPasswordEncoder();
System.out.println(t.encode("123456"));
}
public String encode(String content) {
return md5(md5(content) + "asiainfo");
}
public String md5(String content){
if(content == null){
return null;
}
StringBuffer sbReturn = new StringBuffer();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((content).getBytes("utf-8"));
for (byte b : md.digest()) {
sbReturn.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sbReturn.toString();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
测试cas-server环境
三、cas-client配置
web.xml配置
<!-- cas client begin -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- CAS Server 通知 CAS Client,删除session,注销登录信息 -->
<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>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<!-- 将AuthenticationFilter代码复制一份自定义为ClientAuthenticationFilter.java文件 -->
<filter-class>org.jasig.cas.client.authentication.ClientAuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas/login</param-value>
</init-param>
<!-- 添加各客户端对应的登录界面 -->
<init-param>
<param-name>login_from</param-name>
<param-value>http://localhost:7080/test_consumer/login.jsp</param-value>
</init-param>
<init-param>
<!--这里的server是服务端的IP-->
<param-name>serverName</param-name>
<param-value>http://localhost:7080/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:7080/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器负责实现HttpServletRequest请求的包裹,
比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- cas client end -->
自定义ClientAuthenticationFilter.java文件添加:
private String login_from;
......
//initInternal方法中添加:
setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "login_from", null));
log.trace("Loaded login_from parameter: " + this.login_from);
//doFilter方法中添加:
//如果是登录界面则跳过
String loginUrl = request.getRequestURL().toString();
if(this.casServerLoginUrl.equals(loginUrl)){
filterChain.doFilter(request, response);
return;
}
四、客户端自定义登录界面
1.自定义login.jsp登录界面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>自定义登录界面</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<script type="text/javascript">
function getQueryStringByName(name) {
var result = location.search.match(new RegExp("[\?\&]" + name+ "=([^\&]+)","i"));
if(result == null || result.length < 1){
return "";
}
return result[1];
}
var info = getQueryStringByName('info');
if (info == "error")
alert("用户名密码错误!");
</script>
<form method="GET" action="http://localhost:8080/cas/login">
<p>用户名 : <input type="text" name="username"/></p>
<p>密码 : <input type="password" name="password"/></p>
<p><input type="submit" value="登录"/></p>
<input type="hidden" name="auto" value="true"/>
<input type="hidden" name="service" value="<%=request.getParameter("service") %>" />
</form>
</body>
</html>
2.定义相同的路径包org.jasig.cas.web.flow
将cas-server-core-3.5.2中的AuthenticationViaFormAction.java复制到该包下就行改写,修改:
修改submit方法中
try {
WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
putWarnCookieIfRequestParameterPresent(context);
return "success";
} catch (final TicketException e) {
//添加验证失败后的跳转页面 begin
String login_from = context.getRequestParameters().get("login_from");
if (login_from != null && login_from.length() > 0) {
context.getRequestScope().put("redirectUrl", login_from + "?info=error");
return "customizedRedirect";
}
//添加验证失败后的跳转页面 end
populateErrorsInstance(e, messageContext);
if (isCauseAuthenticationException(e))
return getAuthenticationExceptionEventId(e);
return "error";
}
将生成的AuthenticationViaFormAction.class替换cas-server-core-3.5.2.jar中的class文件
3.修改客户端过滤规则,将login.jsp排除在外
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/jsp/*</url-pattern>
</filter-mapping>
4.修改cas的默认登录页面 WEB-INF/view/jsp/default/ui/casLoginView.jsp
5.cas-server-webapp工程中,修改WEB-INF/login-webflow.xml
<action-state id="realSubmit">
......
<transition on="error" to="generateLoginTicket"/>
<!--加入下面这句话该transition , 当验证失败之后转到自定义页面 -->
<transition on="customizedRedirect" to="customizedRedirectView"/>
......
</action-state>
6.添加客户端跳转页面
<end-state id="customizedRedirectView" view="externalRedirect:${requestScope.redirectUrl}"/>
测试自定义登录界面
在客户端浏览器中输入http://localhost:7080/test_consumer/jsp/home.jsp 回车,cas server验证失败 后重定向到客户端传过来的login_form地址