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> 


0 0
原创粉丝点击