之前写了一篇博客是用,ssm+thymeleaf+vue+shiro完成一个具有权限登录,且能增删查改 的这么一个项目。当时只记录了登录权限的操作,现在补充一下增删查改 的具体实现。
上一篇博客:使用shiro实现登录权限认证 详细代码说明。
首先给出完整代码,点击github连接自取 。
一、项目需求:
系统需要在spring boot下面开发
数据库可以需要使用mybatis(可以混合jpa开发)
页面用Thymeleaf模板和Vue.js(vuejs组件可以用element UI)
登录权限采用spring boot security 或者shiro框架(任选其一)
工作量不能低于Boot管理系统,界面美观实用
二、使用到的技术栈:
数据库:springBoot,springMvc,mybatis
页面:thymeleaf,semantic,vue,bootstrap,axios
登录权限和认证:shiro
三、开发过程: 1. 大致开发流程:
2. 具体: 2.1使用shiro安全框架: (1)首先编写我们的底层数据库sql语句和持久层entity类: 我们需要三张表,User,Role,Permission,定义他们对于登录的作用,首先是通过姓名和密码查询User表是否有这个人,查询结束通过User的外键Role_Id,来映射这个人的角色,然后又通过Role表中的Id,来查询此角色对应的Permission表中的权限。这样我们就将这个人的身份与权限一并操作完,并且跳转到主页index.html,主页会根据权限,来展示对应的可操作事件,例如: USER有查询的权限,ADMIN在USER的基础上可以修改,添加用户信息,SUPER_ADMIN可以删除。 我们需要知道三个实体类之间的关系,User与Role一对一,Role与Permissions一对一,当然也可以把它都写成多对多,这就需要去更改数据库文件,和实体类了。 持久层可以使用lombok,让我们可以减少持久类的代码量,减少set,get的编写。
sql代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 CREATE TABLE `role` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '角色表主键' , `role_name` varchar (32 ) DEFAULT NULL COMMENT '角色名称' , PRIMARY KEY (`id` ) ); INSERT INTO `role` VALUES (1 , 'SUPER_ADMIN' );INSERT INTO `role` VALUES (2 , 'ADMIN' );INSERT INTO `role` VALUES (3 , 'USER' );DROP TABLE IF EXISTS `user` ;CREATE TABLE `user` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '用户主键' , `username` varchar (32 ) NOT NULL COMMENT '用户名' , `password` varchar (32 ) NOT NULL COMMENT '密码' , `role_id` int (11 ) DEFAULT NULL COMMENT '与role角色表联系的外键' , PRIMARY KEY (`id` ), CONSTRAINT `user_role_on_role_id` FOREIGN KEY (`role_id` ) REFERENCES `role` (`id` ) ); INSERT INTO `user` VALUES (1 , 'BWH_Steven' , '666666' , 1 );INSERT INTO `user` VALUES (2 , 'admin' , '666666' , 2 );INSERT INTO `user` VALUES (3 , 'zhangsan' , '666666' , 3 );CREATE TABLE `permission` ( `id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '权限表主键' , `permission_name` varchar (50 ) NOT NULL COMMENT '权限名' , `role_id` int (11 ) DEFAULT NULL COMMENT '与role角色表联系的外键' , PRIMARY KEY (`id` ), CONSTRAINT `permission_role_on_role_id` FOREIGN KEY (`role_id` ) REFERENCES `role` (`id` ) ); INSERT INTO `permission` VALUES (1 , 'user:*' , 1 );INSERT INTO `permission` VALUES (2 , 'user:*' , 2 );INSERT INTO `permission` VALUES (3 , 'user:queryAll' , 3 );
(2)接下来就是pom.xml添加相关依赖,这里就不贴代码了,需要的github自取。 (3)整合mybatis和springboot: 就只需要创建一个dao层,一个服务层,需要记住要添加注解,一定要清楚他们的对应关系:
① mapper配置文件(也可以使用注解形式):
UserMapper.xml代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.example.csy.dao.UserMapper" > <select id="queryUserByUsername" resultMap="userRoleMap" > SELECT u.*,r.role_name FROM `user` u, `role` r WHERE username = #{username} AND u.role_id = r.id; </select> <!-- 定义封装 User和 role 的 resultMap --> <resultMap id="userRoleMap" type="com.example.csy.entity.User" > <id property="id" column="id" /> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="roleId" column="role_id"></result> <!-- 配置封装 UserPojo 的内容 --> <association property="role" javaType="com.example.csy.entity.Role" > <id property="id" column="id"></id> <result property="roleName" column="role_name"></result> </association> </resultMap> <select id="queryPermissionByUsername" resultMap="permissionRoleMap" > SELECT p.* ,r.role_name FROM `user` u, `role` r, `permission` p WHERE username = #{username} AND u.role_id = r.id AND p.role_id = r.id; </select> <!-- 定义封装 permission 和 role 的 resultMap --> <resultMap id="permissionRoleMap" type="com.example.csy.entity.Permissions" > <id property="id" column="id" /> <result property="permissionName" column="permission_name"></result> <result property="roleId" column="role_id"></result> <!-- 配置封装 Role 的内容 --> <association property="role" javaType="com.example.csy.entity.Role" > <id property="id" column="id"></id> <!--property是实体类中被赋值的参数名,column是数据库的列名--> <result property="roleName" column="role_name"></result> </association> </resultMap> </mapper>
② dao层与service也比较简单。
③ 弄到这里我们的mybatis+springboot整合也基本结束,所以在测试类里测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @SpringBootTest class CsyApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads () { User admin = userMapper.queryUserByUsername("admin" ); System.out.println(admin.toString()); Permissions permission = userMapper.queryPermissionByUsername("admin" ); System.out.println(permission.toString()); } }
(4)将shiro整合到项目里: ①.shiro最关键的就是Realm组件:
其可以理解为 Shiro 与 数据之间的沟通器与中间桥梁认证授权时,就会去此部分找一些内容,从本质上 Realm 就是一个经过了大量封装的安全 Dao,这是官网的介绍,我的理解对于Realm组件其实就是一个Dao,我们在这里面定义两个方法,一个身份认证doGetAuthenticationInfo,一个授权doGetAuthorizationInfo,这两个方法很关键,所以给出全部代码:
***UserRealm代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class UserRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { String username = (String) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRole(userMapper.queryUserByUsername(username).getRole().getRoleName()); authorizationInfo.addStringPermission(userMapper.queryPermissionByUsername(username).getPermissionName()); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { String username = (String) authenticationToken.getPrincipal(); User user = userMapper.queryUserByUsername(username); if (user != null ) { SecurityUtils.getSubject().getSession().setAttribute("user" , user); AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm" ); return authenticationInfo; } else { return null ; } } }
②接下来就是编写ShiroConfig注解类:
将所有类,属性全部注入spring容器中,但是我们使用springboot,直接使用注解:@Configuration。 该注解类主要是要将,realm类的两个自定义的方法注入,还有配置安全管理器 SecurityManager,还有就是过滤器,这个过滤器是分配权限的一个关键方法,对于这个过滤器,shiro有一个封装好的ShiroFilterFactoryBean工厂类,他有很多自定义方法,set登录页,成功页,未授权页等,这个通过springMvc的控制器控制就行。 重点说一下拦截放行(Map)这块:通过 map 键值对的形式存储,key 存储 URL ,value 存储对应的一些权限或者角色等等,其实 key 这块还是很好理解的,例如 :/css/、/user/admin/ 分别代表 css 文件夹下的所有文件,以及请求路径前缀为 /user/admin/ URL,而对应的 value 就有一定的规范了。 如下面的一些shiro自定好的权限
关键: anon:无需认证,即可访问,也就是游客也可以访问 authc:必须认证,才能访问,也就是例如需要登录后 roles[xxx] :比如拥有某种角色身份才能访问 ,注:xxx为角色参数 perms[xxx]:必须拥有对某个请求、资源的相关权限才能访问,注:xxx为权限参数
就比如我们自己的图片,static文件下所有数据都可以直接使用anon,直接放行,不用管理。
shiroConfig配置类的所有代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 @Configuration public class ShiroConfig { @Bean public UserRealm myShiroRealm () { return new UserRealm(); } @Bean public DefaultWebSecurityManager securityManager () { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/toLoginPage" ); shiroFilterFactoryBean.setSuccessUrl("/success" ); shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized" ); Map<String, Filter> filterMap = new LinkedHashMap<>(); filterMap.put("anyRoleFilter" , new MyRolesAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainMap = new LinkedHashMap<>(); filterChainMap.put("/css/**" , "anon" ); filterChainMap.put("/img/**" , "anon" ); filterChainMap.put("/js/**" , "anon" ); filterChainMap.put("/toLoginPage" , "anon" ); filterChainMap.put("/user/admin/**" , "authc" ); filterChainMap.put("/levelB/**" , "anyRoleFilter[ADMIN,SUPER_ADMIN]" ); filterChainMap.put("/user/admin/**" , "perms[user:*]" ); filterChainMap.put("/logout" , "logout" ); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; } @Bean (name = "shiroDialect" ) public ShiroDialect shiroDialect () { return new ShiroDialect(); } }
④自定义一个角色认证过滤器MyRolesAuthorizationFilter:
因为我们的角色,只需用有一个角色就能访问到映射页面,shiro默认是hasAllRoles,也就是说,我们要满足所有的身份才能访问,所以需要我们自定义一个hasAnyRoles,任选其一角色即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyRolesAuthorizationFilter extends AuthorizationFilter { @SuppressWarnings ({"unchecked" }) public boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0 ) { return false ; } List<String> roles = CollectionUtils.asList(rolesArray); boolean [] hasRoles = subject.hasRoles(roles); for (boolean hasRole : hasRoles) { if (hasRole) { return true ; } } return false ; } }
(5)将整合Thymeleaf和semantic进来: Ui我们使用的是semantic,感觉还可以:接下来,就直接展示页面效果了: 我们对各种角色对数据库的操作进行了权限的限制,如开头所说,USER有查询的权限,ADMIN在USER的基础上可以修改,添加用户信息,SUPER_ADMIN可以删除。 我们实现这种操作,是对表格的一部分标签设置了部分角色才能查看: 关键代码是index主页中标签的声明:
A.USER有查询的权限 shiro:hasAnyRoles=”SUPER_ADMIN,ADMIN,USER”
B.ADMIN在USER的基础上可以修改 shiro:hasAnyRoles=”SUPER_ADMIN,ADMIN”
C.SUPER_ADMIN在ADMIN权限上可以删除 shiro:hasAnyRoles=”SUPER_ADMIN”
(6)最后就是controller,由于比较简单,就不写代码了。 (7)shiro效果展示: 登录login.html
USER权限:
admin权限:
SUPER_ADMIN权限:
至此shiro实现登录的身份认证和权限分配操作已经搞定,接下来就是用户信息的增删查改:
2.2实现用户信息的增删查改: (1)dao(mapper)层和service层的编写: 由于使用的mybatis,所以我们需要有两种方式,一种是使用注解,一种是编写配置文件,在项目里,两种方式,都使用了的。 对于两个一对多的查询使用的是配置文件形式,其他的增、删、改都是用的是注解形式,由于mapper类比较简单就不贴代码了,还有一个UserMapper.xml配置文件的代码,已经在shiro那里贴出来了,这里也不书写了。 然后就是service类的编写,我们使用@Service注解,接口和实现类都需要@service注解,然后在实现类中通过mapper调用方法,再加上一些限制如: 增加用户信息时,如果该用户存在,就打印失败,方法直接结束。
(2)编写测试类: 写了测试成功之后,如果出错,我们可以不用担心是DAO层和service出错了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test void queryAll () { List<User> users = userService.queryAll(); System.out.println(users); } @Test void contextLoads () { User admin = userService.queryUserByUsername("admin" ); System.out.println(admin.toString()); Permissions permission = userService.queryPermissionByUsername("admin" ); System.out.println(permission.toString()); } @Test void addTest () { User user = new User("wu" ,"123" ,3 ); int i = userService.addUser(user); System.out.println(i); } @Test void deleteTest () { userService.deleteUser("zhangsan" ); } @Test void updateTest () { User user = new User("ww" ,"123" ,3 ); } }
(3)显示数据到主页index.html上: 主要使用vue,axios将数据显示到页面,使用vue的指令,比如v-for,v-model等,ui使用的是semantic,bootstrap,我们将数据操作分为四步 怎么实现的。
A.查询所有:
在mapper的定义中,我们有一个queryAll()方法,在controller中,我们返回一个List,所以index中获取这个list,就是最关键的问题了,这里我们在index页面中直接把vue对象定义出来,var vm = new Vue();将数据显示的表格定义一个id,这个id由vue代理。 只需要在data属性里,定义一个数组userList,来将queryAll查询到的list数据赋值到userList数组里。 当然查询这个方法,按我们的一般体验来说,应该要登录了就显示,所以axios就起到作用了,他可以让我们不用刷新就把数据显示到页面上,而且在vue中使用axios也比较的方便。 我们需要考虑一个问题,就是查询操作应该多久显示,我们知道vue对象使用声明周期的,我们可以在创建vue对象之后就调用queryAll方法,这样就可以实现一登录就查询所有的操作了。 在这里我们只需要在vue对象中添加两个方法,一个queryAll,一个create方法。需要值得注意的是我们给userList赋值时,需要声明一个变量 var _this = this;因为vue对象,和axios是不一样的,只有将this赋值,才会调用vue中的对象userList,不然则会调用axios的this,当然我们没有写,所以如果这样写出来,会发现什么数据都没有。 赋值userList后,就只需要展示,使用v-for指令,和取值符号就解决了。
Vue代码部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 var vm = new Vue({ el: "#app" , data: { user: {id :"" , username:"" , password:"" , roleId:"" , role:{id :"" ,roleName :"" } }, userList: [], wo:"ri" }, methods: { addUser:function (user ) { var _this = this ; axios.post("/user/admin/add" ,_this.user).then(function (response ) { _this.queryAll(); console .log(_this.userList); }).catch(function (err ) { console .log(_this.user); console .log(err); }); }, deleteUser:function (username ) { var _this = this ; axios.post("/user/admin/delete" ,username=username).then(function (response ) { _this.queryAll(); }).catch(function (err ) { }); }, queryAll: function ( ) { var _this = this ; axios.get("/user/queryAll" ).then(function (response ) { _this.userList = response.data; console .log(_this.userList); }).catch(function (err ) { console .log(err); }); }, queryByName: function (username ) { var _this = this ; axios.get("/user/queryByName" , { params: { username: username } }).then(function (response ) { _this.user = response.data; $('#updateModal' ).modal("show" ); console .log(_this.user); }).catch(function (err ) { console .log(err); }); }, updateUser: function (user ) { var _this = this ; axios.post("/user/admin/update" ,_this.user).then(function (response ) { _this.queryAll(); console .log(_this.user); }).catch(function (err ) { }); } }, created(){ this .queryAll(); } });
Html表格那部分代码比较简单就不贴了。
B.删除用户:
对于删除用户,我们可以再每个查询结果后弄一个td,这样我们就可以获取当前这一行的user的id,方便赋值,这里我们使用v-on:,直接调用vue中定义的方法deleteUser(id),在这里我们也使用axios.post(),因为我们要传参数,执行操作,deleteUser方法的代码就在上面,同时我们需要在执行完操作之后,直接查询所有方法queryAll,所以我们需要在delete结束后,调用_this.queryAll()方法。这样交互性比较好。
C.修改用户:
修改用户我们可以使用bootstrap中的模态框,使用模态框的好处,就是交互性好,不需要跳转页面,使用起来更加简洁。使用模态框也不是很难,就只需要在div标签中的class使用bootstrap的语法,在点击修改之后,会调用queryByName方法,在方法中使用取值符号来获取模态框的id并且调用bootstrap封装的moal(“show)方法,显示模态框。 _this.user = response.data;$(‘#updateModal’).modal(“show”);
E.增加用户:
增加用户和修改用户差不多,也是v-model实现,只不过加了提示,当然后续操作和上面一样,也是直接queryAll(),还有一点因为网页传给我们的数据时json,所以需要在controller中使用@RequestBody将json转换成字符串,如果有返回值也要使用@ResponseBody。因为要赋值。
到此开发就结束了。
四、实验成果展示: 实现的效果大家可以克隆下来源代码自己测试,这里我只给出一些大概的展示。 (1)增加用户:
本博客内容基本到此结束,如果觉得有用的,你懂得,码字不易!嘻嘻!