仔细看过Spring BlazeDS Integration文档,我们知道,配置spring security其实很简单。
如下:
<flex:message-broker>
<flex:secured />
</flex:message-broker>
即可。但是这小小的<flex:secured /> 标记,其后台初始化了很多instance,其中最重要的就是
org.springframework.flex.security3.SpringSecurityLoginCommand 类,该类存在spring-flex-core-1.5.2.RELEASE.jar中,
之前提到过,等flex ui调用login api,请求会进入SpringSecurityLoginCommand.doAuthentication(String username, Object credentials)方法
我们来看看其具体实现:
public Principal doAuthentication(String username, Object credentials) {
HttpServletRequest request = FlexContext.getHttpRequest();
HttpServletResponse response = FlexContext.getHttpResponse();
try {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, extractPassword(credentials));
setDetails(request, authRequest);
Authentication authentication = this.authManager.authenticate(authRequest);
if (authentication != null) {
if (!isPerClientAuthentication() && request != null && response != null) {
this.sessionStrategy.onAuthentication(authentication, request, response);
this.rememberMeServices.loginSuccess(request, response, authentication); //注意这里
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
return authentication;
} catch (AuthenticationException ex) {
SecurityContextHolder.clearContext();
if (request != null && response != null && !isPerClientAuthentication()) {
this.rememberMeServices.loginFail(request, response);
}
throw ex;
}
}
可见此方法,让flex的login api 和spring sceurity 做了集成。让即使是flex ui传递过来的login请求也会走入到rememberMeServices当中去。
所以可以在rememberMeServices里做文章,找到用户登录成功后的切入点去,编写特殊逻辑。
修改security-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http entry-point-ref="entryPoint">
<anonymous enabled="false" />
<form-login login-page="/login.jsp"
authentication-success-handler-ref="simpleLoginSuccessHandler" />
<remember-me key="testdrive" services-ref="rememberMeServices" />
</http>
<beans:bean id="rememberMeServices" class="test.MyRememberMeServices">
<beans:property name="key" value="testdrive" />
<beans:property name="alwaysRemember" value="true" />
</beans:bean>
<beans:bean id="entryPoint"
class="org.springframework.flex.security3.FlexAuthenticationEntryPoint" />
<beans:bean id="simpleLoginSuccessHandler" class="test.SimpleLoginSuccessHandler">
<beans:property name="defaultTargetUrl" value="/secured/secured.html"></beans:property>
<beans:property name="forwardToDestination" value="false"></beans:property>
</beans:bean>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="john" password="john" authorities="ROLE_USER" />
<user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="guest" password="guest" authorities="ROLE_GUEST" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
自定义的rememberMeServices如下:
package test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.flex.samples.contact.IContactDAO;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
public class MyRememberMeServices extends TokenBasedRememberMeServices {
@Autowired
private IContactDAO contactDAO;
@Override
public void onLoginSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication successfulAuthentication) {
super.onLoginSuccess(request, response, successfulAuthentication);
//SecurityContextHolder.getContext().setAuthentication(successfulAuthentication);
System.out.println("Login Success:"+ contactDAO.findAll()); //登陆成功后,在这里实现自己的特殊逻辑
}
}
注意:
- rememberMeServices.loginSuccess(request, response, authentication); 不允许覆盖,
- 但是loginSuccess()会调用onLoginSuccess() 方法,onLoginSuccess() 方法是允许覆盖的!
- 自定义的rememberMeServices, 不管是http form提交,还是flex UI api提交,都会被调用。
- 如果是http form提交,其调用顺序,会在<form-login login-page="/login.jsp"authentication-success-handler-ref="simpleLoginSuccessHandler" /> 之后被调用
特别注意,如果在MyRememberMeServices.onLoginSuccess()中,所调用的dao方法是,被spring security所管理的话,当通过flex ui 登陆时,是会抱错的:
“Caused by: org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext"
两个解决办法,任选其一:
- 让所调用的dao方法,不被spring security管理控制,spring security管理控制dao的上一级service层。
- 或者,在调用dao方法之前,加上此语句:SecurityContextHolder.getContext().setAuthentication(successfulAuthentication);
PS:org.springframework.flex.security3.SpringSecurityLoginCommand 类 其实也可以继承
然后在自己订制化的LoginCommand类,去找到登陆成功后的切入点。但是本人感觉很复杂,还没研究明白。
具体请看:http://docs.spring.io/spring-flex/docs/1.5.x/reference/html/#custom-login-command