preface

Authority management is a module that is often used in projects and has extremely important functions. There are two well-known permission frameworks in the Java empire, Shiro and Spring Security. Both have their advantages and disadvantages, but that is not the focus of this article. This time we will implement RBAC permission management without any permission framework. Has an extremely important function.

Download all the code from github.com/zhaojun1998…

RBAC profile

Role-based Access Control (RBAC) Indicates role-based Access Control.

That is, users have roles and roles have rights. I will not go into details about the benefits of RBAC. If you are interested, please find out for yourself.

Database design

There are five tables, including user table, role table, permission table, user-role relationship table, and role-permission relationship table. The relationship between the user table and the role table is many-to-many, and the relationship between the role table and the permission table is many-to-many. See the corresponding comment for the specific meaning of each field.

SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for permission -- ---------------------------- DROP TABLE IF EXISTS `permission`; CREATE TABLE `permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, 'name' varchar(20) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'Privileges ', Varchar (50) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'Privileges ', PRIMARY KEY (' id ') USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'privilege' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`;  CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, 'name' varchar(20) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'Privileges ', Varchar (50) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'Permissions ', PRIMARY KEY (' id ') USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for role_premission -- ---------------------------- DROP TABLE IF EXISTS `role_premission`; CREATE TABLE `role_premission` ( `role_id` int(11) NULL DEFAULT NULL, `permission_id` int(11) NULL DEFAULT NULL, INDEX `role_premission_uid_fk`(`role_id`) USING BTREE, INDEX `role_premission_pid_fk`(`permission_id`) USING BTREE, CONSTRAINT `role_premission_pid_fk` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `role_premission_uid_fk` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`;  CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, 'username' varchar(20) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'username ', Varchar (50) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'password ', PRIMARY KEY (' id ') USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for user_role -- ---------------------------- DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` ( `user_id` int(11) NULL DEFAULT NULL, `role_id` int(11) NULL DEFAULT NULL, INDEX `user_role_uid_fk`(`user_id`) USING BTREE, INDEX `user_role_rid_fk`(`role_id`) USING BTREE, CONSTRAINT `user_role_rid_fk` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `user_role_uid_fk` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = UTf8 COLLATE = UTf8_general_ci COMMENT = 'user role table' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;Copy the code

Environment configuration

This time based on the environment is Spring + SpringMVC + MyBatis, but even if you do not know these frameworks, it does not matter, because the permission management does not involve too many features of these frameworks, with ordinary Servlet + JDBC can also be implemented.

For reasons of space, I won’t post the framework configuration file here, but I will post the source code to Github and you can download the complete example code.

Entity class

You first need to create three entity classes that correspond to the database

public class User { private Integer id; private String username; private String password; // getsetter omitted}Copy the code
public class Role { private Integer id; private String name; private String description; // getsetter omitted}Copy the code
public class Permission { private Integer id; private String name; private String description; // getsetter omitted}Copy the code

DAO data manipulation layer

UserMapper

public interface UserMapper { int insert(User record); User selectByPrimaryKey(Integer id); int updateByPrimaryKey(User record); List<User> selectALL(); /** * Query the user Role List * @param ID User ID * @return Role List */ List<Role> selectRolesByPrimaryKey(Integer ID); /** * Delete all roles * @param ID User ID * @return Number of deleted roles */ int deleteRoles(Integer id); * @param userId userId * @param roleId granted roleId * @return number of successful inserts */ int insertUserRole(@param ("user_id")) Integer userId, @Param("role_id") Integer roleId); * @param username User name * @param password password * @return Queried account */ User selectUserByUsernameAndPassword(@Param("username")String username,@Param("password")String password); }Copy the code

RoleMapper

public interface RoleMapper { int insert(Role record); Role selectByPrimaryKey(Integer id); int updateByPrimaryKey(Role record); List<Role> selectAll(); / * * * * query roles have Permission List @ param id character id * @ * / return Permission List List < Permission > selectPermissionsByPrimaryKey (Integer id); * @param ID Role ID * @return Number of deletePermissions */ int deletePermissions(Integer ID); /** * add a permission for a role * @param roleId roleId * @param permissionId permissionId * @return number of successfully inserted roles */ int insertRolePermission(@Param("role_id")Integer roleId, @Param("permission_id") Integer permissionId); }Copy the code

PermissionMapper

public interface PermissionMapper {
    int insert(Permission record);

    Permission selectByPrimaryKey(Integer id);

    int updateByPrimaryKey(Permission record);

    List<Permission> selectAll();
}
Copy the code

You only need some simple SQL operations. For example, you can go to Github to view mapper. XML.

User management

Users to add

HTML page:

<form action="addUser" method="post"> username :<input type="text" name="username"> Name ="password"> <input type="submit" > </form>Copy the code

Controller:

public String addUserSubmit(User user) {
    return userService.add(user) > 0 ? "success" : "error";
}
Copy the code

Service and DAO are omitted.

The user login

HTML page:

<form action="login" method="post"> Username :<input type="text" name="username"><br> Password :<input type="password" Name ="password"><br> <input type=" value ">< /form>Copy the code

Controller:

public String login(String username, String password, HttpSession httpSession) { User user = userService.selectUserByUsernameAndPassword(username, password); if (user ! = null) { httpSession.setAttribute("user", user); Return "Login succeeded "; } return "login failed "; }Copy the code

Service and DAO are omitted.

Viewing a User List

HTML page:

< table border = "1" > < tr > < td > user name < / td > < td > password < / td > < td > action < / td > < / tr > < c: forEach items = "${users}" var = "user" > < tr > <td>${user.username}</td> <td>${user.password}</td> <td> <a href="grantRoleView? Id = ${user. Id} "> role < / a > < / td > < / tr > < / c: forEach > < / table >Copy the code

Controller:

public ModelAndView listUser() {
    return new ModelAndView("user.jsp").addObject("users", userService.getAllUser());
}
Copy the code

Service and DAO are omitted.

Assign roles to users

To assign a role to a user, you need to add a role first. Please see adding a role below first.

HTML page:

<form action="grantRole"> <table border="1"> <tr> < TD > current user </ TD > < TD > ${user.username} <input type="hidden" name="id" Value = "${user. Id}" > < / td > < / tr > < tr > < td > has role < / td > < td > < c: forEach items = "${grantRole}" var = "role" > < span > ${role. The name} < / span > < / c: forEach > < / td > < / tr > < tr > < td > all roles < / td > < td > < c: forEach items = "${roles}" var = "role" > <input type="checkbox" name="roleId" value="${role.id}"> ${role.name} </c:forEach> </td> </tr> <tr> <td></td> <td><input Type = "submit" value = "submit" > < / td > < / tr > < / table > < / form >Copy the code

Controller:

public String grantRole(int id, int[] roleId) {
    userService.updateRoles(id, roleId);
    return "success";
}
Copy the code

Service:

public void updateRoles(Integer id, int[] roleIds) { userMapper.deleteRoles(id); if (roleIds ! = null) { for (int roleId : roleIds) { userMapper.insertUserRole(id, roleId); }}}Copy the code

In fact, the modification authorization role only deletes all the roles it originally owns and reassigns them to all the roles it submits.

Role management

Adding roles

HTML page:

<form action="addRole" method="post"> Role name :<input type="text" name="name"> < form> </form>Copy the code

Controller:

public String addRole(Role role) {
    return roleService.add(role) > 0 ? "success" : "error";
}
Copy the code

Service and DAO are omitted.

The role list

HTML page:

< table border = "1" > < tr > < td > character name < / td > < td > description < / td > < td > action < / td > < / tr > < c: forEach items = "${roles}" var = "role" > < tr > < td > ${role. The name} < / td > < td > ${role. The description} < / td > < td > < a href = "grantPermissionView? Id = ${role. Id}" > authorized < / a > < / td > < / tr >  </c:forEach> </table>Copy the code

Controller:

public ModelAndView listRole() {
    return new ModelAndView("role.jsp").addObject("roles", roleService.getAll());
}
Copy the code

Service and DAO are omitted.

Assign permissions to roles

HTML page:

<form action="grantPermission"> <table border="1"> <tr> < TD > Current role </ TD > < TD > ${role-name} <input type="hidden" name="id" Value = "${role. Id}" > < / td > < / tr > < tr > < td > has permissions < / td > < td > < c: forEach items = "${grantPermission}" var = "permission" > < span > ${permission. The name} < / span > < / c: forEach > < / td > < / tr > < tr > < td > all permissions < / td > < td > < c: forEach items = "${permissions}" var="permission"> <input type="checkbox" name="premissionId" value="${permission.id}"> ${permission.name} </c:forEach> < / td > < / tr > < tr > < td > < / td > < td > < input type = "submit" value = "submit" > < / td > < / tr > < / table > < / form >Copy the code

Controller:

public String grantPermission(int id, int[] premissionId) {
    roleService.updatePermission(id, premissionId);
    return "success";
}
Copy the code

Service:

public void updatePermission(Integer roleId, int[] permissionsIds) { roleMapper.deletePermissions(roleId); if (permissionsIds ! = null) { for (int permissionId : permissionsIds) { roleMapper.insertRolePermission(roleId, permissionId); }}}Copy the code

Here, granting permissions to roles is also the same as deleting the permissions of roles before adding all permissions for form submission.

Rights management

Add permissions

HTML page:

<form action="addPermission" method="post"> <input type="text" name="name"> Name ="description"> <input type="submit" > </form>Copy the code

Controller:

public String add(Permission permission) {
    return permissionService.add(permission) > 0 ? "success" : "error";
}
Copy the code

Service and DAO are omitted.

Permissions list

HTML page:

<form action="addPermission" method="post"> <input type="text" name="name"> Name ="description"> <input type="submit" > </form>Copy the code

Controller:

public String add(Permission permission) {
    return permissionService.add(permission) > 0 ? "success" : "error";
}
Copy the code

Service and DAO are omitted.

Permission to intercept

Now that we have assigned the relationships between users, roles, and permissions, we can set up some resources that require permissions to access.

I set up 5 urls and labeled what permissions or roles are required to access them:

/ API /add # add permission/API /delete # delete permission/API /get # get permission/API /employee # Employee role/API /boss # boos roleCopy the code

We can use interceptors to block all requests under/API /*, so how do we tell which permissions are required for different requests?

Here I refer to Shiro’s design, which annotates @requiredrole and @requiredPreMission in the corresponding method to indicate that the request requires a role or permission to access it.

@RestController @RequestMapping("/api") public class APIController { @RequiredPermission("add") @RequestMapping("/add") Public String add() {return "Data added successfully "; } @requiredPermission ("delete") @requestMapping ("/delete") public String delete() {return "delete data successfully "; } @requiredPermission ("get") @requestMapping ("/get") public String select() {return "request succeeded "; } @requiredrole ("boss") @requestMapping ("/boss") public String boss() {return "this data is for boss, you are boss, you can check it "; } @requiredrole ("employee") @requestMapping ("/employee") public String Employee () {return "This is an employee data, you can view it "; }}Copy the code

The interceptor gets an annotation on the intercepting method to know what permissions are needed to make the appropriate judgment. Spring interceptors:

public class PermissionHandlerInterceptor implements HandlerInterceptor { @Resource private UserService userService; @Resource private RoleService roleService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setHeader("Content-type", "text/html; charset=UTF-8"); Method method = ((HandlerMethod)handler).getMethod(); RequiredRole requiredRole = method.getAnnotation(RequiredRole.class); RequiredPermission requiredPermission = method.getAnnotation(RequiredPermission.class); User user = (User) request.getSession().getAttribute("user"); If (user == null) {response.getwriter ().write(" not logged in "); return false; } List<Role> userRoles = userService.getUserRoles(user.getId()); if (requiredRole ! = null) { for (Role role : userRoles) { if (role.getName().equals(requiredRole.value())) { return true; } } } if (requiredPermission ! = null) { for (Role role : userRoles) { List<Permission> permissions = roleService.getPermissions(role.getId()); for (Permission persission : permissions) { if (requiredPermission.value().equals(persission.getName())) { return true; }}}} response.getwriter ().println(" permissions insufficient "); return false; }}Copy the code

conclusion

This is the basic implementation, in fact, nothing very complicated, just RBAC this idea to apply.

So what are the defects in our permission management?

Let me list a few:

  • If the password is not encrypted, salt and hash the password.
  • Each request will obtain the corresponding permission data and role data, which is too resource-consuming and should be cached.
  • Do not support multiple credentials login, such as available email can also use mobile phone number login.

I will cover these issues in a subsequent shiro note.

Download all the code from github.com/zhaojun1998…