最近在作一個期末做品,就是使用ssm+thymeleaf+vue+shiro完成一個具備權限登陸,且能實現用戶信息增刪查改的這麼一個項目,下面僅僅是實現權限認證和登陸。爲何我選shiro,而不選spring Security,是由於我試過,security實在是比較難,封裝的太厲害了,哈哈哈哈,因此果斷放棄,選擇shiro進行。css
下一篇還實現了增刪查改,使用vue,可是沒有先後端分離,博客連接html
!github源碼鏈接,須要請自行下載。前端
提示,這個項目已經有了增刪查改,跟着下面的博客作,也能作出來頁面跳轉權限,可是沒有增刪查改。vue
如下是學習shiro的一個小Demo:java
-- ---------------------------- -- 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);
建立實體類(User,Role,Permissions):
User:mysql
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String username; private String password; //用戶對應的角色集合 private Role role; }
Role:jquery
@Data @AllArgsConstructor @NoArgsConstructor public class Role { private Integer id; private String roleName; }
Permissions:git
@Data @AllArgsConstructor @NoArgsConstructor public class Permissions { private Integer id; private String permissionName; private Role role; }
咱們須要知道三個實體類之間的關係,User與Role一對一,Role與Permissions一對一,固然也能夠把它都寫成多對多,這就須要去更改數據庫文件,和實體類了。github
下面只給出相關依賴源web
<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>
就只須要建立一個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()); } }
測試結果:
獲得了查詢結果
前端頁面:
在html頁面咱們整合了Thymeleaf,使用了Jquery,semantic,須要導包
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" rel="stylesheet">--> <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 class="column"></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>
咱們須要自定義,認證和受權:
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; } } }
@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爲權限參數
由於咱們的角色,只需用有一個角色就能訪問到映射頁面,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; } }
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"; } } }
首先是http://localhost:8080/index
登陸界面:
表單提交後,就返回值到UserController那個Login方法,認證:
這樣咱們就登陸成功了,而且是superAdmin的權限,能夠查看A,B,C
而用戶張三,只能看見A
到此結束,本博客借鑑:博客,須要源碼的請查看此博客。