`

AJAX Authentication with ACEGI

    博客分类:
  • java
阅读更多
原文链接 http://brian.pontarelli.com/ajax-acegi/index.html

Brian Pontarelli

Abstract

The ACEGI security framework provides a robust authentication and authorization mechanism for applications that use the Spring Framework. However, web applications that leverage AJAX for inline editing have special requirements when it comes to authentication. This article shows how to configure the ACEGI framework to handle inline AJAX login.

The AJAX Login Problem

AJAX applications often allow users to add and edit data inline by replacing parts of the page with forms. This is done by replacing the contents of a DIV or P element with new XHTML retrieved from the server using an AJAX request. The exact steps to get inline AJAX editing working in your application are outside the scope of this article but there are a ton of resources available out on the web to help you get started. This article focuses on the what happens when a user’s session times out while doing inline editing.

Figure 1 and 2 show briefly how inline editing looks to the user. Here are the simple steps that are involved:

  1. The user logs into the application
  2. The user opens up the edit form
  3. The user starts editing and then gets a craving for a venti gingerbread latte
  4. The user returns 45 minutes and one latte later and submits the form

Inline AJAX editing closed
Figure 1: Inline AJAX editing closed

Inline AJAX editing closed
Figure 2: Inline AJAX editing open

Most applications have a session duration of around 30 minutes. The user from the scenario above has obviously lost their session and with it their credentials, which are usually stored in the session. At this point the user must log back into the application in order to continue working, and hopefully the application saves the work they did before they went out for coffee. The issue here is that the processing in the browser is occuring in AJAX and the response from the server is normally displayed within a specific element in the XHTML document (usually a DIV or P element). Therefore, the login process must be complete AJAXified in order to provide the correct user experience.

The General Solution

The general solution to this problem, that is if you have built a custom authentication and authorization package and need to add AJAX support to it, is to create two sets of JSP pages, one for normal login and one for AJAX login. The reason two sets are required is that normal login pages are complete XHTML documents that include HEAD, BODY, and HTML tags. AJAX login does not need the entire XHTML document. Rather an AJAX login form should be just that, only the login form.

Listing 1 illustrates what a simple normal login form might look like, while listing 2 illustrates an AJAX login form. Notice that listing 2 only contains the FORM element and its children, while listing 1 contains an entire XHTML document.

xml 代码
 
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>  
  2. <html>  
  3.   <head><title>Non-AJAX Login form</title></head>  
  4.   <body>  
  5.     <h1>This is the full login page! Login dude!</h1>  
  6.     <form action="/secure/custom_security_check">  
  7.       Username: <input type="text" name="j_username"/><br/>  
  8.       Password: <input type="text" name="j_password"/><br/>  
  9.       <input type="submit" name="Login" value="Login"/>  
  10.     </form>  
  11.   </body>  
  12. </html>  
Listing 1: Normal login form
xml 代码
 
  1. <form action="/secure/custom_ajax_security_check" id="ajax-form">  
  2.   Username: <input type="text" name="j_username"/><br/>  
  3.   Password: <input type="text" name="j_password"/><br/>  
  4.   <input type="submit" name="Login" value="Login"   
  5.      onclick="return ajax_submit_form();"/>  
  6. </form>  
Listing 2: AJAX login form

Once you have have two login pages, one for normal login and one for AJAX login, some mechanism for determining whether or not a request is an AJAX request must be constructed. I prefer using regular expressions whenever possible because of their flexibility, but any mechanism that can distinquish requests based on the incoming URL is all that is needed. It is also important to select a naming standard for AJAX URLs. This will make the process of controlling AJAX authentication much simpler. My standard is to prepend all URLs with ajax for AJAX requests. Figure 3 shows an example URL.

http://www.example.com/profile/ajax_update_info.action

Figure 3: URL example

Usually a servlet filter chain is attached to secured resources to check if the user is logged in. Once you have a naming standard and the AJAX login pages, adding code to this filter to determine if the URL is an AJAX request will determine which set of login pages to take the user to. Listing 3 illustrates how a simple filter method might look.

java 代码
 
  1. public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain)   
  2. throws Exception{  
  3.   if (req.getSession().getAttribute("user") == null) {  
  4.     if (req.get\RequestURI().matches("^ajax.*")) {  
  5.       RequestDispatcher rd = req.get\RequestDispatcher("/login/ajax_login.jsp");  
  6.       rd.forward(req, res);  
  7.     } else {  
  8.       res.sendRedirect("/login/login.jsp");  
  9.     }  
  10.     return;  
  11.   }  
  12. }  
Listing 3: Simple authorization filter Method

ACEGI Solution

The website I was working on when I first came across this issue was http://www.naymz.com and the majority of the editing is done inline. Naymz uses ACEGI (http://www.acegisecurity.org) for authentication and authorization and as it turns out ACEGI handles this situation quite easily. The key to understanding how ACEGI can be configured to handle AJAX authentication is understand how ACEGI handles each request that comes into the servlet container. Each request is passed to an ACEGI filter chain, which is almost identical to the J2EE servlet filter chains most are familiar with. This filter chain is normally defined in the Spring configuration file using the FilterChainProxy bean. This bean is configured by setting up filter chains to respond to specific URL patterns. These patterns are defined either using the Apache Ant syntax or regular expressions. For this article I will be using the the Ant syntax. Listing 4 shows a simple configuration that has a single filter chain for all requests.

xml 代码
 
  1. <bean id="filter\ChainProxy" class="org.acegisecurity.util.FilterChainProxy">  
  2.   <property name="filter\InvocationDefinitionSource">  
  3.     <value>  
  4.       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
  5.       PATTERN_TYPE_APACHE_ANT  
  6.       /**=saved\RequestFilter, ..., authentication\ProcessingFilter, ...,   
  7.       exception\TranslationFilter, ...  
  8.     </value>  
  9.   </property>  
  10. </bean>  
Listing 4: ACEGI configuration

The line that starts with /**= tells the ACEGI framework to send all requests to this filter chain. The chain is defined by a comma separated list of bean ids. Each filter in the chain is called by ACEGI to handle different aspects of the authentication from checking if the user is logged in and taking them to the login page if not, to authentication and authorization. Recall that in order to handle AJAX login correctly we needed a mechanism for determining if a request is an AJAX request. In order to accomplish this in ACEGI, we add an additional line to the FilterChainProxy configuration to construct a new filter chain for AJAX requests. Listing 5 illustrates this configuration.

xml 代码
 
  1. <bean id="filter\ChainProxy" class="org.acegisecurity.util.FilterChainProxy">  
  2.   <property name="filter\InvocationDefinitionSource">  
  3.     <value>  
  4.       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
  5.       PATTERN_TYPE_APACHE_ANT  
  6.       /**/*ajax*=saved\RequestFilter, ..., ajax\AuthenticationProcessingFilter, ...,  
  7.       ajax\ExceptionTranslationFilter, ...  
  8.       /**=saved\RequestFilter, ..., authentication\ProcessingFilter, ...,   
  9.       exception\TranslationFilter, ...  
  10.     </value>  
  11.   </property>  
  12. </bean>  
Listing 5: AJAX FilterChainProxy Configuration

The configuration now tells ACEGI that any URL that contains ajax should use the new filter chain. Now that ACEGI is able to handle AJAX requests separately from normal requests, we need to configure the rest of ACEGI for AJAX. There are two filters in the ACEGI authentication process that will need tweaking for AJAX authentication to work. If you notice from listing 5, the AJAX filter chain defines two new filters whose names contains ajax. These filters handle failed authorization and login and once defined will allow AJAX authentication with ACEGI.

One filter in almost all chains is the ExceptionTranslationFilter. This filter handles the failed authorization step. This step occurs when a resource is being requested that requires the user to be logged in but the user is not yet logged in. In this case the ExceptionTranslationFilter catches an AuthenticationException from a filter farther down the chain and takes the user to the login page. Since AJAX applications have a special login page for AJAX requests, a second ExceptionTranslationFilter must be defined that uses the AJAX login page. ExceptionTranslationFilter is configured by a reference to an EntryPoint, which is the ACEGI version of the login page. Listing 6 shows the configuration for the new ExceptionTranslationFilter and the EntryPoint for the AJAX login page.

xml 代码
 
  1. <bean id="ajax\ExceptionTranslationFilter"   
  2.       class="org.acegisecurity.ui.ExceptionTranslationFilter">  
  3.   <property name="authentication\EntryPoint">  
  4.     <ref local="ajax\AuthenticationEntryPoint"/>  
  5.   </property>  
  6. </bean>  
  7. <bean id="ajax\AuthenticationEntryPoint"  
  8.       class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">  
  9.   <property name="login\FormUrl" value="/login/ajax_login.jsp"/>  
  10. </bean>  
Listing 6: AJAX ExceptionTranslationFilter configuration

Notice that in listing 6 the EntryPoint references the /login/ajax_login.jsp JSP page. This JSP contains the AJAX login for from listing 8 (listing 7 contains the ACEGI normal login form). If you are planning on using ACEGI 1.0.3 or later, a feature has been added so that EntryPoints can be forwarded to rather than redirected to by ACEGI. This can reduce round trips to the server by the browser and speed up AJAX login a bit. To turn this on set the property named serverSideRedirect to false on the AJAX EntryPoint.

xml 代码
 
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>  
  2. <html>  
  3.   <head><title>Non-AJAX Login form</title></head>  
  4.   <body>  
  5.     <h1>This is the full login page! Login dude!</h1>  
  6.     <form action="/secure/j_acegi_security_check">  
  7.       Username: <input type="text" name="j_username"/><br/>  
  8.       Password: <input type="text" name="j_password"/><br/>  
  9.       <input type="submit" name="Login" value="Login"/>  
  10.     </form>  
  11.   </body>  
  12. </html>  
Listing 7: ACEGI normal login form
xml 代码
 
  1. <form action="/secure/j_acegi_ajax_security_check" id="ajax-form">  
  2.   Username: <input type="text" name="j_username"/><br/>  
  3.   Password: <input type="text" name="j_password"/><br/>  
  4.   <input type="submit" name="Login" value="Login"   
  5.      onclick="return ajax_submit_form();"/>  
  6. </form>  
Listing 8: ACEGI AJAX login form

The final step to configuring ACEGI is to configure a new AuthenticationProcessingFilter. This class handles the submission of the login form, validation of the user credentials and either resubmitting the user’s original request, taking the user to a default success page or handling any login errors. There are three configuration parameters used by this filter that will need tweaking. These are authenticationFailureUrl, defaultTargetUrl and filterProcessesUrl.

The filterProcessesUrl parameter controls the URL that is used by the login form. One tricky thing to remember is that the login for is an AJAX inline form and therefore must be handled by our new filter chain. Therefore it must contain ajax somewhere in the URL. A good default URL to use is /secure/j_ajax_acegi_security_check. This URL contains ajax and therefore will be sent to the correct filter chain.

Also, because the AJAX login form is being handled inline, the failure and success pages must also be XHTML snippets so that the login process is rendered correctly by the browser. Therefore, these two configuration parameters must be configured to use special AJAX pages. Listing 9 shows the AJAX AuthenticationProcessingFilter configuration.

xml 代码
 
  1. <bean id="ajax\AuthenticationProcessingFilter"  
  2.       class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">  
  3.   <property name="authenticationManager" ref="authenticationManager"/>  
  4.   <property name="authentication\FailureUrl" value="/login/ajax_login_failed.jsp"/>  
  5.   <property name="default\TargetUrl" value="/login/ajax_login_successful.jsp"/>  
  6.   <property name="filter\ProcessesUrl" value="/secure/j_ajax_acegi_security_check"/>  
  7. </bean>  
Listing 9: AJAX AuthenticationProcessingFilter configuration

Notice that listing 9 defines special AJAX JSPs for failed and successful login. These JSPs, like the AJAX login JSP, contain only XHTML snippets and are show in listing 10.

xml 代码
 
  1. <!-- ajax_login_failed.jsp -->  
  2. Login FAILED!<br/>  
  3. <form action="/secure/j_ajax_acegi_security_check" id="ajax-form">  
  4.   Username: <input type="text" name="j_username"/><br/>  
  5.   Password: <input type="text" name="j_password"/><br/>  
  6.   <input type="submit" name="Login" value="Login" onclick="return ajax_submit_form();"/>  
  7. </form>  
  8.   
  9. <!-- ajax_login_successful.jsp -->  
  10. Login successful!<br/>  
Listing 10: AJAX success and failure JSPs

Saved Request

One final thing I should mention is that ACEGI 1.x handles saving the users original request during the login process so that it can be re-executed once the user has logged in successfully. This mechanism uses redirects for normal requests and normal login. Using the configuration described above to achieve AJAX login, any application that wishes to use the saved request feature of ACEGI for AJAX login can. Browsers handle redirects within AJAX processing in the same manner as they do for normal requests and therefore after the user has successfully logged in, the AJAX processing will handle the saved request. As long as the AJAX filter chain is configured for saved requests, no extra configuration is required.

Conclusion

AJAX applications allow users a better experience and can greatly reduce the bandwidth and processing power required of servers. However, AJAX applications suffer from the fact that if user sessions expire and their credentials are lost, they must have a method of re-logging into the application within the bounds of the AJAX processing that they are currently performing. This mechanism is simple to implement by hand or with the ACEGI security framework. Using the configuration above, the ACEGI framework can easily and quickly handle AJAX authentication allowing users to login using an AJAX inline form. Furthermore, ACEGI’s saved request feature can be configured so that work that is done inline and submitted via AJAX is not lost if the users session expires.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics