I think I need to explain.
The setup expects 2 tables. One has the userid/password and one has the userid/groupid. Whatever other stuff you may wish to put into them is up to you, but that's all the container security system is interested in.
The "userid" is the EXACT text that you would enter in as a login ID on the login form or pop-up dialog. So, whether it's "mtsinc" or "
[email protected]" or even "Tim Holloway", whatever you log in with, that's what the security system needs. Auto-increment IDs are generally not what you'd want there. The userid also MUST be unique in the user/password table.
The group table allows a 1-to-many mapping for rolenames, pairing the userID (login ID) with role names. You can use the same user and role ("group") tables for multiple apps, as long as any role names that the apps have have the same meaning. In other words, if you have a role named "administrator", then every app that's got an "administrator" role in its web.xml and shares that role table will have that user pegged as an administrator. If that's a problem, define something like "app1-administrator". You can map logical (in-app) role names to physical (role table) names using web.xml. The role table does NOT have unique simple keys, but every userid/roleid pairing should be unique.
There is, as you've noted, no join. What happens is that when you log in, a UserPrincipal object gets constructed and attached to your session, where it can them be populated into incoming HttpServletRequest objects, along with the login userid accessible via getRemoteUser(). The authentication code simply checks to see if "COUNT(*) FROM user_password WHERE user_id = ? AND password = ?" is greater than zero (ideally, it's 1). If that query fails, then login fails. Otherwise, a "SELECT rolename FROM user_roles WHERE user_id=?" is executed to retrieve and build the list of allowed roles for the user. That's done at login time because a user's roles cannot be changed while a user is logged in or potential security leaks could develop.
Just in case you're confused, I've used generic table and column names for my example above. The actual table and column names come from your security configuration dialog.
So the password check (authentication) and the retrieval of roles (authorization) are 2 independent functions, using 2 independent tables, despite the fact that both use a common userID. You can, of course, add to the capabilities of your webapp using the userID (via getRemoteUser) to look up additional user account properties (via application database logic, LDAP, or whatever) within the webapp as you see fit.