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