最近写了一个关于shiro集成Spring Boot的demo,感受了一下shiro的技能。
功能介绍:1,用户进行登录,此时地址栏中只能访问登录页面,其余都会被拦截。
2,登录时根据用户登录状态给出 登陆成功;当前用户不存在;密码错误 三种状态。
3,登录成功后,页面直接显示当前用户所拥有的权限功能(也可以显示全部功能,根据当前用户的角色权限进行操作,未授权的给出无法访问的提示信息),未授权的功能不显示。
代码介绍:1,此demo的pom.xml,里面大部分有注解,可以根据需要拿.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.tianle</groupId> <artifactId>login</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>login</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- web支持,SpringMVC, Servlet支持等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 导入thymeleaf依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--shiro依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 导入mybatis相关的依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency> <!-- thymel对shiro的扩展坐标 --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2,shiro配置类,shiro有三个核心组件:Subject, SecurityManager 和 Realms.
package me.tianle.login.shiro; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.apache.shiro.mgt.SecurityManager; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射 shiroFilterFactoryBean.setLoginUrl("/ "); // 设置无权限时跳转的 url; shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); /** * Shiro内置过滤器,可以实现权限相关的拦截器 * 常用的过滤器: * anon: 无需认证(登录)可以访问 * authc: 必须认证才可以访问 * user: 如果使用rememberMe的功能可以直接访问 * perms: 该资源必须得到资源权限才可以访问 * role: 该资源必须得到角色权限才可以访问 */ // 设置拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //开放登陆接口 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); //设置未授权提示页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); //授权过滤器 //注意:当前授权拦截后,shiro会自动跳转到未授权页面 filterChainDefinitionMap.put("/add", "perms[添加]"); filterChainDefinitionMap.put("/update", "perms[修改]"); filterChainDefinitionMap.put("/del", "perms[删除]"); filterChainDefinitionMap.put("/detail", "perms[查看]"); //其余接口一律拦截 //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 filterChainDefinitionMap.put("/*", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println("Shiro拦截器工厂类注入成功"); return shiroFilterFactoryBean; } /** * 注入 securityManager */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(getRealm()); return securityManager; } /** * 自定义身份认证 realm; * <p> * 必须写这个类,并加上 @Bean 注解,目的是注入 UserRealm, * 否则会影响 UserRealm类 中其他类的依赖注入 */ @Bean public UserRealm getRealm() { return new UserRealm(); } /** * 配置ShiroDialect,用于thymeleaf和shiro标签配合使用 */ @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
/** * 配置ShiroDialect,用于thymeleaf和shiro标签配合使用 */ 里面的方法是用来连接spring boot的模板thymeleaf和shiro的.
3,Realm类,跟shiro配置类配合使用,里面实现了两个方法,用来执行授权逻辑方法和执行认证逻辑方法.授权用于登录后用户的角色授权,认证用于登录时的几种情况分别提示。
package me.tianle.login.shiro; import me.tianle.login.model.Permission; import me.tianle.login.model.User; import me.tianle.login.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权"); //给资源进行授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加资源的授权字符串 //info.addStringPermission("user:add"); //到数据库查询当前登录用户的授权字符串 //获取当前登录用户 Subject subject = SecurityUtils.getSubject(); User user = (User)subject.getPrincipal(); List<Permission> list = userService.findPermission(user.getUid()); System.out.println(list); for (Permission per:list){ info.addStringPermission(per.getName()); System.out.println("mmm: "+ per.getName()); } return info; } /** * 执行认证逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证"); UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByUsername(token.getUsername()); if(user==null){ //用户名不存在 return null; } return new SimpleAuthenticationInfo(user,user.getPassword(),""); } }
4,controller层 主要体现在登录过程中,login()方法中抛出的两个异常是shiro这些工具类中封装好的,可以直接拿来使用,包括里面的UsernamePasswordToken 用来封装用户的用户名称和用户密码。
package me.tianle.login.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class LoginController { @RequestMapping("") public ModelAndView index() { return new ModelAndView("index"); } @RequestMapping("/add") public String add(){ return "/per/addUser"; } @RequestMapping("/detail") public String detail(){ return "/per/detail"; } @RequestMapping("/del") public String del(){ return "/per/delUser"; } @RequestMapping("/update") public String update(){ return "/per/updateUser"; } //未授权页面跳转 @RequestMapping("/noAuth") public String noAuth(){ return "noAuth"; } //登陆成功页面 @RequestMapping("/success") public String success(){ return "success"; } //退出当前用户 @RequestMapping("/logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return "index"; } /** * 登录逻辑处理 */ @RequestMapping("/login") public String login(String username, String password, Model model){ System.out.println("name="+username); /** * 使用Shiro编写认证操作 */ //1.获取Subject Subject subject = SecurityUtils.getSubject(); //2.封装用户数据 UsernamePasswordToken token = new UsernamePasswordToken(username,password); //3.执行登录方法 try { subject.login(token); //登录成功 //跳转到test.html return "redirect:/success"; } catch (UnknownAccountException e) { //e.printStackTrace(); //登录失败:用户名不存在 model.addAttribute("msg", "用户名不存在!"); return "index"; }catch (IncorrectCredentialsException e) { //e.printStackTrace(); //登录失败:密码错误 model.addAttribute("msg", "密码错误!"); return "index"; } } }
5,我的登录页面用的是index.html,和登录成功之后的一个success.html页面
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>用户登录</title> <link rel='stylesheet' href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css'> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"></script> </head> <body class="form-bg"> <div class="container my-form"> <div class="row"> <div class="col-md-offset-3 col-md-6"> <!--th:object="${user}"--> <form class="form-horizontal" th:action="@{/login}" method="post"> <span class="heading">用户登录</span> <div class="form-group"> <input type="text" name="username" class="form-control" id="username" placeholder="用户名"> <i class="fa fa-user"></i> </div> <div class="form-group help"> <input type="password" name="password" class="form-control" id="password" placeholder="密 码"> <i class="fa fa-lock"></i> </div> <div class="form-group"> <button type="submit" class="btn btn-default">登录</button> </div> <div> <h3 th:text="${msg}" style="color: red"></h3> </div> </form> </div> </div> </div> </body> <style> .my-form { margin-top: 100px; } .form-bg { background: #bbd5ef; } .form-horizontal { background: #fff; padding-bottom: 40px; border-radius: 15px; text-align: center; } .form-horizontal .heading { display: block; font-size: 25px; font-weight: 700; padding: 35px 0; border-bottom: 1px solid #f0f0f0; margin-bottom: 30px; } .form-horizontal .form-group { padding: 0 40px; margin: 0 0 25px 0; position: relative; } .form-horizontal .form-control { background: #f0f0f0; border: none; border-radius: 20px; box-shadow: none; padding: 0 20px 0 45px; height: 40px; transition: all 0.3s ease 0s; } .form-horizontal .form-control:focus { background: #e0e0e0; box-shadow: none; outline: 0 none; } .form-horizontal .form-group i { position: absolute; top: 12px; left: 60px; font-size: 17px; color: #c8c8c8; transition: all 0.5s ease 0s; } .form-horizontal .form-control:focus + i { color: #00b4ef; } .form-horizontal .fa-question-circle { display: inline-block; position: absolute; top: 12px; right: 60px; font-size: 20px; color: #808080; transition: all 0.5s ease 0s; } .form-horizontal .fa-question-circle:hover { color: #000; } .form-horizontal .main-checkbox { float: left; width: 20px; height: 20px; background: #11a3fc; border-radius: 50%; position: relative; margin: 5px 0 0 5px; border: 1px solid #11a3fc; } .form-horizontal .main-checkbox label { width: 20px; height: 20px; position: absolute; top: 0; left: 0; cursor: pointer; } .form-horizontal .main-checkbox label:after { content: ""; width: 10px; height: 5px; position: absolute; top: 5px; left: 4px; border: 3px solid #fff; border-top: none; border-right: none; background: transparent; opacity: 0; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); } .form-horizontal .main-checkbox input[type=checkbox] { visibility: hidden; } .form-horizontal .main-checkbox input[type=checkbox]:checked + label:after { opacity: 1; } .form-horizontal .text { float: left; margin-left: 7px; line-height: 20px; padding-top: 5px; text-transform: capitalize; } .form-horizontal .btn { float: right; font-size: 14px; color: #fff; background: #00b4ef; border-radius: 30px; padding: 10px 25px; border: none; text-transform: capitalize; transition: all 0.5s ease 0s; } @media only screen and (max-width: 479px) { .form-horizontal .form-group { padding: 0 25px; } .form-horizontal .form-group i { left: 45px; } .form-horizontal .btn { padding: 10px 20px; } } </style> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>登陆成功</h3> <hr/> <div shiro:hasPermission="添加"> 添加功能: <a href="add">用户添加</a><br/> </div> <div shiro:hasPermission="修改"> 更新功能: <a href="update">用户更新</a><br/> </div> <div shiro:hasPermission="删除"> 删除功能: <a href="del">用户删除</a><br/> </div> <div shiro:hasPermission="查看"> 查看详情: <a href="detail">用户详情</a><br/> </div> <h4><a href="logout">退出</a></h4> </body> </html>
6,数据库里面有5张表,用户表,角色表,权限表,还有两张他们的关系表.
以上就是此demo的重要组成部分,如果觉得不够清晰的话,可以访问我的仓库地址 https://gitee.com/whatever110/SpringBoot-Html.git 或者去看看shiro的视频讲解,再见喽!