Skip to main content

Using the @ServletSecurity annotation in JavaEE 6

Posted by kumarjayanti on December 24, 2009 at 2:42 AM PST

Shing Wai's post explains the @ServletSecurity annotation that has been introduced newly in JavaEE 6 (Servlet 3.0 specification). The @ServletSecurity annotation provides an alternative mechanism for defining access control constraints equivalent to those that could otherwise have been expressed declaratively via security-constraint elements in the portable deployment descriptor. There is also a Programmatic approach for adding security (via ServletRegistration.Dynamic API) that has been introduced newly in JavaEE 6, i will talk about it in another post.

Using the @ServletSecurity annotation one can now have a descriptor free secure WebApplication in Glassfish V3. And using the fact that the default mode in calls to enterprise beans from web applications is for the security identity of a web user to be propagated to the EJB container, we can actually have a secure enterprise application without any security information in the portable deployment descriptors.

So let us define an example Servlet which invokes a Secure EJB (A currency converter bean). The ServletSecurity annotation on the servlet below is such that any authenticated user in role "TutorialUser" or "guest" is allowed access to the Servlet. However the Converter EJB would only allow a user in role "TutorialUser" to access its methods. The ServletSecurity annotation below is equivalent to the following element.

<security-constraint>

<web-resource-collection>

<url-pattern>/TutorialServletBASIC</url-pattern>

</web-resource-collection>

<auth-constraint>

<security-role-name>TutorialUser</security-role-name>

<security-role-name>guest</security-role-name>

</auth-constraint>

</security-constraint>

package test;



import java.io.IOException;

import java.io.PrintWriter;

import java.math.BigDecimal;

import javax.ejb.EJB;

import javax.servlet.ServletException;

import javax.servlet.annotation.HttpConstraint;

import javax.servlet.annotation.ServletSecurity;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



@WebServlet(name="TutorialServletBASIC", urlPatterns={"/TutorialServletBASIC"})

//for all HTTP methods, auth-constraint requiring membership in Role TutorialUser or guest

@ServletSecurity(@HttpConstraint(rolesAllowed = {"TutorialUser", "guest"}))

public class TutorialServletBASIC extends HttpServlet {

    @EJB

    private ConverterBean converterBean;

    /**

     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.

     * @param request servlet request

     * @param response servlet response

     * @throws ServletException if a servlet-specific error occurs

     * @throws IOException if an I/O error occurs

     */

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)

    throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();

        try {

           

            out.println("<html>");

            out.println("<head>");

            out.println("<title>Servlet TutorialServletBASIC</title>"); 

            out.println("</head>");

            out.println("<body>");

            BigDecimal result = converterBean.dollarToYen(new BigDecimal("1.0"));

            out.println("<h1>Servlet TutorialServlet result of dollarToYen= " + result + "</h1>");

            out.println("</body>");

            out.println("</html>");

           

        } finally {

            out.close();

        }

    }

   

    @Override

    protected void doGet(HttpServletRequest request, HttpServletResponse response)

    throws ServletException, IOException {

        processRequest(request, response);

    }



    @Override

    protected void doPost(HttpServletRequest request, HttpServletResponse response)

    throws ServletException, IOException {

        processRequest(request, response);

    }

}

 

Now let's look at the NoInterface Converter EJB, which only allows TutorialUser roles to invoke its methods.

 


package test;



import java.math.BigDecimal;

import javax.ejb.*;

import java.security.Principal;

import javax.annotation.Resource;

import javax.ejb.SessionContext;

import javax.annotation.security.DeclareRoles;

import javax.annotation.security.RolesAllowed;



@Stateless()

@DeclareRoles("TutorialUser")

public class ConverterBean {



    @Resource

    SessionContext ctx;

    private BigDecimal yenRate = new BigDecimal("96.0650");

    private BigDecimal euroRate = new BigDecimal("0.0078");



    @RolesAllowed("TutorialUser")

    public BigDecimal dollarToYen(BigDecimal dollars) {

        BigDecimal result = new BigDecimal("0.0");

        Principal callerPrincipal = ctx.getCallerPrincipal();

        if (ctx.isCallerInRole("TutorialUser")) {

            result = dollars.multiply(yenRate);

            return result.setScale(2, BigDecimal.ROUND_UP);

        } else {

            return result.setScale(2, BigDecimal.ROUND_UP);

        }

    }



    @RolesAllowed("TutorialUser")

    public BigDecimal yenToEuro(BigDecimal yen) {

        BigDecimal result = new BigDecimal("0.0");

        Principal callerPrincipal = ctx.getCallerPrincipal();

        if (ctx.isCallerInRole("TutorialUser")) {

            result = yen.multiply(euroRate);

            return result.setScale(2, BigDecimal.ROUND_UP);

        } else {

            return result.setScale(2, BigDecimal.ROUND_UP);

        }

    }

}

 

 We are done with the application, there is no need to add any security information to any deployment descriptors. You will ofcourse need to go to the admin console and enable the "Default Principal to Role Mapping" feature  (Configuration -> Security tab) and create two file-users who are assigned to groups named "guest" and "TutorialUser" respectively.

 

Then build the attached project using Netbeans 6.8 M2 (or latest 6.8 builds) and deploy on GlassFish V3. Then you can try accessing the Servlet URL :

http://localhost:8080/TutorialEAR-war/TutorialServletBASIC

Your browser would then pop-up the BASIC authentication username-password dialog.  You will see that if you specify a user in group TutorialUser as the username then the servlet can successfully invoke the Converter Bean and return a result, but when you login as  user in the "guest" group,  it would throw the following exception when the servlet tries to invoke the  EJB.

 


Caused by: javax.ejb.AccessLocalException: Client not authorized for this invocation.

        at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1801)

        at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:188)

 

One question that would come to mind is where did we specify BASIC authentication for the Servlet . The answer is that the RI establishes BASIC authentication as the default when no auth-method is specified, but the request is for an auth-constrained resource.

AttachmentSize
TutorialEAR.zip40.23 KB