Search code examples
javamongodbjaasform-authenticationwildfly-8

Java/WildFly/MongoDB/JAAS - Authentication always returns 403 - Forbidden


I've created a custom LoginModule to authenticate the users present in mongoDB collections. In my case, I need one Role per page... I've already used JAAS authentication with JSF, but in this case, it's not working as expected... It's always returns 403 error (Forbidden). The mapping of the URLs apparently is OK.

That's my pages hierarchy:

  • app <-root
    • pages
      • my protected pages (one role per page)
    • login.html
    • login_error.html
    • index.html

Follow my configurations:

jboss-web.xml

<jboss-web>
    <security-domain>nfceSecurityDomain</security-domain>
    <disable-audit>true</disable-audit>
</jboss-web>

web.xml

<session-config>
  <session-timeout>30</session-timeout>
</session-config>

<login-config>
     <auth-method>FORM</auth-method>
     <form-login-config>
          <form-login-page>/app/login.html</form-login-page>
          <form-error-page>/app/login_error.html</form-error-page>
     </form-login-config>
</login-config>

<security-role>
   <role-name>VISUALIZAR_NOTAS</role-name>
</security-role>
<security-role>
   <role-name>GESTAO_CERTIFICADO</role-name>
</security-role>
<security-role>
   <role-name>GESTAO_EMPRESA</role-name>
</security-role>
<security-role>
   <role-name>DOWNLOAD_XML</role-name>
</security-role>
<security-role>
   <role-name>INUTILIZACAO</role-name>
</security-role>

<security-constraint>
    <web-resource-collection>
         <web-resource-name>index</web-resource-name>
         <url-pattern>/app/index.html</url-pattern>
         <http-method>POST</http-method>
         <http-method>GET</http-method>
         <http-method>PUT</http-method>
         <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
            <role-name>VISUALIZAR_NOTAS</role-name>
    </auth-constraint>
</security-constraint>
<security-constraint>
    <web-resource-collection>
         <web-resource-name>orderList</web-resource-name>
         <url-pattern>/app/pages/orderlist.html</url-pattern>
         <http-method>POST</http-method>
         <http-method>GET</http-method>
         <http-method>PUT</http-method>
         <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
         <role-name>VISUALIZAR_NOTAS</role-name>
    </auth-constraint>
</security-constraint>
<security-constraint>
   <web-resource-collection>
       <web-resource-name>certificateConfigurations</web-resource-name>
       <url-pattern>/app/pages/certifiedlist.html</url-pattern>
        <http-method>POST</http-method>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
        <http-method>DELETE</http-method>
   </web-resource-collection>
   <auth-constraint>
       <role-name>GESTAO_CERTIFICADO</role-name>
   </auth-constraint>
</security-constraint>
<security-constraint>
   <web-resource-collection>
       <web-resource-name>enterpriseConfigurations</web-resource-name>
       <url-pattern>/app/pages/enterpriselist.html</url-pattern>
        <http-method>POST</http-method>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
        <http-method>DELETE</http-method>
   </web-resource-collection>
   <auth-constraint>
       <role-name>GESTAO_EMPRESA</role-name>
   </auth-constraint>
</security-constraint>
<security-constraint>
   <web-resource-collection>
       <web-resource-name>xmlDownload</web-resource-name>
       <url-pattern>/app/pages/orderdownload.html</url-pattern>
        <http-method>POST</http-method>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
        <http-method>DELETE</http-method>
   </web-resource-collection>
   <auth-constraint>
       <role-name>DOWNLOAD_XML</role-name>
   </auth-constraint>
</security-constraint>
<security-constraint>
   <web-resource-collection>
       <web-resource-name>invalidate</web-resource-name>
       <url-pattern>/app/pages/orderInvalidate.html</url-pattern>
        <http-method>POST</http-method>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
        <http-method>DELETE</http-method>
   </web-resource-collection>
   <auth-constraint>
       <role-name>INUTILIZACAO</role-name>
   </auth-constraint>
</security-constraint>

standalone.xml

<subsystem xmlns="urn:jboss:domain:security:1.2">
            <security-domains>
                <security-domain name="nfceSecurityDomain" cache-type="default">
                    <authentication>
                        <login-module code="br.com.ciss.nfce.security.JAASLoginModule" flag="required"/>
                    </authentication>
                </security-domain>
            </security-domains>
        </subsystem>

And here is my implementation of LoginModule, to get the user and roles from MongoDB:

public class JAASLoginModule implements LoginModule {

    private static final Logger LOG = Logger.getLogger(JAASLoginModule.class);

    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    private String username = null;
    private String _idUser = "";
    private char[] password = null;

