`
dawuafang
  • 浏览: 1192036 次
文章分类
社区版块
存档分类
最新评论

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>


分享到:
评论

相关推荐

    cas-server-webapp-4.0.0单点登录(带超详细文档、数据连接jar包、c3p0)可运行

    单点登录 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-server-3.4.10-release和cas-client-3.2.1-release

    CAS(Central Authentication Service,中央认证服务)是一种广泛使用的开源身份验证框架,主要目的是提供单一登录(Single Sign-On,SSO)功能,使得用户在访问多个应用系统时只需要进行一次身份验证。现在我们来...

    cas-server-webapp-4.0.0实现单点登录

    总的来说,`cas-server-webapp-4.0.0` 提供了一个强大且灵活的单点登录解决方案。正确配置和使用它可以极大地提升用户体验,同时简化企业的身份管理。在部署和使用过程中,理解其工作原理、合理配置及安全策略是至关...

    单点登录服务端项目cas-server

    单点登录服务端项目cas-server单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-server 单点登录服务端项目cas-...

    cas-server-4.0.0-release单点登录源码和war包-原版

    CAS(Central Authentication Service)是一种广泛使用的开放源代码的单点登录(Single Sign-On,简称SSO)系统。它允许用户通过一个认证界面访问多个应用系统,而无需在每个系统上分别进行登录。在这个"cas-server-...

    cas-server-3.4.10-release和cas-client-3.2.1-release两个

    CAS(Central Authentication Service)是一种基于Web的单一登录(Single Sign-On, SSO)协议,用于在多应用环境中统一用户认证。这个协议由耶鲁大学开发并开源,现在由Apereo基金会维护。标题提到的"cas-server-...

    cas-server-4.0.0-release部署包

    CAS(Central Authentication Service)是一种广泛使用的开放源代码的单一登录(Single Sign-On,简称SSO)协议,它允许用户通过一次认证过程访问多个应用系统,从而简化了用户的登录流程并提高了安全性。...

    cas 单点登录 解决方案.

    cas 单点登录解决方案可以通过多种方式来实现,例如使用 cas 服务器、LDAP 服务器、Active Directory 等。 cas 服务器可以作为身份验证服务器,LDAP 服务器可以作为用户信息存储服务器,Active Directory 可以作为...

    cas-server-5.3.14-mysql相关jar包.rar

    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-server-webapp-tomcat-5.3.14

    cas服务器war包,用于搭建cas认证服务器,由于国内网的原因,下载老是超时,故此提供给,cas-server-webapp-tomcat-5.3.14

    cas-server-jdbc-3.0.5-rc2.jar

    cas-server-jdbc-3.0.5-rc2.jar

    cas单点登录客户端--c++版

    CAS(Central Authentication Service)是中央认证服务的缩写,它是一种广泛使用的单点登录(Single Sign-On, SSO)框架,主要用于实现用户在一个系统登录后,可以无须再次验证即可访问其他相互信任的系统。...

    cas-server-4.0.0-release与依赖包.zip

    总的来说,CAS 4.0.0 是一个强大的身份验证解决方案,为多应用环境提供了安全、便捷的单点登录功能。其详细的源码和丰富的文档使得定制和扩展成为可能,适用于各种规模的组织和项目。正确理解和部署CAS服务器及其...

    cas-server-webapp-tomcat-5.3.16.war

    cas-server-webapp-tomcat-5.3.16.war maven依赖包

    cas-client-3.3.3-release和cas-server-4.2.1-release下载

    单点登录(Single Sign-On,简称SSO)是一种网络访问控制机制,允许用户在一次登录后,无需再次认证即可访问多个相互信任的应用系统。CAS(Central Authentication Service)是 Yale 大学开发的一个开源项目,它提供...

    单点登录sso-shiro-cas-maven

    # sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次 ## 系统模块说明 1. cas: 单点登录模块,这里直接拿的是cas的项目改了点样式而已 2. doc: 文档目录,里面有数据库...

    cas.war下载 cas-server-webapp-5.0.3.1

    cas.war下载 cas-server-webapp-5.0.3.1

    Spring Security 3 与 CAS单点登录配置-Server

    标题 "Spring Security 3 与 CAS 单点登录配置 - Server" 涉及到的是在企业级应用中实现安全访问控制的重要技术。Spring Security 是一个强大的和高度可定制的安全框架,用于保护基于 Java 的 Web 应用程序。而 CAS...

    单点登录cas-overlay-template-5.3编译后的cas.war

    cas6.x需要使用jdk11版本,目前cas5.3.15.1是8的最高版本,但是稳定性cas-5.3.14.war最佳,本次基于jdk8-201编译

    cas-server-webapp-tomcat-5.2.2.war

    cas-server-webapp-tomcat-5.2.2.war

Global site tag (gtag.js) - Google Analytics