最近在做一个期末作品,就是使用ssm+thymeleaf+vue+shiro完成一个具有权限登录,且能实现用户信息增删查改 的这么一个项目,下面仅仅是实现权限认证和登录 。为什么我选shiro,而不选spring Security,是因为我试过,security实在是比较难,封装的太厉害了,哈哈哈哈,所以果断放弃,选择shiro进行。
下一篇还实现了增删查改,使用vue,但是没有前后端分离,博客链接
!github源码连接 ,需要请自行下载。
提示,这个项目已经有了增删查改,跟着下面的博客做,也能做出来页面跳转权限,但是没有增删查改。
以下是学习shiro的一个小Demo:
1.首先是底层数据库: 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.创建spring boot项目,用maven构建 创建实体类(User,Role,Permissions):User:
1 2 3 4 5 6 7 8 9 10 @Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String username; private String password; private Role role; }
Role:
1 2 3 4 5 6 7 8 9 @Data @AllArgsConstructor @NoArgsConstructor public class Role { private Integer id; private String roleName; }
Permissions:
1 2 3 4 5 6 7 8 @Data @AllArgsConstructor @NoArgsConstructor public class Permissions { private Integer id; private String permissionName; private Role role; }
我们需要知道三个实体类之间的关系,User与Role一对一,Role与Permissions一对一,当然也可以把它都写成多对多,这就需要去更改数据库文件,和实体类了。
3.在pom.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 40 41 42 43 44 45 46 47 48 49 50 51 52 <dependency > <groupId > com.github.theborakompanioni</groupId > <artifactId > thymeleaf-extras-shiro</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.5.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.3</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > </exclusion > </exclusions > </dependency >
4.整合mybatis和springboot: 就只需要创建一个dao层,一个服务层,需要记住要添加注解 : (1)mapper配置文件(也可以使用注解形式):
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 <?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 > <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 > <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 > <resultMap id ="permissionRoleMap" type ="com.example.csy.entity.Permissions" > <id property ="id" column ="id" /> <result property ="permissionName" column ="permission_name" > </result > <association property ="role" javaType ="com.example.csy.entity.Role" > <id property ="id" column ="id" > </id > <result property ="roleName" column ="role_name" > </result > </association > </resultMap > </mapper >
(2)DAO层:
1 2 3 4 5 6 @Mapper public interface UserMapper { User queryUserByUsername (@Param("username" ) String username) ; Permissions queryPermissionByUsername (@Param("username" ) String username) ; }
(3)service层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User queryUserByUsername (String username) { return userMapper.queryUserByUsername(username); } @Override public Permissions queryPermissionByUsername (String username) { return userMapper.queryPermissionByUsername(username); } }
弄到这里,我们的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()); } }
测试结果: 得到了查询结果
6.整合Thymeleaf进来: 前端页面: 在html页面我们整合了Thymeleaf,使用了Jquery,semantic,需要导包
index.html代码: 在这里,如果是User就只能访问A,Admin能访问A,B,superAdmin能访问A,B,C
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 <!DOCTYPE html > <html lang ="zh_CN" xmlns:th ="http://www.thymeleaf.org" xmlns ="http://www.w3.org/1999/xhtml" xmlns:layout ="http://www.ultraq.net.nz/web/thymeleaf/layout" xmlns:shiro ="http://www.pollix.at/thymeleaf/shiro" > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 信息管理平台-首页</title > <meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1" /> <title > 首页</title > <link href ="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel ="stylesheet" /> <link th:href ="@{/css/index.css}" rel ="stylesheet" > <script src ="js/jquery-3.1.1.min.js" > </script > </head > <body > <div class ="ui container" > <div class ="ui secondary menu" > <a class ="active item" th:href ="@{/index}" > 首页 </a > <a class ="active item" th:href ="@{/about}" > 关于 </a > <div class ="right menu" > <div shiro:notAuthenticated ="" > <a class ="item" th:href ="@{/toLoginPage}" > <i class ="address card icon" > </i > 登录 </a > </div > <div shiro:authenticated ="" > <a class ="item" > <i class ="address card icon" > </i > 用户名:<span shiro:principal > </span > </a > </div > <div shiro:authenticated ="" > <a class ="item" th:href ="@{/logout}" > <i class ="address card icon" > </i > 注销 </a > </div > </div > </div > <div class ="ui stackable three column grid" > <div class ="column" shiro:hasAnyRoles ="USER,ADMIN,SUPER_ADMIN" > <div class ="ui raised segments" > <div class ="ui segment" > <a th:href ="@{/levelA/a}" > L-A-a</a > </div > <div class ="ui segment" > <a th:href ="@{/levelA/b}" > L-A-b</a > </div > <div class ="ui segment" > <a th:href ="@{/levelA/c}" > L-A-c</a > </div > </div > </div > <div class ="column" shiro:hasAnyRoles ="ADMIN,SUPER_ADMIN" > <div class ="ui raised segments" > <div class ="ui segment" > <a th:href ="@{/levelB/a}" > L-B-a</a > </div > <div class ="ui segment" > <a th:href ="@{/levelB/b}" > L-B-b</a > </div > <div class ="ui segment" > <a th:href ="@{/levelB/c}" > L-B-c</a > </div > </div > </div > <div class ="column" shiro:hasRole ="SUPER_ADMIN" > <div class ="ui raised segments" > <div class ="ui segment" > <a th:href ="@{/levelC/a}" > L-C-a</a > </div > <div class ="ui segment" > <a th:href ="@{/levelC/b}" > L-C-b</a > </div > <div class ="ui segment" > <a th:href ="@{/levelC/c}" > L-C-c</a > </div > </div > </div > </div > <div class ="ui stacked segment" > <div class ="ui stackable three column grid" > <div class ="column" > <p > 晚风吹起你鬓间的白发<br /> 抚平回忆留下的疤<br /> 你的眼中 明暗交杂 一笑生花<br /> 暮色遮住你蹒跚的步伐<br /> 走进床头藏起的画<br /> 画中的你 低着头说话<br /> 我仍感叹于世界之大 </p > </div > <div class ="column" > <p > 也沉醉于儿时情话<br /> 不剩真假 不做挣扎 无谓笑话<br /> 我终将青春还给了她<br /> 连同指尖弹出的盛夏<br /> 心之所动 就随风去了<br /> 以爱之名 你还愿意吗<br /> ❤ </p > </div > <div class ="column" > <img class ="ui medium circular image" src ="images/001.jpg" > </div > </div > </div > <div class ="ui info message" > <div class ="header" > 理想二旬不止</div > <p > BWH_Steven</p > </div > </div > </body > </html >
login.html代码:
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 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" xmlns ="http://www.w3.org/1999/xhtml" xmlns:layout ="http://www.ultraq.net.nz/web/thymeleaf/layout" > <head > <meta charset ="UTF-8" > <title > 用户管理系统-登录</title > <script src ="js/jquery-3.1.1.min.js" > </script > <link href ="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel ="stylesheet" /> </head > <body > <h1 > 用户管理系统-登录</h1 > <div class ="ui container" style ="margin-top: 180px;" > <div style ="text-align: center; margin-bottom: 20px;" > <h1 class ="header" > 登录 </h1 > </div > <div class ="ui three column stackable grid login-div" > <div class ="column" > </div > <div class ="column" > <form id ="login" class ="ui fluid form segment" th:action ="@{/login}" method ="post" > <div class ="field" > <label class ="" > 用户名</label > <div class ="ui left icon input" > <input type ="text" name ="username" placeholder ="" /> <i class ="user icon" > </i > <div class ="ui corner label" > <i class ="icon asterisk" > </i > </div > </div > </div > <div class ="field" > <label class ="" > 密码</label > <div class ="ui left icon input" > <input type ="password" name ="password" placeholder ="" /> <i class ="lock icon" > </i > <div class ="ui corner label" > <i class ="icon asterisk" > </i > </div > </div > </div > <div class ="inline field" > <div class ="ui checkbox" > <input type ="checkbox" name ="terms" /> <label > 记住密码</label > </div > </div > <div class ="inline field" > <input type ="submit" class ="ui blue submit button" > </div > </form > </div > <div class ="column" > </div > </div > </div > </body > </html >
success.html:
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 用户管理系统-成功</title > </head > <body > <h2 > 登录成功</h2 > <a href ="/index" > 返回主页</a > </body > </html >
7.将shiro整合到项目里: (1)自定义Realm: 我们需要自定义,认证和授权:
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 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 ; } } }
(2)写配置类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 80 81 82 83 84 85 86 87 88 89 90 91 @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("/levelA/**" , "anyRoleFilter[USER,ADMIN,SUPER_ADMIN]" ); filterChainMap.put("/levelB/**" , "anyRoleFilter[ADMIN,SUPER_ADMIN]" ); filterChainMap.put("/levelC/**" , "anyRoleFilter[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(); }
首先我们将自定义的Realm方法,依赖注入进来到容器
1 2 3 4 5 @Bean public UserRealm myShiroRealm () { return new UserRealm(); }
然后是:SecurityManager配置安全管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 @Bean public DefaultWebSecurityManager securityManager () { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; }
最后就是自定义的过滤器,控制那些页面需要什么样的角色才能访问,哪些资源需要谁才能访问,并且setSecurityManager,返回一个ShiroFilterFactoryBean。
重点说一下拦截放行(Map)这块:通过 map 键值对的形式存储,key 存储 URL ,value 存储对应的一些权限或者角色等等,其实 key 这块还是很好理解的,例如 :/css/、/user/admin/ 分别代表 css 文件夹下的所有文件,以及请求路径前缀为 /user/admin/ URL,而对应的 value 就有一定的规范了。
关键: anon:无需认证,即可访问,也就是游客也可以访问 authc:必须认证,才能访问,也就是例如需要登录后 roles[xxx] :比如拥有某种角色身份才能访问 ,注:xxx为角色参数 perms[xxx]:必须拥有对某个请求、资源的相关权限才能访问,注:xxx为权限参数
(3)自定义一个角色认证过滤器MyRolesAuthorizationFilter: 因为我们的角色,只需用有一个角色就能访问到映射页面,shiro默认是hasAllRoles,也就是说,我们要满足所有的身份才能访问,所以需要我们自定义一个hasAnyRoles,任选其一角色即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 ; } }
(4)最后就是controller controller是springMvc的前端控制器,接收什么请求,并且返回对应指定的页面(映射)。 首先我们先将所以页面的映射写好,
PageController:
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 @Controller public class PageController { @RequestMapping ({"/" , "index" }) public String index () { return "index" ; } @RequestMapping ("about" ) public String toAboutPage () { return "redirect:http://www.ideal-20.cn" ; } @RequestMapping ("/toLoginPage" ) public String toLoginPage () { return "views/login" ; } @RequestMapping ("/levelA/{name}" ) public String toLevelAPage (@PathVariable("name" ) String name) { return "views/L-A/" + name; } @RequestMapping ("/levelB/{name}" ) public String toLevelBPage (@PathVariable("name" ) String name) { return "views/L-B/" + name; } @RequestMapping ("/levelC/{name}" ) public String toLevelCPage (@PathVariable("name" ) String name) { return "views/L-C/" + name; } @RequestMapping ("/unauthorized" ) public String toUnauthorizedPage () { return "views/unauthorized" ; } @RequestMapping ("/success" ) public String toSuccessPage () { return "views/success" ; } }
UserController: 上面那两个映射,只是测试,主要是那个login方法,他可以根据我们前台输入的数据,并创建一个token,如果该token能被认证,即返回成功页面,否则就失败。
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 @Controller public class UserController { @RequestMapping ("/user/queryAll" ) @ResponseBody public String queryAll () { return "这是 user/queryAll 方法" ; } @RequestMapping ("/user/admin/add" ) @ResponseBody public String adminAdd () { return "这是 user/adminAdd 方法" ; } @RequestMapping ("/login" ) public String login (String username, String password, HttpServletRequest request) { User user = new User(); user.setUsername(username); user.setPassword(password); UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); request.getSession().setAttribute("user" , user); return "views/success" ; }catch (Exception e){ e.printStackTrace(); request.getSession().setAttribute("user" , user); request.setAttribute("errorMsg" , "兄弟,用户名或密码错误" ); return "views/login" ; } } }
8.最终效果: 首先是http://localhost:8080/index
登录界面:
表单提交后,就返回值到UserController那个Login方法,认证:
这样我们就登录成功了,并且是superAdmin的权限,可以查看A,B,C
而用户张三,只能看见A
到此结束,本博客借鉴:博客 ,需要源码的请查看此博客。