我有一個與我的項目之一的Spring Security身份驗證問題。[春季安全] [CXF]混合身份驗證
這裏是我的項目的技術architeture:
- TIBCO BPM其公開Web服務(我沒有能力 修改這一部分,因爲它是一個供應商
- BPM-包裝-WS :CXF的Web服務,簡化並顯露出一些 的BPM Web服務調用使用Spring Security通過安全:CAS, 用戶名令牌和基本身份驗證
BPM-包裝-ws可以被稱爲或者通過:
- 一個CAS通過BPM認證的用戶通過web應用
- 的程序通過一個默認的UsernameToken
- 。在這種情況下,它有點複雜,因爲我需要驗證用戶,但BPM只能處理WSS(使用默認用戶名)。 因此,我使用WSS身份驗證來驗證呼叫,然後使用自定義代碼自己設置已驗證的用戶。
例子:
public void completeWorkitemForProcess(@WebParam(name = "processId") String processId, @WebParam(name = "workItemName") String workItemName, @WebParam(name= "username") String username) {
// Authenticated with a generic user
// Setting the login of the user passed in the parameters of the method
TibcoAuthenticationHolder.setLogin(username);
final WorkItemSearchCriteria searchCriteria = new WorkItemSearchCriteria();
searchCriteria.setProcessId(processId);
searchCriteria.setWorkItemName(workItemName);
searchCriteria.setFirstResult(0);
searchCriteria.setMaxResults(1);
final SearchResult<WorkItem> result = findWorkItems(userGuid, username, searchCriteria);
if (result != null && result.getTotalRecords() == 1) {
WorkItem workItem = result.getResult().get(0);
if (workItem.getState() == WorkItemState.OFFERED) {
workItem = openWorkitem(userGuid, workItem.getId());
}
// do the TIBCO BPM call (use ClientPasswordCallback and BpmAuthenticator)
completeWorkitem(workItem);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("completeWorkitemForProcess - aucun workItem trouvé [username=" + username
+ ", processId=" + processId + ", workItemName=" + workItemName + "]");
}
}
// Delete the authentication
TibcoAuthenticationHolder.clear();
SecurityContextHolder.clearContext();
}
我不得不添加以下行,以便能夠做到的BPM完整的呼叫:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass"
value="org.springframework.security.core.context.SecurityContextHolder" />
<property name="targetMethod" value="setStrategyName" />
<property name="arguments">
<list>
<value>MODE_INHERITABLETHREADLOCAL</value>
</list>
</property>
</bean>
顯然,問題是,CXF創建一個workqueue(因爲它是一個新的線程),其中沒有上面的行我沒有認證(SecurityContextHolder.getContext()。getAuthentication()返回null)。
第一個問題:我對嗎?
我的問題是,有時認證用戶會混淆不同種類的認證。 實施例:
- 用戶A調用web服務BPM-包裝
- 在同一時間的方法1,用戶B調用Web服務的方法2 BPM-包裝
我有處理BPM調用的事件收集器,我看到:userB完成method1和userA完成方法2
您知道我的問題在哪裏嗎?以及如何糾正這一點?
問候,
傑里米
的WS-Security上下文。XML:彈簧安全配置文件
<sec:http authentication-manager-ref="authenticationManager"
use-expressions="true">
<sec:http-basic />
<sec:intercept-url pattern="/**"
access="isAuthenticated() and hasRole('ACCES_APP')" />
<sec:custom-filter ref="casAuthenticationFilter"
position="CAS_FILTER" />
<sec:custom-filter ref="usernameTokenAuthenticationFilter"
after="BASIC_AUTH_FILTER" />
</sec:http>
<sec:authentication-manager alias="authenticationManager"
erase-credentials="false">
<sec:authentication-provider ref="casAuthenticationProvider" />
<sec:authentication-provider
user-service-ref="inMemoryUserService">
<sec:password-encoder base64="true" ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass"
value="org.springframework.security.core.context.SecurityContextHolder" />
<property name="targetMethod" value="setStrategyName" />
<property name="arguments">
<list>
<value>MODE_INHERITABLETHREADLOCAL</value>
</list>
</property>
</bean>
<bean id="usernameTokenAuthenticationFilter"
class="com.agipi.spring.commons.security.wss.filter.UsernameTokenAuthenticationFilter">
<constructor-arg ref="authenticationManager" />
</bean>
<bean id="casAuthenticationFilter"
class="com.agipi.spring.commons.security.cas.filter.WebServiceCasAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="serviceProperties" ref="serviceProperties" />
<property name="proxyGrantingTicketStorage" ref="proxyGrantingTicketStorage" />
<property name="proxyReceptorUrl" value="/j_spring_cas_security_proxyreceptor" />
<property name="authenticationDetailsSource">
<bean
class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource" />
</property>
</bean>
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService" ref="userDetailsService" />
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator" ref="proxyTicketValidator" />
<property name="key" value="an_id_for_this_auth_provider_only" />
<property name="statelessTicketCache">
<bean
class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
<property name="cache">
<bean class="net.sf.ehcache.Cache" init-method="initialise"
destroy-method="dispose">
<constructor-arg value="casTickets" />
<constructor-arg value="50" />
<constructor-arg value="false" />
<constructor-arg value="false" />
<constructor-arg value="3600" />
<constructor-arg value="900" />
</bean>
</property>
</bean>
</property>
</bean>
<bean id="proxyTicketValidator"
class="com.agipi.spring.commons.security.cas.validator.ProxyTicketValidator">
<constructor-arg index="0" value="${cas.url}" />
<property name="proxyCallbackUrl" value="${proxy.callback.url}" />
<property name="proxyGrantingTicketStorage" ref="proxyGrantingTicketStorage" />
<property name="acceptAnyProxy" value="true" />
<property name="customParameters">
<util:map>
<entry key="profil" value="${cas.profil}" />
<entry key="ptr" value="j_spring_cas_security_proxyreceptor" />
</util:map>
</property>
</bean>
<bean id="proxyGrantingTicketStorage"
class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl" />
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
<property name="service" value="${service.url}" />
<property name="sendRenew" value="false" />
<property name="authenticateAllArtifacts" value="true" />
</bean>
<bean id="userDetailsService"
class="com.agipi.spring.commons.security.service.impl.UserDetailsServiceImpl">
<property name="userDetailsClass"
value="com.agipi.spring.commons.security.domain.DefaultUserDetails" />
</bean>
<bean id="inMemoryUserService"
class="com.agipi.spring.commons.security.service.impl.InMemoryUserDetailsServiceImpl">
<constructor-arg ref="usersProps" />
<constructor-arg>
<list>
<value>service.pid</value>
<value>fabric.zookeeper.pid</value>
</list>
</constructor-arg>
</bean>
<bean id="passwordEncoder"
class="org.springframework.security.authentication.encoding.PlaintextPasswordEncoder" />
TibcoAuthenticationHolder:類保持所述 當前登錄用戶(僅在SOAP消息的情況下,使用具有 WSS報頭)的登錄:
public class TibcoAuthenticationHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new InheritableThreadLocal<String>();
public static boolean hasLogin() {
return StringUtils.isNotBlank(CONTEXT_HOLDER.get());
}
public static String getLogin() {
return CONTEXT_HOLDER.get();
}
public static void setLogin(String login) {
CONTEXT_HOLDER.set(login);
}
public static void clear() {
CONTEXT_HOLDER.remove();
}
}
BpmAuthenticator :重新獲得當前認證用戶的實用程序類別(登錄名/密碼)
public String[] getCurrentCredentials() {
String[] currentCredentials = null;
// Hack permettant d'authentifier un utilisateur ayant appelé le service depuis un process BPM
if (TibcoAuthenticationHolder.hasLogin()) {
currentCredentials = new String[2];
currentCredentials[0] = StringUtils.lowerCase(TibcoAuthenticationHolder.getLogin());
currentCredentials[1] = globalPassword;
} else {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && springAuthentication) {
if (authentication instanceof CasAuthenticationToken) {
currentCredentials = new String[2];
currentCredentials[0] = StringUtils.lowerCase(authentication.getName());
currentCredentials[1] = globalPassword;
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
currentCredentials = new String[2];
currentCredentials[0] = StringUtils.lowerCase(authentication.getName());
currentCredentials[1] = globalPassword;
}
}
}
return currentCredentials;
}
ws-standalon電子context.xml中:全局彈簧配置文件
<!-- Web services -->
<bean id="workItemService" class="com.agipi.bpm.wrapper.service.impl.WorkItemServiceImpl" />
<bean id="processService" class="com.agipi.bpm.wrapper.service.impl.ProcessServiceImpl" />
<bean id="orgModelService" class="com.agipi.bpm.wrapper.service.impl.OrgModelServiceImpl" />
<bean id="userService" class="com.agipi.bpm.wrapper.service.impl.UserServiceImpl" />
<!-- Intercepteur pour supprimer les headers WSS -->
<bean id="wssHeaderInterceptor" class="com.agipi.bpm.wrapper.util.WssHeaderInterceptor" />
<!-- JAX-WS Service Endpoint -->
<jaxws:endpoint id="workItemWs" implementor="#workItemService"
address="${ws.workitem.url}">
<jaxws:inInterceptors>
<ref bean="wssHeaderInterceptor" />
</jaxws:inInterceptors>
<jaxws:dataBinding>
<bean class="com.agipi.commons.xml.CustomJAXBDataBinding">
<property name="basePackage" value="com.agipi.bpm" />
</bean>
</jaxws:dataBinding>
</jaxws:endpoint>
<jaxws:endpoint id="processWs" implementor="#processService"
address="${ws.process.url}">
<jaxws:inInterceptors>
<ref bean="wssHeaderInterceptor" />
</jaxws:inInterceptors>
<jaxws:dataBinding>
<bean class="com.agipi.commons.xml.CustomJAXBDataBinding">
<property name="basePackage" value="com.agipi.bpm" />
</bean>
</jaxws:dataBinding>
</jaxws:endpoint>