Question

I'm using GlassFish server 4.0 in which I have assigned different authorities/roles to different users.

A user may have multiple authorities/roles. For example, an admin user may be associated with ROLE_ADMIN (to perform administrative tasks) and ROLE_USER (to perform tasks as a registered user).

In my web.xml, this is configured as follows.

<security-constraint>
    <display-name>AdminConstraint</display-name>
    <web-resource-collection>
        <web-resource-name>ROLE_ADMIN</web-resource-name>
        <description/>
        <url-pattern>/admin_side/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <description/>
        <role-name>ROLE_ADMIN</role-name>
    </auth-constraint>
    <user-data-constraint>
        <description/>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<security-constraint>
    <display-name>UserConstraint</display-name>
    <web-resource-collection>
        <web-resource-name>ROLE_USER</web-resource-name>
        <description/>
        <url-pattern>/user_side/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <description/>
        <role-name>ROLE_USER</role-name>
    </auth-constraint>
    <user-data-constraint>
        <description/>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<login-config>
    <!--<auth-method>DIGEST</auth-method>-->
    <auth-method>FORM</auth-method>
    <realm-name>projectRealm</realm-name>
    <form-login-config>
        <form-login-page>/utility/Login.jsf</form-login-page>
        <form-error-page>/utility/ErrorPage.jsf</form-error-page>
    </form-login-config>
</login-config>

<security-role>
    <description/>
    <role-name>ROLE_ADMIN</role-name>
</security-role>

<security-role>
    <description/>
    <role-name>ROLE_USER</role-name>
</security-role>

This works just fine.


