https://www.cnblogs.com/tohxyblog/p/8426230.html 分布式SHIRO
第一部分 什么是Apache Shiro
1、什么是 apache shiro :
Apache Shiro是一個功能強大且易於使用的Java安全框架,提供了認證,授權,加密,和會話管理
如同 spring security 一樣都是是一個權限安全框架,但是與Spring Security相比,在於他使用了和比較簡潔易懂的認證和授權方式。
2、Apache Shiro 的三大核心組件:
1、Subject :當前用戶的操作
2、SecurityManager:用於管理所有的Subject
3、Realms:用於進行權限信息的驗證
Subject:即當前用戶,在權限管理的應用程序里往往需要知道誰能夠操作什么,誰擁有操作該程序的權利,shiro中則需要通過Subject來提供基礎的當前用戶信息,Subject 不僅僅代表某個用戶,也可以是第三方進程、后台帳戶(Daemon Account)或其他類似事物。
SecurityManager:即所有Subject的管理者,這是Shiro框架的核心組件,可以把他看做是一個Shiro框架的全局管理組件,用於調度各種Shiro框架的服務。
Realms:Realms則是用戶的信息認證器和用戶的權限人證器,我們需要自己來實現Realms來自定義的管理我們自己系統內部的權限規則。
3、Authentication 和 Authorization
在shiro的用戶權限認證過程中其通過兩個方法來實現:
1、Authentication:是驗證用戶身份的過程。
2、Authorization:是授權訪問控制,用於對用戶進行的操作進行人證授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。
4、其他組件:
除了以上幾個組件外,Shiro還有幾個其他組件:
1、SessionManager :Shiro為任何應用提供了一個會話編程范式。
2、CacheManager :對Shiro的其他組件提供緩存支持。