    private Principal userPrincipal = null;
    private Principal passwordPrincipal = null;

    private ConnectionMongoUtil connectionMongoUtil;

    public JAASLoginModule() {
        super();
    }

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler,
            Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;
    }

    @Override
    public boolean login() throws LoginException {

        if (callbackHandler == null) {
            throw new LoginException("Error: no CallbackHandler available "
                    + "to garner authentication information from the user");
        }
        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("username");
        callbacks[1] = new PasswordCallback("password: ", false);

        try {
            callbackHandler.handle(callbacks);
            username = ((NameCallback) callbacks[0]).getName();
            password = ((PasswordCallback) callbacks[1]).getPassword();

            if (username == null || password == null) {
                LOG.error("Callback handler does not return login data properly");
                throw new LoginException(
                        "Callback handler does not return login data properly");
            }

            if (isValidUser()) { // validate user.
                succeeded = true;
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnsupportedCallbackException e) {
            e.printStackTrace();
        }

        return false;
    }

    @Override
    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else {
            userPrincipal = new JAASUserPrincipal(username);
            if (!subject.getPrincipals().contains(userPrincipal)) {
                subject.getPrincipals().add(userPrincipal);
                LOG.debug("User principal added:" + userPrincipal);
            }
            passwordPrincipal = new JAASPasswordPrincipal(new String(password));
            if (!subject.getPrincipals().contains(passwordPrincipal)) {
                subject.getPrincipals().add(passwordPrincipal);
                LOG.debug("Password principal added: " + passwordPrincipal);
            }

            List<String> roles = getRoles();
            for (String role : roles) {
                Principal rolePrincipal = new JAASRolePrincipal(role);
                if (!subject.getPrincipals().contains(rolePrincipal)) {
                    subject.getPrincipals().add(rolePrincipal);
                    LOG.debug("Role principal added: " + rolePrincipal);
                }
            }

            commitSucceeded = true;
            LOG.info("Login subject were successfully populated with principals and roles");
            return true;
        }
    }

    @Override
    public boolean abort() throws LoginException {
        if (succeeded == false) {
            return false;
        } else if (succeeded == true && commitSucceeded == false) {
            succeeded = false;
            username = null;
            if (password != null) {
                password = null;
            }
            userPrincipal = null;
        } else {
            logout();
        }
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        subject.getPrincipals().remove(userPrincipal);
        succeeded = false;
        succeeded = commitSucceeded;
        username = null;
        if (password != null) {
            for (int i = 0; i < password.length; i++) {
                password[i] = ' ';
                password = null;
            }
        }
        userPrincipal = null;
        return true;
    }

    private boolean isValidUser() throws LoginException {

        try {
            BasicDBObject search = new BasicDBObject();
            search.put("email", username);
            connectionMongoUtil = new ConnectionMongoUtil();
            DBObject user = connectionMongoUtil.getCollection("User").findOne(search);

            if (user == null){
                LOG.debug("USUÁRIO NÃO LOCALIZADO!");
                return false;
            }else{
                if ( new String(password).equals(user.get("password"))){
                    _idUser = user.get("_id").toString();
                    return true;
                }else{
                    LOG.debug("SENHA INVÁLIDA PARA O USUÁRIO " + username);
                }
            }

            return false;
        } catch (Exception e) {
            LOG.error("Error when loading user " + username + " from the database \n", e);
        }

        return false;
    }

    /**
     * Returns list of roles assigned to authenticated user.
     * 
     * @return
     */
    private List<String> getRoles() {
        BasicDBObject search = new BasicDBObject();
        search.put("_idUser", _idUser);
        DBObject userModules = connectionMongoUtil.getCollection("UserModules").findOne(search);
        String jsonArrayModules = userModules.get("_idModules").toString();

        String[] modules = null;
        try {
            modules = new ObjectMapper().readValue(jsonArrayModules, String[].class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        List<String> modulesList = new ArrayList<String>();
        for (String _idModule : modules) {
            DBObject module = connectionMongoUtil.getCollection("Module").findOne(new BasicDBObject("_id", new ObjectId(_idModule)));
            if (module != null){
                modulesList.add(module.get("name").toString());
            }
        }

        return modulesList;
    }
}

I have added the following property in standalone.xml, to see the logs generated by JAAS, and don't have any error log...

<logger category="org.jboss.security">
    <level name="ALL"/>
</logger>

In my MongoDB collections, I've added all the roles to my user, and when I try to login, I can log in, but all the pages are locked, returning the 403 error.

Anybody can help me? Maybe it's just a little detail causing the 403 error...

Thanks for your attention!


Solution

  • The only way to resolve this situation is change JAAS by Spring Security :)