超详细,附源码!使用SSM+spingboot+vue+shiro实现权限登录和增删查改。
之前写了一篇博客是用,ssm+thymeleaf+vue+shiro完成一个具有权限登录,且能增删查改的这么一个项目。当时只记录了登录权限的操作,现在补充一下增删查改的具体实现。首先给出完整代码,点击github连接自取。一、项目需求:系统需要在spring boot下面开发数据库可以需要使用mybatis(可以混合jpa开发)页面用Thymeleaf模板和Vue.js(vuejs组件可以用ele
之前写了一篇博客是用,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代码:
-- ----------------------------
-- Table structure for role
-- ----------------------------
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表主键',
`role_name` varchar(32) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
);
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'SUPER_ADMIN');
INSERT INTO `role` VALUES (2, 'ADMIN');
INSERT INTO `role` VALUES (3, 'USER');
-- ----------------------------
-- Table structure for 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`)
);
-- ----------------------------
-- Records of user
-- ----------------------------
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);
-- ----------------------------
-- Table structure for permission
-- ----------------------------
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`)
);
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 'user:*', 1);
INSERT INTO `permission` VALUES (2, 'user:*', 2);
INSERT INTO `permission` VALUES (3, 'user:queryAll', 3);
- entity层比较简单,不贴代码:
(2)接下来就是pom.xml添加相关依赖,这里就不贴代码了,需要的github自取。
(3)整合mybatis和springboot:
就只需要创建一个dao层,一个服务层,需要记住要添加注解,一定要清楚他们的对应关系:
① mapper配置文件(也可以使用注解形式):
UserMapper.xml代码
<?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整合也基本结束,所以在测试类里测试一下:
@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代码:
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
/**
* @MethodName doGetAuthorizationInfo 授权操作
* @Description 权限配置类
* @Param [principalCollection]
* @Return AuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名信息
String username = (String) principalCollection.getPrimaryPrincipal();
// 创建一个简单授权验证信息
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 给这个用户设置从 role 表获取到的角色信息
authorizationInfo.addRole(userMapper.queryUserByUsername(username).getRole().getRoleName());
//给这个用户设置从 permission 表获取的权限信息
authorizationInfo.addStringPermission(userMapper.queryPermissionByUsername(username).getPermissionName());
return authorizationInfo;
}
/**
* @MethodName doGetAuthenticationInfo 身份验证
* @Description 认证配置类
* @Param [authenticationToken]
* @Return AuthenticationInfo
* @Author WangShiLin
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据在接受前台数据创建的 Token 获取用户名
String username = (String) authenticationToken.getPrincipal();
// 通过用户名查询相关的用户信息(实体)
User user = userMapper.queryUserByUsername(username);
if (user != null) {
// 存入 Session,可选
SecurityUtils.getSubject().getSession().setAttribute("user", user);
// 密码认证的工作,Shiro 来做
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm");
return authenticationInfo;
} else {
// 返回 null 即会抛异常
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配置类的所有代码:
@Configuration
public class ShiroConfig {
//将自己的验证方式加入容器
@Bean
public UserRealm myShiroRealm() {
return new UserRealm();
}
/**
* 配置安全管理器 SecurityManager
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
// 将自定义 Realm 加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 Realm
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 配置 Shiro 过滤器
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
// 定义 shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 关联 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 自定义登录页面,如果登录的时候,就会执行这个请求,即跳转到登录页
shiroFilterFactoryBean.setLoginUrl("/toLoginPage");
// 指定成功页面
shiroFilterFactoryBean.setSuccessUrl("/success");
// 指定未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// 设置自定义 filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("anyRoleFilter", new MyRolesAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// LinkedHashMap 是有序的,进行顺序拦截器配置
Map<String, String> filterChainMap = new LinkedHashMap<>();
// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
filterChainMap.put("/css/**", "anon");
filterChainMap.put("/img/**", "anon");
filterChainMap.put("/js/**", "anon");
// 指定页面放行,例如登录页面允许所有人登录
filterChainMap.put("/toLoginPage", "anon");
// 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
filterChainMap.put("/user/admin/**", "authc");
// 页面 -用户需要角色认证
// filterChainMap.put("/levelA/**", "anyRoleFilter[USER,ADMIN,SUPER_ADMIN]");
filterChainMap.put("/levelB/**", "anyRoleFilter[ADMIN,SUPER_ADMIN]");
// filterChainMap.put("/levelC/**", "anyRoleFilter[SUPER_ADMIN]");
// filterChainMap.put("/levelA/**", "roles[USER]");
// filterChainMap.put("/levelB/**", "roles[ADMIN]");
// filterChainMap.put("/levelC/**", "roles[SUPER_ADMIN]");
// /user/admin/ 下的所有请求都要经过权限认证,只有权限为 user:[*] 的可以访问,也可以具体设置到 user:xxx
filterChainMap.put("/user/admin/**", "perms[user:*]");
// 配置注销过滤器
filterChainMap.put("/logout", "logout");
// 将Map 存入过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
/**
* 整合 thymeleaf
* @return
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
④自定义一个角色认证过滤器MyRolesAuthorizationFilter:
因为我们的角色,只需用有一个角色就能访问到映射页面,shiro默认是hasAllRoles,也就是说,我们要满足所有的身份才能访问,所以需要我们自定义一个hasAnyRoles,任选其一角色即可。
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) {
//no roles specified, so nothing to check - allow access.
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出错了。
@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代码部分:
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)增加用户:
本博客内容基本到此结束,如果觉得有用的,你懂得,码字不易!嘻嘻!
更多推荐
所有评论(0)