第二部分 Apache Shiro 整合Spring的Web程序構建
1、准備工具:
持久層框架:Hibernate4 這邊我使用了hibernate來對數據持久層進行操作
控制顯示層框架:SpringMVC 這邊我使用了SpringMVC實際開發中也可以是其他框架
准備好所需要的jar放到項目中。
2、創建數據庫:
首先需要四張表,分別為 user(用戶)、role(角色)、permission(權限)、userRole(用戶角色關系表)
這邊分別創建四張表的實體類,通過Hiberante的hibernate.hbm2ddl.auto屬性的update 來自動生成數據表結構。
/*** * 用戶表 * * @author Swinglife * */ @Table(name = "t_user") @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /** 用戶名 **/ String username; /** 密碼 **/ String password; /** 是否刪除 **/ Integer isDelete; /** 創建時間 **/ Date createDate; //多對多用戶權限表 @OneToMany(mappedBy = "user",cascade=CascadeType.ALL) List<UserRole> userRoles; 省略get set…. }
/**** * 角色表 * * @author Swinglife * */ @Entity @Table(name = "t_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /**角色名**/ String name; /**角色說明**/ String description; }
/**** * 權限表 * * @author Swinglife * */ @Entity @Table(name = "t_permission") public class Permission { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /**token**/ String token; /**資源url**/ String url; /**權限說明**/ String description; /**所屬角色編號**/ Integer roleId; }
/*** * 用戶角色表 * * @author Swinglife * */ @Entity @Table(name = "t_user_role") public class UserRole { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "userId", unique = true) User user; @ManyToOne @JoinColumn(name = "roleId", unique = true) Role role; }
3、編寫操作用戶業務的Service:
@Service public class AccountService { /**** * 通過用戶名獲取用戶對象 * * @param username * @return */ public User getUserByUserName(String username) { User user = (User) dao.findObjectByHQL("FROM User WHERE username = ?", new Object[] { username }); return user; } /*** * 通過用戶名獲取權限資源 * * @param username * @return */ public List<String> getPermissionsByUserName(String username) { System.out.println("調用"); User user = getUserByUserName(username); if (user == null) { return null; } List<String> list = new ArrayList<String>(); // System.out.println(user.getUserRoles().get(0).get); for (UserRole userRole : user.getUserRoles()) { Role role = userRole.getRole(); List<Permission> permissions = dao.findAllByHQL("FROM Permission WHERE roleId = ?", new Object[] { role.getId() }); for (Permission p : permissions) { list.add(p.getUrl()); } } return list; } // 公共的數據庫訪問接口 // 這里省略BaseDao dao的編寫 @Autowired private BaseDao dao; }
4、編寫shiro組件自定義Realm:
package org.swinglife.shiro; import java.util.List; 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.UsernamePasswordToken; 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.swinglife.model.User; import org.swinglife.service.AccountService; /**** * 自定義Realm * * @author Swinglife * */ public class MyShiroRealm extends AuthorizingRealm { /*** * 獲取授權信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { //根據自己系統規則的需要編寫獲取授權信息,這里為了快速入門只獲取了用戶對應角色的資源url信息 String username = (String) pc.fromRealm(getName()).iterator().next(); if (username != null) { List<String> pers = accountService.getPermissionsByUserName(username); if (pers != null && !pers.isEmpty()) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (String each : pers) { //將權限資源添加到用戶信息中 info.addStringPermission(each); } return info; } } return null; } /*** * 獲取認證信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) at; // 通過表單接收的用戶名 String username = token.getUsername(); if (username != null && !"".equals(username)) { User user = accountService.getUserByUserName(username); if (user != null) { return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } } return null; } /**用戶的業務類**/ private AccountService accountService; public AccountService getAccountService() { return accountService; } public void setAccountService(AccountService accountService) { this.accountService = accountService; } }
上述類繼承了Shiro的AuthorizingRealm類 實現了AuthorizationInfo和AuthenticationInfo兩個方法,用於獲取用戶權限和認證用戶登錄信息
5、編寫LoginController:
package org.swinglife.controller; 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.RequestMethod; import org.springframework.web.portlet.ModelAndView; import org.swinglife.model.User; import org.swinglife.service.AccountService; /**** * 用戶登錄Controller * * @author Swinglife * */ @Controller public class LoginController { /*** * 跳轉到登錄頁面 * * @return */ @RequestMapping(value = "toLogin") public String toLogin() { // 跳轉到/page/login.jsp頁面 return "login"; } /*** * 實現用戶登錄 * * @param username * @param password * @return */ @RequestMapping(value = "login") public ModelAndView Login(String username, String password) { ModelAndView mav = new ModelAndView(); User user = accountService.getUserByUserName(username); if (user == null) { mav.setView("toLogin"); mav.addObject("msg", "用戶不存在"); return mav; } if (!user.getPassword().equals(password)) { mav.setView("toLogin"); mav.addObject("msg", "賬號密碼錯誤"); return mav; } SecurityUtils.getSecurityManager().logout(SecurityUtils.getSubject()); // 登錄后存放進shiro token UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); Subject subject = SecurityUtils.getSubject(); subject.login(token); // 登錄成功后會跳轉到successUrl配置的鏈接,不用管下面返回的鏈接。 mav.setView("redirect:/home"); return mav; } // 處理用戶業務類 @Autowired private AccountService accountService; }
6、編寫信息認證成功后的跳轉頁面:
package org.swinglife.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @RequestMapping("home") public String index() { System.out.println("登錄成功"); return "home"; } }
7、Shiro的配置文件.xml
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/toLogin" /> <property name="successUrl" value="/home" /> <property name="unauthorizedUrl" value="/403" /> <property name="filterChainDefinitions"> <value> /toLogin = authc <!-- authc 表示需要認證才能訪問的頁面 --> /home = authc, perms[/home] <!-- perms 表示需要該權限才能訪問的頁面 --> </value> </property> </bean> <bean id="myShiroRealm" class="org.swinglife.shiro.MyShiroRealm"> <property name="accountService" ref="accountService" /> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"></property> </bean> <bean id="accountService" class="org.swinglife.service.AccountService"></bean> <!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager" /> </bean> -->
loginUrl 用於配置登陸頁
successUrl 用於配置登錄成功后返回的頁面,不過該參數只會在當登錄頁面中並沒有任何返回頁面時才會生效,否則會跳轉到登錄Controller中的指定頁面。
unauthorizedUrl 用於配置沒有權限訪問頁面時跳轉的頁面
filterChainDefinitions:apache shiro通過filterChainDefinitions參數來分配鏈接的過濾,資源過濾有常用的以下幾個參數:
1、authc 表示需要認證的鏈接
2、perms[/url] 表示該鏈接需要擁有對應的資源/權限才能訪問
3、roles[admin] 表示需要對應的角色才能訪問
4、perms[admin:url] 表示需要對應角色的資源才能訪問
8、登陸頁login.jsp
<body> <h1>user login</h1> <form action="login" method="post"> username:<input type="text" name="username"><p> password:<input type="password" name="password"> <p> ${msg } <input type="submit" value="submit"> </form> </body>
9、運行程序
在數據庫中添加一條用戶、角色、以及權限數據,並且在關聯表中添加一條關聯數據:

在瀏覽器中訪問: home頁面 就會跳轉到登錄頁面:

最后輸入 賬號密碼 就會跳轉到登錄成功頁面。
關於如何實現同一時刻只有一個相同賬戶登錄的問題:
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager(); Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();
通過以上代碼得到所有的session,循環遍歷session:
for(Session session:sessions){ System.out.println("登錄ip:"+session.getHost()); System.out.println("登錄用戶"+session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)); System.out.println("最后操作日期:"+session.getLastAccessTime()); }
然后自行判斷是否存在某一個賬戶,如果不存在可以登錄,如果存在return null
如果只進入了doGetAuthenticationInfo方法,沒有進入doGetAuthorizationInfo方法,並且配置無異常,無遺漏,可能是因為在過濾器中權限配置沒有配置到你即將進入的頁面,如果沒有配置perms[],則shiro不會自行進入doGetAuthorizationInfo方法進行權限判斷。
shiro jar:http://download.csdn.net/detail/swingpyzf/8766673
項目源碼:github:https://github.com/swinglife/shiro_ex
本文轉自:http://blog.csdn.net/swingpyzf/article/details/46342023/