There are two URL patterns /admin_side/* and /user_side/*. The administrator has two roles ROLE_ADMIN and ROLE_USER.

When the administrator logs in using the authority ROLE_USER, the only resources located in /user_side/* should be accessed. The resources located in /admin_side/* should be forbidden from being accessed because the admin is logged in as a registered user and not as an admin.

Until now what happens in my case is that when admin logs in using any of the authorities, the resources in both the locations can be accessed which is perfectly illegal. It is because the system is able to locate both the authorities for that particular user.

How to have each user access resources at a specific location according to their authority/role?


The authentication filter:

@WebFilter(filterName = "SecurityCheck", urlPatterns = {"/jass/*"})
public final class SecurityCheck implements Filter
{
    private FilterConfig filterConfig = null;

    @Resource(mappedName="jms/destinationFactory")
    private ConnectionFactory connectionFactory;
    @Resource(mappedName="jms/destination")
    private Queue queue;
    @EJB
    private final UserBeanLocal userService=null;

    public SecurityCheck() {}

    private void sendJMSMessageToDestination(String message) throws JMSException
    {
        Connection connection = null;
        Session session = null;

        try
        {
            connection = connectionFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(queue);
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText(message);
            messageProducer.send(textMessage);
        }
        finally
        {
            if(session!=null){session.close();}
            if(connection!=null){connection.close();}
        }
    }

    private void doBeforeProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException
    {
        HttpServletRequest httpServletRequest=(HttpServletRequest)request;
        httpServletRequest.login(httpServletRequest.getParameter("userName"), httpServletRequest.getParameter("password"));
    }

    private void doAfterProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException, JMSException
    {
        HttpServletRequest httpServletRequest=(HttpServletRequest)request;
        HttpServletResponse httpServletResponse=(HttpServletResponse)response;
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        Map<String, Object> sessionMap = externalContext.getSessionMap();

        if(httpServletRequest.isUserInRole("ROLE_USER"))
        {
            sendJMSMessageToDestination(httpServletRequest.getLocalName());
            UserTable userTable = userService.setLastLogin(httpServletRequest.getParameter("userName"));
            userTable.setPassword(null);
            sessionMap.put("userName", userTable!=null?userTable.getFirstName():"Unknown");
            sessionMap.put("user", userTable);

            httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            httpServletResponse.setHeader("Pragma", "no-cache");
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.sendRedirect("../user_side/Home.jsf");
        }
        else if(httpServletRequest.isUserInRole("ROLE_ADMIN"))
        {
            sendJMSMessageToDestination(httpServletRequest.getLocalName());
            UserTable userTable = userService.setLastLogin(httpServletRequest.getParameter("userName"));
            userTable.setPassword(null);
            sessionMap.put("adminName", userTable!=null?userTable.getFirstName():"Unknown");
            sessionMap.put("user", userTable);

            httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            httpServletResponse.setHeader("Pragma", "no-cache");
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.sendRedirect("../admin_side/Home.jsf");
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        try
        {
            doBeforeProcessing(request, response);
        }
        catch (Exception e)
        {
            HttpServletResponse httpServletResponse=(HttpServletResponse)response;
            //FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "Incorrect user name and/or password. Access denied."));
            httpServletResponse.sendRedirect("../utility/Login.jsf");
            return;
        }

        chain.doFilter(request, response);

        try
        {
            doAfterProcessing(request, response);
        }
        catch (JMSException ex)
        {
            Logger.getLogger(SecurityCheck.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    //The rest of the filter.
}

If you need to see other things in my application then, please let me know.

Was it helpful?

Solution

You seem to think that when an user has multiple roles, then this user can login using only one of those roles simultaneously. This is not true. Users are not logged in on a per-role basis. Users are logged in on a per-user basis. If an user has multiple roles, then they will all be used and applied throughout the entire login session.

Actually, it is not possible to let an user pick and use only one of the assigned roles throughout the session. So far it sounds too much like that your admin shouldn't have the ROLE_USER in first place. But this makes in real world little sense. Roles aren't supposed to "extend" existing roles. I.e. ROLE_ADMIN shouldn't copy the same restrictions as ROLE_USER and then add some more on top of that. No, it should solely represent exactly that "some more". Admin users are then just assigned the both roles (you did that part correctly). Otherwise you end up with duplicate checks throughout the code at places which may be accessed/used by both an user and an admin. And then I'm not talking about a third role above this which may require triple checks in the code. You'd need to edit the existing code over all place.

If you'd like to programmatically toggle roles during runtime, perhaps because you'd like being able to "preview" the site as a regular user (e.g. checking how the site look like when admin-only sections/buttons are hidden), then there are basically two options:

  1. Set some flag as session attribute or perhaps as request parameter and have the code check on that. E.g.

    <h:form>
        <h:selectBooleanCheckbox value="#{sessionScope.preview}">
            <f:ajax render="@all" />
        </h:selectBooleanCheckbox>
    </h:form>
    

    (note: the code is as-is, #{sessionScope} is an implicit EL variable referring ExternalContext#getSessionMap(); no additional backing bean necessary)

    And then in the master tamplate:

    <c:set var="userIsAdmin" value="#{request.isUserInRole('ROLE_ADMIN') and not preview}" scope="request" />
    

    And in the target views containing some admin specific stuff:

    <h:commandButton value="Some awesome admin button" rendered="#{userIsAdmin}" />
    

  2. Perform programmatic login as a regular user. You can use HttpServletRequest#login() to programmatically trigger container managed authentication. This way an admin can "impersonate" a different user and browse the site as if he's logged-in as the particular user. E.g. in a session scoped bean:

    public void runAs(User user) {
        // ...
        try {
            request.login(user.getUsername(), user.getPassword());
            originalUser = currentUser;
            currentUser = user;
            // ...
        } catch (ServletException e) {
            // ...
        }
    }
    
    public void releaseRunAs() {
        // ...
        try {
            request.login(originalUser.getUsername(), originalUser.getPassword());
            currentUser = originalUser;
            // ...
        } catch (ServletException e) {
            // ...
        }
    }
    

    You can even extend it by holding all previous users in a LILO (last in, last out) queue in the session scope. Most security frameworks like Apache Shiro have builtin APIs for this.

OTHER TIPS

I think the way to get the behavior you asked for being very careful with this, maybe add another admin account instead and remove the ROLE_USER from that account.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top