今天,在单点登录系统中,使用中文用户名登录系统时,出现了返回的用户名乱码的问题。
通过阅读cas_client源码,找到了具体的原因。
获取用户名的操作是在ticket验证的过程中,下面,我先按照流程描述一下ticket验证的过程。
首先,由于我们在客户端进行了如下配置(代码1):
<filter>
<filter-name>CASValidationFilter</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><!--cas服务器地址http://IP:PORT/CasWebProName-->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value><!--客户端服务器地址http://IP:PORT-->
</init-param>
</filter>
<filter-mapping>
<filter-name>CASValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
所以,在登陆成功以后,将进入Cas20ProxyReceivingTicketValidationFilter类。
AbstractTicketValidationFilter继承于AbstractCasFilter类。
AbstractCasFilter的doFilter方法如下(代码2):
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException
{
if (!preFilter(servletRequest, servletResponse, filterChain)) {
return;
}
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String ticket = retrieveTicketFromRequest(request);
if (CommonUtils.isNotBlank(ticket))
{
this.logger.debug("Attempting to validate ticket: {}", ticket);
try
{
Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));
this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
request.setAttribute("_const_cas_assertion_", assertion);
if (this.useSession) {
request.getSession().setAttribute("_const_cas_assertion_", assertion);
}
onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation)
{
this.logger.debug("Redirecting after successful ticket validation.");
response.sendRedirect(constructServiceUrl(request, response));
return;
}
}
catch (TicketValidationException e)
{
this.logger.debug(e.getMessage(), e);
onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(e);
}
response.sendError(403, e.getMessage());
return;
}
}
filterChain.doFilter(request, response);
}
在该方法中使用ticketValidator对象调用validate方法进行ticket校验。
AbstractTicketValidationFilter继承了AbstractTicketValidationFilter的getTicketValidator方法并进行了实现(代码3):
protected final TicketValidator getTicketValidator(FilterConfig filterConfig)
{
boolean allowAnyProxy = getBoolean(ConfigurationKeys.ACCEPT_ANY_PROXY);
String allowedProxyChains = getString(ConfigurationKeys.ALLOWED_PROXY_CHAINS);
String casServerUrlPrefix = getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX);
Class<? extends Cas20ServiceTicketValidator> ticketValidatorClass = getClass(ConfigurationKeys.TICKET_VALIDATOR_CLASS);
Cas20ServiceTicketValidator validator;
Cas20ServiceTicketValidator validator;
if ((allowAnyProxy) || (CommonUtils.isNotBlank(allowedProxyChains)))
{
Cas20ProxyTicketValidator v = (Cas20ProxyTicketValidator)createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix, this.defaultProxyTicketValidatorClass);
v.setAcceptAnyProxy(allowAnyProxy);
v.setAllowedProxyChains(CommonUtils.createProxyList(allowedProxyChains));
validator = v;
}
else
{
validator = (Cas20ServiceTicketValidator)createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix, this.defaultServiceTicketValidatorClass);
}
validator.setProxyCallbackUrl(getString(ConfigurationKeys.PROXY_CALLBACK_URL));
validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage);
HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(), getSSLConfig());
validator.setURLConnectionFactory(factory);
validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix, getString(ConfigurationKeys.ENCODING), factory));
validator.setRenew(getBoolean(ConfigurationKeys.RENEW));
validator.setEncoding(getString(ConfigurationKeys.ENCODING));
Map<String, String> additionalParameters = new HashMap();
List<String> params = Arrays.asList(RESERVED_INIT_PARAMS);
for (Enumeration<?> e = filterConfig.getInitParameterNames(); e.hasMoreElements();)
{
String s = (String)e.nextElement();
if (!params.contains(s)) {
additionalParameters.put(s, filterConfig.getInitParameter(s));
}
}
validator.setCustomParameters(additionalParameters);
return validator;
}
通过此方法可以获取代码2中需要使用的ticketValidator对象。
我们先看看ticketValidator对象都赋予了哪些值,在这里我只着重说一下(代码4):
validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix, getString(ConfigurationKeys.ENCODING), factory));
这个参数在后边会讲到,但我们先看一下第二个参数的ConfigurationKeys.ENCODING的值(代码5):
public static final ConfigurationKey<String> ENCODING = new ConfigurationKey("encoding", null);
可以看到,第二个参数的值默认为null.
在上面提到了,在AbstractCasFilter的doFilter方法中使用ticketValidator对象调用validate进行ticket验证。
AbstractUrlBasedTicketValidator继承了TicketValidator并对TicketValidator方法进行了重写(代码6):
public final Assertion validate(String ticket, String service)
throws TicketValidationException
{
String validationUrl = constructValidationUrl(ticket, service);
this.logger.debug("Constructing validation url: {}", validationUrl);
try
{
this.logger.debug("Retrieving response from server.");
String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);
if (serverResponse == null) {
throw new TicketValidationException("The CAS server returned no response.");
}
this.logger.debug("Server response: {}", serverResponse);
return parseResponseFromServer(serverResponse);
}
catch (MalformedURLException e)
{
throw new TicketValidationException(e);
}
}
该方法返回了parseResponseFromServer(serverResponse);
parseResponseFromServer的代码如下(代码7)
protected final Assertion parseResponseFromServer(String response)
throws TicketValidationException
{
String error = XmlUtils.getTextForElement(response, "authenticationFailure");
if (CommonUtils.isNotBlank(error)) {
throw new TicketValidationException(error);
}
String principal = XmlUtils.getTextForElement(response, "user");
String proxyGrantingTicketIou = XmlUtils.getTextForElement(response, "proxyGrantingTicket");
String proxyGrantingTicket;
String proxyGrantingTicket;
if ((CommonUtils.isBlank(proxyGrantingTicketIou)) || (this.proxyGrantingTicketStorage == null)) {
proxyGrantingTicket = null;
} else {
proxyGrantingTicket = this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou);
}
if (CommonUtils.isEmpty(principal)) {
throw new TicketValidationException("No principal was found in the response from the CAS server.");
}
Map<String, Object> attributes = extractCustomAttributes(response);
Assertion assertion;
Assertion assertion;
if (CommonUtils.isNotBlank(proxyGrantingTicket))
{
AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever);
assertion = new AssertionImpl(attributePrincipal);
}
else
{
assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
}
customParseResponse(response, assertion);
return assertion;
}
下面代码:
AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever);
又涉及到了AttributePrincipalImpl类,在该类中有如下方法:
public String getProxyTicketFor(String service)
{
if (this.proxyGrantingTicket != null) {
return this.proxyRetriever.getProxyTicketIdFor(this.proxyGrantingTicket, service);
}
LOGGER.debug("No ProxyGrantingTicket was supplied, so no Proxy Ticket can be retrieved.");
return null;
}
又调用了下面的方法:
public String getProxyTicketIdFor(String proxyGrantingTicketId, String targetService)
{
CommonUtils.assertNotNull(proxyGrantingTicketId, "proxyGrantingTicketId cannot be null.");
CommonUtils.assertNotNull(targetService, "targetService cannot be null.");
URL url = constructUrl(proxyGrantingTicketId, targetService);
String response;
String response;
if (this.urlConnectionFactory != null) {
response = CommonUtils.getResponseFromServer(url, this.urlConnectionFactory, this.encoding);
} else {
response = CommonUtils.getResponseFromServer(url, this.encoding);
}
String error = XmlUtils.getTextForElement(response, "proxyFailure");
if (CommonUtils.isNotEmpty(error))
{
logger.debug(error);
return null;
}
return XmlUtils.getTextForElement(response, "proxyTicket");
}
下面重点来了,
public static String getResponseFromServer(URL constructedUrl, HttpURLConnectionFactory factory, String encoding)
{
HttpURLConnection conn = null;
InputStreamReader in = null;
try
{
conn = factory.buildHttpURLConnection(constructedUrl.openConnection());
if (isEmpty(encoding)) {
in = new InputStreamReader(conn.getInputStream());
} else {
in = new InputStreamReader(conn.getInputStream(), encoding);
}
StringBuilder builder = new StringBuilder(255);
int byteRead;
while ((byteRead = in.read()) != -1) {
builder.append((char)byteRead);
}
return builder.toString();
}
catch (Exception e)
{
LOGGER.error(e.getMessage(), e);
throw new RuntimeException(e);
}
finally
{
closeQuietly(in);
if (conn != null) {
conn.disconnect();
}
}
}
该方法使用HttpURLConnection向server端发起请求,获取到的返回结果为xml格式,并解析xml数据获取用户名。
可以看到:
if (isEmpty(encoding)) {
in = new InputStreamReader(conn.getInputStream());
} else {
in = new InputStreamReader(conn.getInputStream(), encoding);
}
当encoding为null,时,在定义输入流时不会指定编码格式,通过测试发现,此时读取中文自幅度会乱码。
所以我们需要在客户端的web.xml中按如下进行配置来指定编码格式:
<filter>
<filter-name>CASValidationFilter</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><!--cas服务器地址http://IP:PORT/CasWebProName-->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value><!--客户端服务器地址http://IP:PORT-->
</init-param>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
分享到:
相关推荐
单点登录 sso cas带超详细文档,包含(cas-server-webapp-4.0.0.war、c3p0-0.9.1.2.jar、cas-client-core-3.3.3.jar、cas-server-support-jdbc-4.0.0.jar、cas-server-webapp-support-4.0.0.jar、commons-logging-...
CAS(Central Authentication Service,中央认证服务)是一种广泛使用的开源身份验证框架,主要目的是提供单一登录(Single Sign-On,SSO)功能,使得用户在访问多个应用系统时只需要进行一次身份验证。现在我们来...
总的来说,`cas-server-webapp-4.0.0` 提供了一个强大且灵活的单点登录解决方案。正确配置和使用它可以极大地提升用户体验,同时简化企业的身份管理。在部署和使用过程中,理解其工作原理、合理配置及安全策略是至关...
单点登录服务端项目cas-server单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-...
CAS(Central Authentication Service)是一种广泛使用的开放源代码的单点登录(Single Sign-On,简称SSO)系统。它允许用户通过一个认证界面访问多个应用系统,而无需在每个系统上分别进行登录。在这个"cas-server-...
CAS(Central Authentication Service)是一种基于Web的单一登录(Single Sign-On, SSO)协议,用于在多应用环境中统一用户认证。这个协议由耶鲁大学开发并开源,现在由Apereo基金会维护。标题提到的"cas-server-...
CAS(Central Authentication Service)是一种广泛使用的开放源代码的单一登录(Single Sign-On,简称SSO)协议,它允许用户通过一次认证过程访问多个应用系统,从而简化了用户的登录流程并提高了安全性。...
cas 单点登录解决方案可以通过多种方式来实现,例如使用 cas 服务器、LDAP 服务器、Active Directory 等。 cas 服务器可以作为身份验证服务器,LDAP 服务器可以作为用户信息存储服务器,Active Directory 可以作为...
cas-server mysql相关jar包(cas-server-support-jdbc-5.3.14.jar、cas-server-support-jdbc-authentication-5.3.14.jar、cas-server-support-jdbc-drivers-5.3.14.jar、mysql-connector-java-5.1.28.jar)
cas服务器war包,用于搭建cas认证服务器,由于国内网的原因,下载老是超时,故此提供给,cas-server-webapp-tomcat-5.3.14
cas-server-jdbc-3.0.5-rc2.jar
CAS(Central Authentication Service)是中央认证服务的缩写,它是一种广泛使用的单点登录(Single Sign-On, SSO)框架,主要用于实现用户在一个系统登录后,可以无须再次验证即可访问其他相互信任的系统。...
总的来说,CAS 4.0.0 是一个强大的身份验证解决方案,为多应用环境提供了安全、便捷的单点登录功能。其详细的源码和丰富的文档使得定制和扩展成为可能,适用于各种规模的组织和项目。正确理解和部署CAS服务器及其...
cas-server-webapp-tomcat-5.3.16.war maven依赖包
单点登录(Single Sign-On,简称SSO)是一种网络访问控制机制,允许用户在一次登录后,无需再次认证即可访问多个相互信任的应用系统。CAS(Central Authentication Service)是 Yale 大学开发的一个开源项目,它提供...
# sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次 ## 系统模块说明 1. cas: 单点登录模块,这里直接拿的是cas的项目改了点样式而已 2. doc: 文档目录,里面有数据库...
cas.war下载 cas-server-webapp-5.0.3.1
标题 "Spring Security 3 与 CAS 单点登录配置 - Server" 涉及到的是在企业级应用中实现安全访问控制的重要技术。Spring Security 是一个强大的和高度可定制的安全框架,用于保护基于 Java 的 Web 应用程序。而 CAS...
cas6.x需要使用jdk11版本,目前cas5.3.15.1是8的最高版本,但是稳定性cas-5.3.14.war最佳,本次基于jdk8-201编译
cas-server-webapp-tomcat-5.2.2.war