springboot整合shiro&shiro自定义过滤器
关于shiro的简介与使用方法在shiro分类中已经使用过了,而且在spring中已经成功的整合了shiro。下面研究springboot+thymeleaf中使用shiro。
spring整合shiro参考:https://www.cnblogs.com/qlqwjy/p/7257502.html
springboot整合shiro实际上是将xml整合的方式转为Java配置方式。
基于SpringDataJPA。
1.springboot整合shiro
1.系统权限需要的五张表(三个bean,SpringdataJPA自动创建中间表)
package cn.qlq.shiro.bean; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "system_shiro_permission") public class Permission { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; // 权限名 唯一 private String url; // 访问地址信息 唯一 private String description; // 描述信息 // get,setter }
package cn.qlq.shiro.bean; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "system_shiro_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; // 角色名 唯一 private String description; // 描述信息 @OneToMany(fetch = FetchType.EAGER) private List<Permission> permissions; // 一个用户角色对应多个权限 // getter,setter }
package cn.qlq.shiro.bean; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "system_shiro_user") public class ShiroUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String username;// 用户名 唯一 private String password;// 用户密码 @OneToMany(fetch = FetchType.EAGER) private List<Role> roles;// 用户角色 一个用户可能有一个角色,也可能有 多个角色 // getter,setter }
2. pom文件增加如下配置:
<!-- 整合shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
3.mapper层采用SpringDataJPA自动创建的接口实现
package cn.qlq.shiro.mapper; import org.springframework.data.jpa.repository.JpaRepository; import cn.qlq.shiro.bean.Permission; public interface PermissionMapper extends JpaRepository<Permission, Integer> { Permission findByName(String name); }
package cn.qlq.shiro.mapper; import org.springframework.data.jpa.repository.JpaRepository; import cn.qlq.shiro.bean.Role; public interface RoleMapper extends JpaRepository<Role, Integer> { Role findByName(String name); }
package cn.qlq.shiro.mapper; import org.springframework.data.jpa.repository.JpaRepository; import cn.qlq.shiro.bean.ShiroUser; public interface ShiroUserMapper extends JpaRepository<ShiroUser, Integer> { ShiroUser findByUsername(String username); ShiroUser findByUsernameAndPassword(String username, String password); }
4.自定义Realm和Shiro配置
package cn.qlq.shiro; import javax.annotation.PostConstruct; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 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.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import cn.qlq.shiro.bean.Permission; import cn.qlq.shiro.bean.Role; import cn.qlq.shiro.bean.ShiroUser; import cn.qlq.shiro.mapper.ShiroUserMapper; @Component public class UserAuthRealm extends AuthorizingRealm { @Autowired private ShiroUserMapper shiroUserMapper; /** * 权限核心配置 根据数据库中的该用户 角色 和 权限 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); ShiroUser user = (ShiroUser) principals.getPrimaryPrincipal(); for (Role role : user.getRoles()) { // 获取 角色 authorizationInfo.addRole(role.getName()); // 添加 角色 for (Permission permission : role.getPermissions()) { // 获取 权限 authorizationInfo.addStringPermission(permission.getName());// 添加 // 权限 } } return authorizationInfo; } /** * 用户登陆凭证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); ShiroUser user = shiroUserMapper.findByUsername(username); if (user == null) { return null; } AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return authenticationInfo; } // 清除缓存 public void clearCache() { PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principalCollection); } }
package cn.qlq.shiro; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import cn.qlq.shiro.bean.Permission; import cn.qlq.shiro.mapper.PermissionMapper; @Configuration public class ShiroConfig { @Autowired private PermissionMapper permissionMapper; @Autowired private UserAuthRealm userAuthRealm; /** * 配置 资源访问策略 . web应用程序 shiro核心过滤器配置 */ @Bean public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); factoryBean.setLoginUrl("/shiro/login.html");// 登录页 // 首页(这个不需要设置,因为是JS登录之后自己重定向) // factoryBean.setSuccessUrl("/shiro/index.html"); factoryBean.setUnauthorizedUrl("/shiro/unauthorized.html");// 未授权界面; // 自定义filter配置( 配置 拦截过滤器链) factoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap()); return factoryBean; } /** * 配置 SecurityManager,可配置一个或多个realm */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userAuthRealm); // securityManager.setRealm(xxxxRealm); return securityManager; } /** * 开启shiro 注解支持. 使以下注解能够生效 : 需要认证 * {@link org.apache.shiro.authz.annotation.RequiresAuthentication * RequiresAuthentication} 需要用户 * {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} 需要访客 * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} * 需要角色 {@link org.apache.shiro.authz.annotation.RequiresRoles * RequiresRoles} 需要权限 * {@link org.apache.shiro.authz.annotation.RequiresPermissions * RequiresPermissions} */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 配置 拦截过滤器链. map的键 : 资源地址 ; map的值 : 所有默认Shiro过滤器实例名 默认Shiro过滤器实例 参考 : * {@link org.apache.shiro.web.filter.mgt.DefaultFilter} */ private Map<String, String> setFilterChainDefinitionMap() { Map<String, String> filterMap = new LinkedHashMap<>(); // 注册 数据库中所有的权限 及其对应url List<Permission> allPermission = permissionMapper.findAll();// 数据库中查询所有权限 for (Permission p : allPermission) { filterMap.put(p.getUrl(), "perms[" + p.getName() + "]"); // 拦截器中注册所有的权限 } filterMap.put("/static/**", "anon"); // 公开访问的资源 filterMap.put("/shiro/doLogin.html", "anon"); // 登录地址放开 filterMap.put("/logout", "logout"); // 配置登出页,shiro已经帮我们实现了跳转 filterMap.put("/**", "authc"); // 所有资源都需要经过验证 return filterMap; } }
5.控制层代码和登录代码
package cn.qlq.shiro.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("shiro") public class ShiroIndexController { @RequestMapping("login") public String login() { return "shiro/login"; } @RequestMapping("index") public String index() { return "shiro/index"; } @RequestMapping("unauthorized") public String unauthorized() { return "shiro/unauthorized"; } }
package cn.qlq.shiro.controller; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import cn.qlq.shiro.bean.ShiroUser; import cn.qlq.shiro.mapper.ShiroUserMapper; import cn.qlq.utils.JSONResultUtil; @Controller @RequestMapping("shiro") public class ShiroLoginController { @Autowired private ShiroUserMapper shiroUserMapper; @RequestMapping("doLogin") @ResponseBody public JSONResultUtil<String> doLogin(String username, String password, HttpServletRequest request) { ShiroUser user = shiroUserMapper.findByUsernameAndPassword(username, password); if (user == null) { return new JSONResultUtil<>(false, "账号或者密码错误"); } // shiro中进行登录 Subject currentUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); currentUser.login(token); // 设置登录的user HttpSession session = request.getSession(); session.setAttribute("user", user); return new JSONResultUtil<>(true, "ok"); } }
6. 前台登录界面和首页(thymeleaf界面+layui框架)
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>后台登录-X-admin2.0</title> <meta name="renderer" content="webkit|ie-comp|ie-stand"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <meta http-equiv="Cache-Control" content="no-siteapp" /> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/js/jquery.min.js'}"></script> <script th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.js'}" charset="utf-8"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/MyJs/shiro/login.js'}"></script> </head> <body class="login-bg"> <div class="login layui-anim layui-anim-up"> <div class="message">x-admin2.0-管理登录(Shiro)</div> <div id="darkbannerwrap"></div> <form method="post" class="layui-form" > <input name="username" placeholder="用户名" value="admin" type="text" lay-verify="required" class="layui-input" /> <hr class="hr15"/> <input name="password" lay-verify="required" placeholder="密码" value="admin" type="password" class="layui-input"/> <hr class="hr15"/> <input value="登录" lay-submit="xx" lay-filter="login" style="width:100%;" type="submit" /> <hr class="hr20"/> </form> </div> </body> </html>
登录JS代码login.js
$(function() { layui.use('form', function(){ var form = layui.form; // layer.msg('玩命卖萌中', function(){ // //关闭后的操作 // }); //监听提交 form.on('submit(login)', function(data){ //打印一下填写的值然后区后台进行登陆 $.post("/shiro/doLogin.html",data.field,function(result){ if(result!=null && result.success == true){ window.location = "/shiro/index.html"; }else{ layer.msg(result.msg); } },'json'); return false; }); }); })
首页index.html代码:
<!doctype html> <html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"/> <title>后台登录-X-admin2.0</title> <meta name="renderer" content="webkit|ie-comp|ie-stand"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <meta http-equiv="Cache-Control" content="no-siteapp" /> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/> <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/js/jquery.min.js'}"></script> <script th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.js'}" charset="utf-8"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script> <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/MyJs/login.js'}"></script> </head> <body class="login-bg"> 首页 </body> </html>
2.thymeleaf整合Shiro标签
1.pom文件引入
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>1.2.1</version> </dependency>
2.ShiroConfig里增加如下bean
@Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }
3.thymeleaf首页改动如下:
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
4.页面正常使用shiro标签
<span shiro:authenticated="false" > <span>欢迎您:<span th:text="${session.user.username}"></span></span> </span> <shiro:hasRole name="usermanager"> 系统管理员 </shiro:hasRole> <shiro:hasPermission name="user:add"> 有增加权限 </shiro:hasPermission> <shiro:hasPermission name="user:update"> 有修改权限 </shiro:hasPermission>
3.shiro自定义过滤器
有时候我们希望指定的请求执行特殊的过滤器,例如:
(1) shiro的过滤器链中增加如下策略:
FILTER_CHAIN_DEFINITION_MAP.put("/api/v1/**", "anon, api");
以/api/v1/开头的所有请求都不用登录,然后会进去api拦截器。
(2)filterMaps 增加api过滤器
// Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 过滤器 Map<String, Filter> filterMaps = new HashMap<>(); filterMaps.put("authc", new ShiroAuthFilter()); // 增加API接口的过滤器 filterMaps.put("api", new ApiTokenFilter()); shiroFilterFactoryBean.setFilters(filterMaps); // 定义处理规则 shiroFilterFactoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap()); return shiroFilterFactoryBean; }
(3)API过滤器如下: 返回false则拒绝处理
import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.web.filter.PathMatchingFilter; import com.alibaba.fastjson.JSONObject; import com.zd.bx.utils.JSONResultUtil; import com.zd.bx.utils.shiro.APITokenUtils; import com.zd.bx.utils.web.WebUtils; /** * 验证API接口携带的Token * * @author Administrator * */ public class ApiTokenFilter extends PathMatchingFilter { @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { if (!APITokenUtils.isValidToken(request)) { WebUtils.writeJsonToResponse(response, JSONObject.toJSONString(JSONResultUtil.error("访问拒绝,无效token"))); return false; } return true; } }
【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】