1. 程式人生 > >spring-security 3.0.X, 讓ajax login和普通login共存

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