`

CAS客户端证书认证登录

    博客分类:
  • j2ee
阅读更多

前端时间需要实现公司内网证书自动登录CAS.

由于对CAS的底层还不是特别了解所以学习了下,看了下源码.

 

这里我由上而下的讲解实现的过程.

 

1.Web Flow

我们都知道CAS目前使用了Spring Web Flow,

在CAS中Spring Web Flow的配置文件为login-webflow.xml

里面主要配置了登录的流程.这个如果用图来表示的话那应该是一个状态图,

一些节点会有一些判断然后会有不同的分支.

这里增加了startX509Authenticate这个节点,当需要登录的时候首先进入这个节点来验证,如果这里验证不成功的话才会进入普通的登录界面.负责直接登录成功,或者登录失败.

 

修改后的配置文件如下:

Xml代码 复制代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.     <flow xmlns="http://www.springframework.org/schema/webflow"  
  3.           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.           xsi:schemaLocation="   
  5.               http://www.springframework.org/schema/webflow   
  6.               http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">  
  7.   
  8.     <start-state idref="initialFlowSetup"/>  
  9.   
  10.     <action-state id="initialFlowSetup">  
  11.         <action bean="initialFlowSetupAction" />  
  12.         <transition on="success" to="ticketGrantingTicketExistsCheck" />  
  13.     </action-state>  
  14.        
  15.     <decision-state id="ticketGrantingTicketExistsCheck">  
  16.         <if test="${flowScope.ticketGrantingTicketId != null}" then="hasServiceCheck" else="gatewayRequestCheck" />  
  17.     </decision-state>  
  18.        
  19.     <decision-state id="gatewayRequestCheck">  
  20.         <if test="${externalContext.requestParameterMap['gateway'] != '' &amp;&amp; externalContext.requestParameterMap['gateway'] != null &amp;&amp; flowScope.service != null}" then="redirect" else="startX509Authenticate" />  
  21.     </decision-state>  
  22.        
  23.     <decision-state id="hasServiceCheck">  
  24.         <if test="${flowScope.service != null}" then="renewRequestCheck" else="viewGenericLoginSuccess" />  
  25.     </decision-state>  
  26.        
  27.     <decision-state id="renewRequestCheck">  
  28.         <if test="${externalContext.requestParameterMap['renew'] != '' &amp;&amp; externalContext.requestParameterMap['renew'] != null}" then="startX509Authenticate" else="generateServiceTicket" />  
  29.     </decision-state>  
  30.        
  31.     <decision-state id="warn">  
  32.         <if test="${flowScope.warnCookieValue}" then="showWarningView" else="redirect" />  
  33.     </decision-state>  
  34.        
  35.   
  36.     <action-state id="startX509Authenticate">  
  37.         <action bean="x509Check" />  
  38.         <transition on="success" to="sendTicketGrantingTicket" />  
  39.         <transition on="error" to="viewLoginForm" />  
  40.     </action-state>  
  41.   
  42.     <view-state id="viewLoginForm" view="casLoginView">  
  43.             <render-actions>  
  44.             <action bean="authenticationViaFormAction" method="setupForm"/>  
  45.             <action bean="authenticationViaFormAction" method="referenceData"/>  
  46.         </render-actions>  
  47.         <transition on="submit" to="bindAndValidate" />  
  48.     </view-state>  
  49.        
  50.     <action-state id="bindAndValidate">  
  51.         <action bean="authenticationViaFormAction" />  
  52.         <transition on="success" to="submit" />  
  53.         <transition on="error" to="viewLoginForm" />  
  54.     </action-state>  
  55.        
  56.     <action-state id="submit">  
  57.         <action bean="authenticationViaFormAction" method="submit" />  
  58.         <transition on="warn" to="warn" />  
  59.         <transition on="success" to="sendTicketGrantingTicket" />  
  60.         <transition on="error" to="viewLoginForm" />  
  61.     </action-state>  
  62.        
  63.     <action-state id="sendTicketGrantingTicket">  
  64.         <action bean="sendTicketGrantingTicketAction" />  
  65.         <transition on="success" to="serviceCheck" />  
  66.     </action-state>  
  67.   
  68.     <decision-state id="serviceCheck">  
  69.         <if test="${flowScope.service != null}" then="generateServiceTicket" else="viewGenericLoginSuccess" />  
  70.     </decision-state>  
  71.        
  72.     <action-state id="generateServiceTicket">  
  73.         <action bean="generateServiceTicketAction" />  
  74.         <transition on="success" to ="warn" />  
  75.         <transition on="error" to="viewLoginForm" />  
  76.         <transition on="gateway" to="redirect" />  
  77.     </action-state>  
  78.   
  79.     <end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />  
  80.     <end-state id="showWarningView" view="casLoginConfirmView" />  
  81.     <end-state id="redirect" view="bean:alibabaDynamicRedirectViewSelector" />  
  82.     <end-state id="viewServiceErrorView" view="viewServiceErrorView" />  
  83.         <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" />  
  84.   
  85.     <global-transitions>  
  86.         <transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />  
  87.         <transition to="viewServiceSsoErrorView" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" />  
  88.         <transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />  
  89.     </global-transitions>  
  90. </flow>  
<?xml version="1.0" encoding="UTF-8"?>
    <flow xmlns="http://www.springframework.org/schema/webflow"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="
              http://www.springframework.org/schema/webflow
              http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="initialFlowSetup"/>

	<action-state id="initialFlowSetup">
		<action bean="initialFlowSetupAction" />
		<transition on="success" to="ticketGrantingTicketExistsCheck" />
	</action-state>
	
	<decision-state id="ticketGrantingTicketExistsCheck">
		<if test="${flowScope.ticketGrantingTicketId != null}" then="hasServiceCheck" else="gatewayRequestCheck" />
	</decision-state>
    
	<decision-state id="gatewayRequestCheck">
		<if test="${externalContext.requestParameterMap['gateway'] != '' &amp;&amp; externalContext.requestParameterMap['gateway'] != null &amp;&amp; flowScope.service != null}" then="redirect" else="startX509Authenticate" />
	</decision-state>
	
	<decision-state id="hasServiceCheck">
		<if test="${flowScope.service != null}" then="renewRequestCheck" else="viewGenericLoginSuccess" />
	</decision-state>
	
	<decision-state id="renewRequestCheck">
		<if test="${externalContext.requestParameterMap['renew'] != '' &amp;&amp; externalContext.requestParameterMap['renew'] != null}" then="startX509Authenticate" else="generateServiceTicket" />
	</decision-state>
	
	<decision-state id="warn">
		<if test="${flowScope.warnCookieValue}" then="showWarningView" else="redirect" />
	</decision-state>
	

	<action-state id="startX509Authenticate">
		<action bean="x509Check" />
		<transition on="success" to="sendTicketGrantingTicket" />
		<transition on="error" to="viewLoginForm" />
	</action-state>

	<view-state id="viewLoginForm" view="casLoginView">
			<render-actions>
			<action bean="authenticationViaFormAction" method="setupForm"/>
			<action bean="authenticationViaFormAction" method="referenceData"/>
		</render-actions>
		<transition on="submit" to="bindAndValidate" />
	</view-state>
	
	<action-state id="bindAndValidate">
		<action bean="authenticationViaFormAction" />
		<transition on="success" to="submit" />
		<transition on="error" to="viewLoginForm" />
	</action-state>
	
	<action-state id="submit">
		<action bean="authenticationViaFormAction" method="submit" />
		<transition on="warn" to="warn" />
		<transition on="success" to="sendTicketGrantingTicket" />
		<transition on="error" to="viewLoginForm" />
	</action-state>
	
	<action-state id="sendTicketGrantingTicket">
		<action bean="sendTicketGrantingTicketAction" />
		<transition on="success" to="serviceCheck" />
	</action-state>

	<decision-state id="serviceCheck">
		<if test="${flowScope.service != null}" then="generateServiceTicket" else="viewGenericLoginSuccess" />
	</decision-state>
	
	<action-state id="generateServiceTicket">
		<action bean="generateServiceTicketAction" />
		<transition on="success" to ="warn" />
		<transition on="error" to="viewLoginForm" />
		<transition on="gateway" to="redirect" />
	</action-state>

	<end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />
	<end-state id="showWarningView" view="casLoginConfirmView" />
	<end-state id="redirect" view="bean:alibabaDynamicRedirectViewSelector" />
	<end-state id="viewServiceErrorView" view="viewServiceErrorView" />
        <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" />

	<global-transitions>
		<transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
        <transition to="viewServiceSsoErrorView" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" />
		<transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
	</global-transitions>
</flow>

 

 

2.从X509Check着手

从上面的配置文件我们可看出startX509Authenticate这个节点对应的Bean是x509Check

那么我们需要增加这样的一个Bean

我们在cas-servlet.xml这个配置文件中增加这个类的配置,因为CAS自带了证书认证的Action

Xml代码 复制代码
  1. <bean id="x509Check" class="org.jasig.cas.adaptors.x509.web.flow.X509CertificateCredentialsNonInteractiveAction"  
  2.         p:centralAuthenticationService-ref="centralAuthenticationService"/>  
<bean id="x509Check" class="org.jasig.cas.adaptors.x509.web.flow.X509CertificateCredentialsNonInteractiveAction"
		p:centralAuthenticationService-ref="centralAuthenticationService"/>

 这里不得不插进来说一下,在执行的时候是怎么个机制了.

 

当有一个请求过来之后,web flow安排对应节点的Action 来处理,

Action便通过它的centralAuthenticationService 来进行createTicketGrantingTicket,传递的参数是credentials.

centralAuthenticationService ,即org.jasig.cas.CentralAuthenticationServiceImpl这个类.

在颁发TGT之前先通过自己的authenticationManager来验证当前传递过来的credentials是否合法.

来到authenticationManager 的家中之后,使用什么来验证credentials呢,对了各种authenticationHandlers该上场了,这些authenticationHandlers 们也都是在配置authenticationManager的时候xml配置进去的.这里会选择一个能够处理当前credential的authenticationHandler来进行验证工作(authenticationHandler.supports(credentials)).也就是这些处理器都需要实现supports这个方法.

如果验证成功了,我们需要将一些标示信息带到cas的client端阿,那边需要这个信息.

所以验证之后,credentialsToPrincipalResolvers 们上场了,他们能够很好的带出需要传回给cas client的信息.

 

经过上面的分析,我们需要将我们自己的Handler和credentialsToPrincipalResolvers添加到authenticationManager的配置中

Java代码 复制代码
  1. <bean id="authenticationManager"  
  2. class="org.jasig.cas.authentication.AuthenticationManagerImpl">   
  3.     <property name="credentialsToPrincipalResolvers">   
  4.         <list>   
  5.             <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />   
  6.             <bean class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />   
  7.             <bean class="org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToDNEmailPrincipalResolver"/>   
  8.         </list>   
  9.     </property>   
  10.     <property name="authenticationHandlers">   
  11.         <list>   
  12.             <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"  
  13.                 p:httpClient-ref="httpClient" />   
  14.             <ref bean="dynamicAuthenticationHandler" />   
  15.             <ref bean="x509CredentialsAuthenticationHandler"/>   
  16.         </list>   
  17.     </property>   
  18. </bean>  
	<bean id="authenticationManager"
	class="org.jasig.cas.authentication.AuthenticationManagerImpl">
		<property name="credentialsToPrincipalResolvers">
			<list>
				<bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
				<bean class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
				<bean class="org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToDNEmailPrincipalResolver"/>
			</list>
		</property>
		<property name="authenticationHandlers">
			<list>
				<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
					p:httpClient-ref="httpClient" />
				<ref bean="dynamicAuthenticationHandler" />
				<ref bean="x509CredentialsAuthenticationHandler"/>
			</list>
		</property>
	</bean>

  x509CredentialsAuthenticationHandler这个CAS自带了,但是我们需要返回到CAS Client的为证书的邮箱,而CAS没有提供这样的Resolver,所以我自己写了一个从证书中取得email的X509CertificateCredentialsToDNEmailPrincipalResolver

 

当然这里引用到的类也需要在Spring中进行配置

Xml代码 复制代码
  1. <bean id="x509CredentialsAuthenticationHandler" class="org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler" >  
  2. <property name="trustedIssuerDnPattern" value="CN=intranet.+"/>  
  3. </bean>  
	<bean id="x509CredentialsAuthenticationHandler" class="org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler" >
	<property name="trustedIssuerDnPattern" value="CN=intranet.+"/>
	</bean>

 

这样便可以了.

 

3.Tomcat配置

 

接下来我们便需要将CA的证书加入到servlet容器的TrustStore中.并开启客户端认证.

Xml代码 复制代码
  1. <Connector port="8447" maxHttpHeaderSize="8192"  
  2.               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"  
  3.               enableLookups="false" disableUploadTimeout="true"  
  4.               acceptCount="100" scheme="https" secure="true"  
  5.               clientAuth="want" sslProtocol="TLS"    
  6.               keystoreFile="conf/ssl/keystore.jks" keystorePass="changeit"  
  7.            truststoreFile="conf/ssl/keystore.jks" truststorePass="changeit"  
  8.    />  
	<Connector port="8447" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true"
               clientAuth="want" sslProtocol="TLS" 
               keystoreFile="conf/ssl/keystore.jks" keystorePass="changeit"
			   truststoreFile="conf/ssl/keystore.jks" truststorePass="changeit"
    />

 clientAuth="want"配置成want表示不强求,如果有则使用,配置成true的话那就必须通过证书,否则直接返回404

我们这里为了如果没有证书还可以进去普通登录页面,所以采取了want这个参数.

 

启动CAS ,一切OK了.

 

---------------------------------------------------------------------------------------------

PS:附上从证书中获取Email的代码

Java代码 复制代码
  1. package org.jasig.cas.adaptors.x509.authentication.principal;   
  2.   
  3. import java.security.cert.X509Certificate;   
  4. import org.apache.commons.logging.Log;   
  5. import org.apache.commons.logging.LogFactory;   
  6. import org.bouncycastle.asn1.x509.X509NameTokenizer;   
  7.   
  8. public class X509CertificateCredentialsToDNEmailPrincipalResolver extends AbstractX509CertificateCredentialsToPrincipalResolver {   
  9.   
  10.     public static final String EMAIL = "rfc822name";   
  11.     public static final String EMAIL1 = "email";   
  12.     public static final String EMAIL2 = "EmailAddress";   
  13.     public static final String EMAIL3 = "E";   
  14.     private static final String[] EMAILIDS = { EMAIL, EMAIL1, EMAIL2, EMAIL3 };   
  15.     protected final Log log = LogFactory.getLog(this.getClass());   
  16.   
  17.     @Override  
  18.     protected String resolvePrincipalInternal(X509Certificate certificate) {   
  19.         String email =  getEmailFromDN(certificate.getSubjectDN().getName());   
  20.         dynamicAuthenticationHandler.reload();   
  21.         if(email!=null){   
  22.             return email;   
  23.         }   
  24.         return null;   
  25.     }   
  26.        
  27.   
  28.   
  29.     /**  
  30.      * Convenience method for getting an email address from a DN.  
  31.      * @param dn the DN  
  32.      * @return the found email address, or <code>null</code> if none is found  
  33.      */  
  34.     public  String getEmailFromDN(String dn) {   
  35.         log.info(">getEmailFromDN(" + dn + ")");   
  36.         String email = null;   
  37.         for (int i = 0; (i < EMAILIDS.length) && (email == null); i++) {   
  38.             email = getPartFromDN(dn, EMAILIDS[i]);   
  39.         }   
  40.   
  41.         log.error("<getEmailFromDN(" + dn + "): " + email);   
  42.   
  43.         return email;   
  44.     }   
  45.        
  46.     /**  
  47.      * Gets a specified part of a DN. Specifically the first o
    分享到:
    评论

相关推荐

Global site tag (gtag.js) - Google Analytics