Security frameworks for web applications (if not software in general) seem to be an area in which programmers frequently over-think the problem.
A security application checks to see if the current user authorized to perform a given task, whether it's viewing a page or updating a record. The application needs to know two things - 1) who the user is 2) what they're trying to do. From that it needs to return a simple boolean value, yes they are or no they're not allowed to do x.
At all the places I've worked, the application security framework has been far more complicated. I'm not talking about roles -- those are actually a good idea. Roles are a logical grouping of the company's business rules, so internally the framework ought to be able to determine what roles the user belongs to and if those roles are permitted to perform the task. No, it's after roles that the problem becomes inflated.
Usually, I see people inflate the complexity of security by inflating the notion of the task. The task x becomes a site section [s] and a page [p] or a context [c] and then an action [a]. Typically, the action becomes a canonical list like read / write / list / execute. This makes more sense with an operating system, where the security framework's role may only be to guard the file system and therefore these actions apply in all contexts.
In a web application it's kinda silly because those actions won't apply in all or necessarily even in most contexts. Take for example an application which stores sensitive information about users, such as their social security number. A given user may be able to list the other users and view their names -- they may even be able to view the user detail which contains their social security number, yet not be allowed to view the social. In this case "read" may be the only action relevant to the social. Or there may be a content management system which allows only certain users to approve content -- in which case "execute" may be the only relevant action.
So in a typical web application security suite, you might see Security.checkPermission(user,"adminSection","products","edit") which determines if the current user is allowed to edit products in the admin area of the application, where "list" and "read" are both part of the available permissions, but neither are used because you want everyone to be able to see your products.
Some years ago Peter Cathcart Wason developed a cognitive science experiment generally known as the "2-4-6 problem". Participants are given a set of three numbers called a triplet (2-4-6) and told that these numbers follow a sequencing rule. They're then asked to deduce the rule through a simple game. They create another triplet and the experimenter tells them if their triplet is valid under the sequencing rule. When they believe they know what the rule is, they simply state the rule and the experimenter tells them if they've answered correctly and won the game.
So the guy would come in and be given the initial set, 2-4-6. He'd then make his triplet, 6-8-10, be told it conformed to the rule and call out his answer "even numbers". Overwhelmingly, most of the participants in this experiment guessed that the rule was "even numbers" or in some cases even more complicated rules like "counting up by twos". Most people failed to find the correct answer, which was "ascending numbers". The reason most people failed to find the correct answer in this test is because most people only proposed triplets they believed would be valid. Finding the correct answer to the test requires checking sequences you think won't be valid, until you've ruled out all the alternatives.
Programmers over complicate security frameworks because they only envision scenarios in which their initial concept is relevant. And then later when they discover another scenario they end up having to revise the security framework and make it even more complex to accommodate the new scenario.
The Members onTap plugin provides a security framework in which there are never any non-relevant items. It's accessed via a function request.tap.PolicyManager.isPermitted(task,user) which returns a boolean, yes they are or no they're not allowed to perform this task. These tasks can be anything - absolutely anything you want. And they can also be nested (using a forward-slash / character) or not. They're "auto-wired" into the application by using the path to the current base template as the default task. So for example, if a user is viewing the page /admin/product/edit.cfm, the application will deny them access if they don't have permission to perform the task "admin/product/edit". But that task could just as easily be a custom permission that has nothing to do with the context of the application's file structure, like "$myCustomPermission", where the $ is used to ensure the permission doesn't conflict with an existing file-based permission.
This also accounts for the site-section or page-section as well in a manner that's much more flexible than typical application security. Where the typical system would have an explicitly declared "site section" and/or "page", it would only allow a nesting hierarchy of one-to-two levels deep. The onTap framework's permission system, by omitting these as considerations, allows permissions to be indefinitely nested (which in practice is only likely to be at most about 5 levels). Thus when you test for the permission "admin/product/edit" you know that it is automatically testing the permission for "admin/product" and "admin" first and denying access to the nested sections if the user isn't allowed access to its parent. In other words, to "administer products", you first have to be allowed to "administer".
This is again, like the invention of the stirrup. It works and it works well across many contexts, precisely because it's simple and makes minimal assumptions about the environment.