CAS单点登录(四)--cas server返回中文用户名时乱码的原因及解决方式

今天,在单点登录系统中,使用中文用户名登录系统时,出现了返回的用户名乱码的问题。

通过阅读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>


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值