【发布时间】:2015-04-24 12:39:54
【问题描述】:
我的一个项目的 Spring Security Authentication 存在问题。
这是我项目的技术架构:
- TIBCO BPM 公开 Web 服务(我没有能力 修改这部分,因为它是供应商
- bpm-wrapper-ws : CXF Web 服务,可简化和公开一些 BPM Web 服务调用。通过以下方式使用 Spring Security 进行保护:CAS, 用户名令牌和基本身份验证。
bpm-wrapper-ws 可以通过以下方式调用:
- 通过 Web 应用程序通过 CAS 身份验证的用户
- 通过默认用户名令牌的程序
- 通过 BPM。在这种情况下,它有点复杂,因为我需要对用户进行身份验证,但 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 创建了一个工作队列(因为它是一个新线程),如果没有上面的行,我就没有身份验证(SecurityContextHolder.getContext().getAuthentication() 返回 null)。
第一个问题:我说的对吗?
我的问题是,有时经过身份验证的用户与不同类型的身份验证混为一谈。 示例:
- 用户A调用web服务bpm-wrapper的method1
- 同时,用户B调用了web服务的method2 bpm 包装器
我有一个处理 BPM 调用的事件收集器,我看到:userB 完成方法 1 和 userA 完成方法 2
你知道我的问题在哪里吗?以及如何纠正这个问题?
问候,
杰里米
ws-security-context.xml : Spring 安全配置文件
<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 :保存登录的类 当前登录的用户(仅用于带有 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-standalone-context.xml : 全局spring配置文件
<!-- 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>
【问题讨论】:
标签: spring authentication spring-security cxf