问题
Spring security (2.0.x) http namespace, form-login definition automatically uses AuthenticationProcessingFilter.
<form-login login-page='/logon.jsp'
default-target-url='/home.jsp'
always-use-default-target='true' />
I also know that If I set auto-config="false" I can customise authentication by providing custom bean definition.
I have CustomAuthenticationProcessingFilter that extends AuthenticationProcessingFilter overrides obtainUsername and uses custom logic to get username than the one passed.
protected String obtainUsername(HttpServletRequest request) {
// custom logic to return username from parameter/cookies/header etc ...
}
Is it possible to use CustomAuthenticationProcessingFilter while still using auto-config="true" <form-login> without needing to define customAuthFilter and all dependent beans ?
<beans:bean id="customAuthFilter" class="x.y.z.CustomAuthenticationProcessingFilter">
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER" />
<beans:property name="defaultTargetUrl" value="/home.jsp"></beans:property>
...
...
</beans:bean>
回答1:
Intro
Spring Security 2.0 is in maintenance mode, so there are not going to be any official updates to it. There are, however, a few approaches you can use to get around this problem.
BeanPostProcessor
A trick you can use from the Spring Security FAQ is to use a BeanPostProcessor. Instead of modifying a property, you can return your custom Filter. An example might be something like this:
public class CustomFilterBeanPostProcessor implements BeanPostProcessor {
private Filter customFilter;
public Object postProcessAfterInitialization(Object bean, String name) {
if (bean instanceof AuthenticationProcessingFilter) {
return customFilter;
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String name) {
return bean;
}
public void setFilter(Filter filter) {
this.customFilter = filter;
}
}
Then your configuration would include the following:
<beans:bean class="CustomFilterBeanPostProcessor">
<beans:property name="filter" ref="customAuthFilter"/>
</beans:bean>
Use before attribute
An alternative is to insert the custom Filter before the AuthenticationProcessingFilter. This will have an additional Filter, but it should be minimally invasive since it is small and should not ever be reached (i.e. since the custom Filter only continues the FilterChain when the AuthenticationProcessingFilter ignores the request). An example configuration using this approach can be seen below:
<beans:bean id="customAuthFilter" class="x.y.z.CustomAuthenticationProcessingFilter">
<custom-filter before="AUTHENTICATION_PROCESSING_FILTER" />
<beans:property name="defaultTargetUrl" value="/home.jsp"></beans:property>
...
...
</beans:bean>
回答2:
Alas, As it appears (If I am not wrong) nothing much could be done as AuthenticationProcessingFilter class name is hardcoded in <HttpSecurityBeanDefinitionParser> :(
if (formLoginElt != null || autoConfig) {
FormLoginBeanDefinitionParser parser =
new FormLoginBeanDefinitionParser("/j_spring_security_check",
"org.springframework.security.ui.webapp.AuthenticationProcessingFilter");
.
.
It would have been nicer if filter class was a config attribute and be controlled externally (just like default-target-url)
may be using attribute authentication-filter-class
<form-login login-page='/logon.jsp'
default-target-url='/home.jsp'
always-use-default-target='true'
authentication-filter-class='x.y.z.CustomAuthenticationProcessingFilter'
/>
Hope Spring folks are listening ;)
回答3:
The fact is that spring's namespace handler internally defines bean with the name _formLoginFilter for AuthenticationProcessingFilter (See for BeanIds complete list). There are coulpe of ways to workaround with this issue (i.e to authenticate using something other than j_username from DaoAuthenticationProvider , like say take username from header etc... )
Use Spring AOP bean() syntax to intercept doFilter()
Define a pointcut that looks for bean with name _formLoginFilter and intercepts doFiltermethod. ( AuthenticationProcessingFilter.doFilter() method) and conditionally delegate to something else
public class AuthenticationProcessingFilterAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationProcessingFilterAspect.class);
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
LOGGER.info("intercept------------------{}",pjp.toLongString());
//Delegate to customised method instead of default pjp.proceed()
return pjp.proceed();
}
}
Config
<beans:bean id="authFilterAspect" class="x.y.z.AuthenticationProcessingFilterAspect" />
<aop:config>
<aop:aspect ref="authFilterAspect">
<aop:around pointcut="bean(_formLoginFilter) && execution(* doFilter(..))" method="intercept"/>
</aop:aspect>
</aop:config>
Use CustomWebAuthenticationDetails to do authentication
Define a bean postprocessor for AuthenticationProcessingFilter bean that injects CustomWebAuthenticationDetails which populates custom fields
public class AuthenticationProcessingFilterBeanPostProcessor implements
BeanPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationProcessingFilterBeanPostProcessor.class);
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if ("_formLoginFilter".equals(beanName) && bean instanceof AuthenticationProcessingFilter) {
AuthenticationProcessingFilter filter = (AuthenticationProcessingFilter) bean;
WebAuthenticationDetailsSource source = (WebAuthenticationDetailsSource) filter.getAuthenticationDetailsSource();
source.setClazz(CustomWebAuthenticationDetails.class);
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@SuppressWarnings("serial")
public static class CustomWebAuthenticationDetails extends
WebAuthenticationDetails {
private String customAttribute;//customfield
public CustomWebAuthenticationDetails(HttpServletRequest request) {
super(request);
//Build custom attributes that could be used elsewhere (say in DaoAuthenticationProvider )
//with (CustomWebAuthenticationDetails)authentication.getDetails()
customAttribute = request.getHeader("username");
}
public boolean getCustomAttribute() {
return customAttribute;
}
}
}
Config
<beans:bean id="authFilterProcessor" class="x.y.z.AuthenticationProcessingFilterBeanPostProcessor" />
Use thread bound request to do actual authentication (within DaoAuthenticationProvider)
Use getHttpServletRequest() to access threadbound request object and use request.getHeader("username") to do custom authentication.
public static HttpServletRequest getHttpServletRequest(){
return((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
Also need to Define this in web.xml if request is not through DispatcherServlet
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/j_spring_security_check</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/j_spring_security_logout</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
If its faces application use FacesContext.getCurrentInstance()
public static HttpServletRequest getHttpServletRequest(){
FacesContext context = FacesContext.getCurrentInstance();
return (HttpServletRequest) context.getExternalContext().getRequest();
}
来源:https://stackoverflow.com/questions/12261620/using-custom-authenticationprocessingfilter-with-form-login-auto-config-true