CAS单点登录(四)--cas server返回中文用户名时乱码的原因及解决方式
来源:互联网 发布:scratch软件 编辑:程序博客网 时间:2024/05/29 19:24
今天,在单点登录系统中,使用中文用户名登录系统时,出现了返回的用户名乱码的问题。
通过阅读cas_client源码,找到了具体的原因。
获取用户名的操作是在ticket验证的过程中,下面,我先按照流程描述一下ticket验证的过程。
首先,由于我们在客户端进行了如下配置(代码1):
<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><!-- 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>CAS Validation Filter</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>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><!-- 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>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- CAS单点登录(四)--cas server返回中文用户名时乱码的原因及解决方式
- CAS单点登录中文用户名乱码问题
- CAS单点登录中文用户名乱码问题
- CAS单点登录中文用户名乱码问题
- CAS单点登录,用户名为中文时,乱码的解决方法
- CAS解决单点登录SSO
- CAS解决单点登录SSO
- 【cas】cas获取登录的用户名
- 单点登录cas常见问题(四) - ticket有哪些存储方式?
- CAS单点登录(三)--服务端改造(登录页及登录方式的自定义)
- CAS单点登录四-单点登出
- Mvc4单点登录之四 配置Cas服务端,返回更多的用户信息!
- 单点登录cas的使用
- CAS的单点登录原理
- CAS 实现的单点登录
- CAS 实现的单点登录
- SSO单点登录 cas-server端的搭建
- cas server + cas client 单点登录 原理介绍
- FFmpeg 提取多音轨视频文件
- C++的函数模板
- 第三阶段-Ajax异步局部刷新
- 【22.70%】【codeforces 591C】 Median Smoothing
- redies
- CAS单点登录(四)--cas server返回中文用户名时乱码的原因及解决方式
- 算法分析:害死人不偿命的(3n+1)猜想(Java)
- 概念:Inner_Product(IP)层
- 聚类算法总结
- CSS中几种清除浮动法解决高度塌陷
- 程序员书籍
- SQL中CASE WHEN THEN的用法
- Atitit 文档资料管理同步解决方案
- Ubuntu 16.04与Win 7双系统时间相差8小时解决方法