How to make each user access resources at a specific location according to their authority/role in JAAS?
Asked Answered
R

2

6

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.

Reber answered 2/11, 2013 at 18:16 Comment(2)
How exactly are you logging in an admin as a registered user? If a particular user has multiple roles, then there's actually no way to login using only one of those. So the whole question is confusing.Rhubarb
It is a filter that authenticates users. The filter is mapped to URL patter - /jass/* (mistakenly typed, should have been /jaas/*). It indicates a directory in which there is only one page - temp.jsp where the request is dispatched, when the login button on JSF page is pressed (via its corresponding JSF managed bean). Is there no way to log in once as an admin and once as a user using the same id/password? I also felt so but thought the container might support some mechanisms to do this. I looked into some tutorials but found nothing about it.Reber
R
1

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.

Rhubarb answered 14/11, 2013 at 9:49 Comment(1)
I currently stick at the "per-user basis" method instead of going into complexity unnecessarily. Mainly, I wanted to know whether it is supported by the container, out of the box for which I was completely tired of flipping back and forth the Oracle documents :) I now removed my big doubt. Thanks.Reber
N
1

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.

Newcomen answered 11/11, 2013 at 2:49 Comment(1)
Before asking this question, I also thought that this would only be the answer to this question :). Doing so, however we cannot say one user can have multiple authorities/roles because a user requires to have multiple accounts that correspond to multiple authorities/roles. Although a single user may have multiple accounts with different authorities/roles, they all are treated as different users by the system :).Reber
R
1

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.

Rhubarb answered 14/11, 2013 at 9:49 Comment(1)
I currently stick at the "per-user basis" method instead of going into complexity unnecessarily. Mainly, I wanted to know whether it is supported by the container, out of the box for which I was completely tired of flipping back and forth the Oracle documents :) I now removed my big doubt. Thanks.Reber

© 2022 - 2024 — McMap. All rights reserved.