Recently, Angular is used to create a single-page application. However, because users have different roles (administrator, editor, and common financial personnel), access control for different roles is required.
Because the backend access control has rich experience, only the implementation of front-end access control is recorded here. Please note that the front-end can only achieve display control at most! Security is not guaranteed, so the backend must implement access control!
Role-based access control requires two levels of access control:
Control page route jump. Users without permissions cannot jump to the specified url.
Display Control of page elements. Users without corresponding permissions cannot see this element
But before that, we have another important thing to do.
Store user information
What we need to do first is not related to access control. First, we need to save user information. Includes basic user information, such as user name, real name, and user role. The following is the data structure:
User = {
Username :"",
Realname :"",
Role :""
}
The entire user is stored during storage, but where does it exist? Considering that it must be accessible on any page, the first response is to store it in $ rootScope, but we should avoid using $ rootScope; otherwise, we can store these two solutions in the top-level controller or global constant, but their problem is that once the page is refreshed, it doesn't matter if you use $ rootScope ). Considering that the life cycle of the user variable should be the same as that of the session, I used SessionStorage.
When creating a controller, you need to add $ sessionStorage:
App. controller ('controller', ['$ sessionStorage', function ($ sessionStorage) {}]);
After successful logon, store the user in SessionStorage:
$ SessionStorage. USER = user;
Now, you can get user information through $ sessionStorage.
User = $ sessionStorage. USER;
Control page route jump
Next we will start to implement the first point: control the page route jump.
It is easier to achieve the first point. When Angular routes change, the $ stateChangeStart event is triggered (I use stateProvider, so I listen to stateChangeStart. If the route or location is used, listen to the corresponding events), listen to this event, and make permission judgments based on the access url and user role, such as logon judgment, to access the url, you need to log on to the logon page.
First, write an auth service for permission authentication:
/**
* Role-based access control
*/
App. service ("auth", ["$ http", "$ sessionStorage", function ($ http, $ sessionStorage ){
Var roles = []; // role table obtained from the backend database
// Role permission Url ing table obtained from the backend. The structure is {"role": ["/page1", "/page2"…]}.
Var urlPermissions = {};
// Obtain from the backend
(Function (){
// This is convenient for testing and has been assigned a value directly. The following examples are used only for the purpose and are as simple as possible.
Roles = ["admin", "user"]
UrlPermissions = {
// The administrator can access the page
"Admin": ["*"],
// Normal users can access all interfaces (logon, registration, and other pages) in the page path and the system homepage.
"User": ["page. *", "app. index", "app. detail"]
}
})();
Function convertState (state ){
Return state. replace (".", "\\\\."). replace ("*",".*");
}
Return {
// Whether you have the permission to access a url
IsAccessUrl: function (url ){
Var user = $ sessionStorage. USER;
For (var role in roles ){
If (user. role. toLowerCase () = roles [role]. toLowerCase ()){
Console. log (urlPermissions [roles [role])
For (I in urlPermissions [roles [role]) {
Var regx = eval ("/" + convertState (urlPermissions [roles [role] [I]) + "/");
Console. log (regx + "" + url)
If (regx. test (url )){
Return true;
}
}
}
}
Return false;
}
}
}])
Roles is a role that is obtained from the background. urlPermissions is a list of URLs accessible to each role. It is also obtained from the background and can be configured in the background. In this way, each time a role is added, we can dynamically configure access permissions for it.
The most important thing is the isAccessUrl method. After the url is imported, isAccessUrl first obtains user information through $ sessionStorage, obtains the user role, and then checks whether the user role is in the role table. If the user role is in the role table, check whether the role has the permission to access the url. When we configure the backend, the status is directly specified, but if there is no wildcard *, then a url is required for each page. Therefore, the wildcard * function is added, then, convert each url in the url list to a regular expression and verify the configuration.
Finally, listen to the event $ stateChangeStart in run:
App. run (["$ rootScope", '$ state', "auth", "$ sessionStorage", function ($ rootScope, $ state, auth, $ sessionStorage ){
$ RootScope. $ on ('$ statechangestart', function (event, toState, toParams, fromState, fromParams ){
// Route access control
If (toState. name! = "Page. login "&&! Auth. isAccessUrl (toState. name )){
// Check whether logon is required:
Var user = $ sessionStorage. USER;
If (user = null ){
Event. preventDefault ();
$ State. go ("page. login ");
Return;
}
Event. preventDefault ();
$ State. go ("page. error ");
}
});
}])
Now we have implemented url access control.
Display Control of page elements
As for the second point, my solution is custom commands. The following is an example:
<Div zg-access = "TEST_ACCESS"> </div>
Note: The role is not passed in, but the permission. This is because user roles can be dynamically expanded. If a role is written here to access this element, it will be a great deal of trouble for every new role to be added in the future, because you have to modify the code one by one. The code for zg-access is as follows:
/**
* Element-level access control commands
*/
App. directive ("zgAccess", function ($ sessionStorage, $ http ){
Var roles = []; // role
Var elemPermissions ={}; // role element permission ing table, for example, {"role": {"SEARCH" }}, role has this SEARCH permission
// Obtain from the background
(Function (){
// For the sake of simplicity, generate directly here
Roles = ["admin", "user", "visitor"];
ElemPermission = {
"Admin": ["*"],
"User": ["SEARCH"],
"Visitor": []
}
})();
Console. log ("zg-access ");
Return {
Restrict: 'A ',
Compile: function (element, attr ){
// The initial state is "none", and disbaled and OK are disabled. Three statuses are available:
Var level = "none ";
Console. log (attr)
If (attr & attr ["zgAccessLevel"]) {
Level = attr ["zgAccessLevel"];
}
Switch (level ){
Case "none": element. hide (); break;
Case "disabled ":
Element. attr ("disabled ","");
Break;
}
// Obtain the element permission
Var access = attr ["zgAccess"];
// Upload the permission to the backend database
(Function (){
// Upload
})();
Return function (scope, element ){
// Determine whether the user has permissions
Var user = $ sessionStorage. USER;
If (user = null | angular. equals ({}, user )){
User = {};
User. role = "visitor ";
}
Var role = user. role. toLowerCase ();
Console. log (roles );
For (var I in roles ){
Var tmp = roles [I]. toLowerCase ();
If (role = tmp ){
Tmp = elemPermission [role];
Console. log (tmp)
For (var j in tmp ){
Console. log (tmp [j] + "" + access );
If (access. toLowerCase () = tmp [j]. toLowerCase ()){
Element. removeAttr ("disabled ");
Element. show ();
}
}
}
}
};
}
}
})
ZgAccessLevel is an attribute used to control the level. If it is none (the default value is none), no element is displayed. If it is disbaled, the element is unavailable (such as the Button is unavailable ).
The following is an element example:
<Button ng-click = "" zg-access = "SEARCH" zg-access-level = "disabled"> Search </button>
If you log on as admin or user, the Search button is unavailable.