spring-security 3.0.X, 讓ajax login和普通login共存
轉自: http://my.oschina.net/jilujia/blog/66795
使用spring security時遇到一個問題,有大量的ajax post是需要登入控制的,但是預設的spring-security機制導致post結果返回的是登入頁。
現在要解決幾個問題:
1,ajax post如果需要登入的話,返回需要登入的json訊息,前端可以繼續處理
2,新建一套ajax login的頁面流轉,但是不能和原有的login過程衝突,因為其他的非ajax請求還是需要用正常的login。
spring security配置如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
<? xml
version = "1.0"
encoding = "UTF-8" ?>
< beans
xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:security = "http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<!-- Configure Spring Security -->
<!--
<security:http auto-config="true">
<security:form-login login-page="/login" login-processing-url="/loginProcess"
default-target-url="/" authentication-failure-url="/login?login_error=1" /> <security:logout logout-url="/logout" logout-success-url="/logoutSuccess" />
<security:remember-me key="bookingtest" />
</security:http>
-->
< security:http
auto-config = "false"
entry-point-ref = "jilujiaAuthenticationEntryPoint" >
<!-- 登入過濾器 -->
< security:custom-filter
before = "FORM_LOGIN_FILTER"
ref = "loginFilter" />
<!-- ajax登入過濾器 -->
< security:custom-filter
position = "FORM_LOGIN_FILTER"
ref = "ajaxLoginFilter" />
<!-- 只cache get,避免ajax post 被cache -->
< security:request-cache
ref = "httpSessionRequestCache" />
<!-- 登出過濾器 -->
< security:logout
logout-url = "/logout"
logout-success-url = "/logoutSuccess"
/>
<!-- remember me -->
< security:remember-me
key = "bookingtest"
/>
</ security:http >
< bean
id = "jilujiaAuthenticationEntryPoint"
class = "com.jilujia.framework.security.JilujiaAuthenticationEntryPoint" >
< property
name = "loginFormUrl"
value = "/login"
/>
</ bean >
< bean
id = "httpSessionRequestCache"
class = "org.springframework.security.web.savedrequest.HttpSessionRequestCache" >
< property
name = "justUseSavedRequestOnGet"
value = "true"
/>
</ bean >
<!-- 驗證普通使用者 -->
< bean
id = "loginFilter"
class = "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" >
< property
name = "authenticationManager"
ref = "authenticationManager" />
< property
name = "authenticationFailureHandler"
ref = "failureHandler" />
< property
name = "authenticationSuccessHandler"
ref = "successHandler" />
< property
name = "filterProcessesUrl"
value = "/loginProcess" />
</ bean >
< bean
id = "failureHandler"
class = "org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" >
< property
name = "defaultFailureUrl"
value = "/login?login_error=1"
/>
</ bean >
< bean
id = "successHandler"
class = "org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler" >
< property
name = "alwaysUseDefaultTargetUrl"
value = "false" />
< property
name = "defaultTargetUrl"
value = "/" />
</ bean >
<!-- 驗證ajax請求-->
< bean
id = "ajaxLoginFilter"
class = "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" >
< property
name = "authenticationManager"
ref = "authenticationManager" />
< property
name = "authenticationFailureHandler"
ref = "ajaxFailureHandler" />
< property
name = "authenticationSuccessHandler"
ref = "ajaxSuccessHandler" />
< property
name = "filterProcessesUrl"
value = "/ajaxLoginProcess" />
</ bean >
< bean
id = "ajaxFailureHandler"
class = "com.jilujia.framework.security.AjaxAuthenticationFailureHandler" >
</ bean >
< bean
id = "ajaxSuccessHandler"
class = "com.jilujia.framework.security.AjaxAuthenticationSuccessHandler" >
</ bean >
< security:global-method-security
jsr250-annotations = "enabled"
secured-annotations = "enabled"
/>
< security:authentication-manager
alias = "authenticationManager" >
< security:authentication-provider
user-service-ref = "customUserDetailsService" >
< security:password-encoder
ref = "passwordEncoder"
/>
</ security:authentication-provider >
</ security:authentication-manager >
< bean
id = "customUserDetailsService"
class = "com.jilujia.framework.security.JilujiaUserDetailsService" >
< property
name = "dataSource"
ref = "dataSource"
/>
</ bean >
< bean
id = "passwordEncoder"
class = "org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
</ beans >
|
重點有幾個:jilujiaAuthenticationEntryPoint,解決問題1, 這裡區分ajax請求和非ajax請求的方式是uri中包含不包含ajax字串,可以按需調整。
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
public
class JilujiaAuthenticationEntryPoint extends
LoginUrlAuthenticationEntryPoint {
private
static final
Log logger = LogFactory.getLog(JilujiaAuthenticationEntryPoint. class );
private
RedirectStrategy redirectStrategy = new
DefaultRedirectStrategy();
public
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws
IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String redirectUrl =
null ;
String url = request.getRequestURI();
if
(logger.isDebugEnabled()) {
logger.debug( "url:"
+ url);
}
// 非ajax請求
if
(url.indexOf( "ajax" ) == - 1 ) {
if
( this .isUseForward()) {
if
( this .isForceHttps() &&
"http" .equals(request.getScheme())) {
// First redirect the current request to HTTPS.
// When that request is received, the forward to the login page will be used.
redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest);
}
if
(redirectUrl == null ) {
String loginForm = determineUrlToUseForThisRequest(httpRequest, httpResponse, authException);
if
(logger.isDebugEnabled()) {
logger.debug( "Server side forward to: "
+ loginForm);
}
RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return ;
}
}
else {
// redirect to login page. Use https if forceHttps true
redirectUrl = buildRedirectUrlToLoginPage(httpRequest, httpResponse, authException);
}
redirectStrategy.sendRedirect(httpRequest, httpResponse, redirectUrl);
}
else {
// ajax請求,返回json,替代redirect到login page
if
(logger.isDebugEnabled()) {
logger.debug( "ajax request or post" );
}
ObjectMapper objectMapper =
new ObjectMapper();
response.setHeader( "Content-Type" ,
"application/json;charset=UTF-8" );
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
JsonData jsonData =
new JsonData( 2 ,
null );
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch (JsonProcessingException ex) {
throw
new HttpMessageNotWritableException( "Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
}
|
第二個問題,注意配置一個新的過濾器專門處理ajax 請求,這個filter是通過filterProcessesUrl=ajaxLoginProcess來區分ajax login動作和普通login動作的。
<!-- ajax登入過濾器 -->
<security:custom-filter position="FORM_LOGIN_FILTER" ref="ajaxLoginFilter"/>
<bean id="ajaxLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureHandler" ref="ajaxFailureHandler"/>
<property name="authenticationSuccessHandler" ref="ajaxSuccessHandler"/>
<property name="filterProcessesUrl" value="/ajaxLoginProcess"/>
</bean>
同時對應了兩個handler,專門處理ajax登入的成功和失敗,都返回json訊息。
<bean id="ajaxFailureHandler" class="com.jilujia.framework.security.AjaxAuthenticationFailureHandler">
</bean>
<bean id="ajaxSuccessHandler" class="com.jilujia.framework.security.AjaxAuthenticationSuccessHandler">
</bean>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public
class AjaxAuthenticationSuccessHandler implements
AuthenticationSuccessHandler {
public
AjaxAuthenticationSuccessHandler() {
}
public
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
ObjectMapper objectMapper =
new ObjectMapper();
response.setHeader( "Content-Type" ,
"application/json;charset=UTF-8" );
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
//成功為0
JsonData jsonData =
new JsonData( 0 ,
null );
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch (JsonProcessingException ex) {
throw
new HttpMessageNotWritableException( "Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
public
class AjaxAuthenticationFailureHandler implements
AuthenticationFailureHandler {
protected
final Log logger = LogFactory.getLog(getClass());
public
AjaxAuthenticationFailureHandler() {
}
public
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
ObjectMapper objectMapper =
new ObjectMapper();
response.setHeader( "Content-Type" ,
"application/json;charset=UTF-8" );
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(),
JsonEncoding.UTF8);
try
{
//失敗為1
JsonData jsonData =
new JsonData( 1 ,
null );
objectMapper.writeValue(jsonGenerator, jsonData);
}
catch (JsonProcessingException ex) {
throw
new HttpMessageNotWritableException( "Could not write JSON: "
+ ex.getMessage(), ex);
}
}
}
|
ajax login page差不多是這樣:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
< div
id = "inlineLogin"
style = "width:500px;display: none;" >
< form
id = "LoginForm"
action = "<c:url value=" /ajaxLoginProcess" />" method="post">
< fieldset >
< legend >Login Information</ legend >
< p >
< label
for = "j_username" >User:</ label >
< br
/>
< input
type = "text"
name = "j_username"
id = "j_username"
<c:if test = "${not empty param.login_error}" >value="<%= session.getAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY)
%>"</ c:if > />
</ p >
< p >
< label
for = "j_password" >Password:</ label >
< br
/>
< input
type = "password"
name = "j_password"
id = "j_password"
/>
</ p >
< p >
< input
type = "checkbox"
name = "_spring_security_remember_me"
id = "remember_me"
/>
< label
for = "remember_me" >Don't ask for my password for two weeks:</ label >
</ p >
< p >
< a
href = "javascript:loginSubmit()"
id = 'btn_login'
class = 'rndbutton' >< span >Login</ span ></ a >
</ p >
</ fieldset >
</ form >
</ div >
|
1 2 3 4 5 6 7 8 9 10 |
function
loginSubmit(){
var
form = $( '#LoginForm' ).serialize();
$.post( '<c:url value="/ajaxLoginProcess" />' ,form, function (data){
if (data.error == 1)
alert(data.messages);
else
if (data.error == 0){
alert( "success" )
}
});
}
|
特別注意的是配置了一個
<!-- 只cache get,避免ajax post 被cache -->
<security:request-cache ref="httpSessionRequestCache"/>
因為我的環境中所有的post都是ajax,這些都不需要cache。
參考:
http://blog.csdn.net/zjh527/article/details/6158706