SpringBoot+shiro+mybatis实现权限登录

SpringBoot+shiro+mybatis+Thymeleaf实现权限登录系统

记录一下,学习shiro的一个小Demo:

1.首先是底层数据库:

-- ---------------------------- -- 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);

2.创建spring boot项目,用maven构建

创建实体类(User,Role,Permissions):

User:

@Data @AllArgsConstructor @NoArgsConstructor public class User {     private Integer id;     private String username;     private String password;     //用户对应的角色集合     private Role role; }

Role:

@Data @AllArgsConstructor @NoArgsConstructor public class Role {     private Integer id;     private String roleName; }

Permissions:

@Data @AllArgsConstructor @NoArgsConstructor public class Permissions {     private Integer id;     private String permissionName;     private Role role; }

我们需要知道三个实体类之间的关系,User与Role一对一,Role与Permissions一对一,当然也可以把它都写成多对多,这就需要去更改数据库文件,和实体类了。

3.在pom.xml添加相关依赖:

下面只给出相关依赖源

<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配置文件(也可以使用注解形式):

<?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>         <!-- 配置封装 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>         <!-- 配置封装 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>

(2)DAO层:

@Mapper public interface UserMapper {     User queryUserByUsername(@Param("username") String username);      Permissions queryPermissionByUsername(@Param("username") String username); }

(3)service层:

@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整合也基本结束,所以在测试类里测试一下:

@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());     } }

测试结果:

得到了查询结果

SpringBoot+shiro+mybatis实现权限登录

6.整合Thymeleaf进来:

前端页面:

在html页面我们整合了Thymeleaf,使用了Jquery,semantic,需要导包

SpringBoot+shiro+mybatis实现权限登录

SpringBoot+shiro+mybatis实现权限登录

index.html代码:

在这里,如果是User就只能访问A,Admin能访问A,B,superAdmin能访问A,B,C

<!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>     <!--semantic-ui-->     <link             href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css"             rel="stylesheet"     />      <!--<link href="css/index.css">-->     <link th:href="@{/css/index.css}" rel="stylesheet">      <!-- <script th:src="@{js/jquery-3.1.1.min.js}"></script> -->     <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:authorize="!isAuthenticated()">-->             <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>                     <!--角色:<span sec:authentication="principal.authorities"></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> -->     </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代码:

<!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 th:src="@{js/jquery-3.1.1.min.js}"></script> -->     <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:

<!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:

我们需要自定义,认证和授权:

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();  //        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; //        System.out.println(userToken.getPrincipal()); //        System.out.println(userToken.getUsername()); //        System.out.println(userToken.getPassword());          // 通过用户名查询相关的用户信息(实体)         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;         }     } }

(2)写配置类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();      }

首先我们将自定义的Realm方法,依赖注入进来到容器

//将自己的验证方式加入容器     @Bean     public UserRealm myShiroRealm() {         return new UserRealm();     }

然后是:SecurityManager配置安全管理器

/**      * 配置安全管理器 SecurityManager      *      * @return      */     @Bean     public DefaultWebSecurityManager securityManager() {         // 将自定义 Realm 加进来         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();         // 关联 Realm         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,任选其一角色即可。

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;     } }

(4)最后就是controller

controller是springMvc的前端控制器,接收什么请求,并且返回对应指定的页面(映射)。

首先我们先将所以页面的映射写好,

PageController:

@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能被认证,即返回成功页面,否则就失败。

@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) {         // 由于是根据name参数获取的,我这里封装了一下         User user = new User();         user.setUsername(username);         user.setPassword(password);          // 创建出一个 Token 内容本质基于前台的用户名和密码(不一定正确)         UsernamePasswordToken token = new UsernamePasswordToken(username, password);         // 获取 subject 认证主体(这里也就是现在登录的用户)         Subject subject = SecurityUtils.getSubject();         try{             // 认证开始,这里会跳转到自定义的 UserRealm 中             subject.login(token);             // 可以存储到 session 中             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

SpringBoot+shiro+mybatis实现权限登录

登录界面:

SpringBoot+shiro+mybatis实现权限登录

表单提交后,就返回值到UserController那个Login方法,认证:

SpringBoot+shiro+mybatis实现权限登录

这样我们就登录成功了,并且是superAdmin的权限,可以查看A,B,C

SpringBoot+shiro+mybatis实现权限登录

而用户张三,只能看见A

SpringBoot+shiro+mybatis实现权限登录

来源:https://www.tuicool.com/articles/RBzueij

您可能还会对下面的文章感兴趣: