Shiro學習(16)綜合實例
簡單的實體關系圖
簡單數據字典
用戶(sys_user)
名稱 |
類型 |
長度 |
描述 |
id |
bigint |
|
編號 主鍵 |
username |
varchar |
100 |
用戶名 |
password |
varchar |
100 |
密碼 |
salt |
varchar |
50 |
鹽 |
role_ids |
varchar |
100 |
角色列表 |
locked |
bool |
|
賬戶是否鎖定 |
組織機構(sys_organization)
名稱 |
類型 |
長度 |
描述 |
id |
bigint |
|
編號 主鍵 |
name |
varchar |
100 |
組織機構名 |
priority |
int |
|
顯示順序 |
parent_id |
bigint |
|
父編號 |
parent_ids |
varchar |
100 |
父編號列表 |
available |
bool |
|
是否可用 |
資源(sys_resource)
名稱 |
類型 |
長度 |
描述 |
id |
bigint |
|
編號 主鍵 |
name |
varchar |
100 |
資源名稱 |
type |
varchar |
50 |
資源類型, |
priority |
int |
|
顯示順序 |
parent_id |
bigint |
|
父編號 |
parent_ids |
varchar |
100 |
父編號列表 |
permission |
varchar |
100 |
權限字符串 |
available |
bool |
|
是否可用 |
角色(sys_role)
名稱 |
類型 |
長度 |
描述 |
id |
bigint |
|
編號 主鍵 |
role |
varchar |
100 |
角色名稱 |
description |
varchar |
100 |
角色描述 |
resource_ids |
varchar |
100 |
授權的資源 |
available |
bool |
|
是否可用 |
資源:表示菜單元素、頁面按鈕元素等;菜單元素用來顯示界面菜單的,頁面按鈕是每個頁面可進行的操作,如新增、修改、刪除按鈕;使用type來區分元素類型(如menu表示菜單,button代表按鈕),priority是元素的排序,如菜單顯示順序;permission表示權限;如用戶菜單使用user:*;也就是把菜單授權給用戶後,用戶就擁有了user:*權限;如用戶新增按鈕使用user:create,也就是把用戶新增按鈕授權給用戶後,用戶就擁有了user:create權限了;available表示資源是否可用,如菜單顯示/不顯示。
角色:role表示角色標識符,如admin,用於後臺判斷使用;description表示角色描述,如超級管理員,用於前端顯示給用戶使用;resource_ids表示該角色擁有的資源列表,即該角色擁有的權限列表(顯示角色),即角色是權限字符串集合;available表示角色是否可用。
組織機構:name表示組織機構名稱,priority是組織機構的排序,即顯示順序;available表示組織機構是否可用。
用戶:username表示用戶名;password表示密碼;salt表示加密密碼的鹽;role_ids表示用戶擁有的角色列表,可以通過角色再獲取其權限字符串列表;locked表示用戶是否鎖定。
此處如資源、組織機構都是樹型結構:
id |
name |
parent_id |
parent_ids |
1 |
總公司 |
0 |
0/ |
2 |
山東分公司 |
1 |
0/1/ |
3 |
河北分公司 |
1 |
0/1/ |
4 |
濟南分公司 |
2 |
0/1/2/ |
parent_id表示父編號,parent_ids表示所有祖先編號;如0/1/2/表示其祖先是2、1、0;其中根節點父編號為0。
為了簡單性,如用戶-角色,角色-資源關系直接在實體(用戶表中的role_ids,角色表中的resource_ids)裏完成的,沒有建立多余的關系表,如要查詢擁有admin角色的用戶時,建議建立關聯表,否則就沒必要建立了。在存儲關系時如role_ids=1,2,3,;多個之間使用逗號分隔。
用戶組、組織機構組本實例沒有實現,即可以把一組權限授權給這些組,組中的用戶/組織機構就自動擁有這些角色/權限了;另外對於用戶組可以實現一個默認用戶組,如論壇,不管匿名/登錄用戶都有查看帖子的權限。
更復雜的權限請參考我的《JavaEE項目開發腳手架》:http://github.com/zhangkaitao/es。
表/數據SQL
具體請參考
sql/ shiro-schema.sql (表結構)
sql/ shiro-data.sql (初始數據)
默認用戶名/密碼是admin/123456。
實體
具體請參考com.github.zhangkaitao.shiro.chapter16.entity包下的實體,此處就不列舉了。
DAO
具體請參考com.github.zhangkaitao.shiro.chapter16.dao包下的DAO接口及實現。
Service
具體請參考com.github.zhangkaitao.shiro.chapter16.service包下的Service接口及實現。以下是出了基本CRUD之外的關鍵接口:
Java代碼- public interface ResourceService {
- Set<String> findPermissions(Set<Long> resourceIds); //得到資源對應的權限字符串
- List<Resource> findMenus(Set<String> permissions); //根據用戶權限得到菜單
- }
- public interface RoleService {
- Set<String> findRoles(Long... roleIds); //根據角色編號得到角色標識符列表
- Set<String> findPermissions(Long[] roleIds); //根據角色編號得到權限字符串列表
- }
- public interface UserService {
- public void changePassword(Long userId, String newPassword); //修改密碼
- public User findByUsername(String username); //根據用戶名查找用戶
- public Set<String> findRoles(String username);// 根據用戶名查找其角色
- public Set<String> findPermissions(String username);// 根據用戶名查找其權限
- }
Service實現請參考源代碼,此處就不列舉了。
UserRealm實現
Java代碼- public class UserRealm extends AuthorizingRealm {
- @Autowired private UserService userService;
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- String username = (String)principals.getPrimaryPrincipal();
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- authorizationInfo.setRoles(userService.findRoles(username));
- authorizationInfo.setStringPermissions(userService.findPermissions(username));
- System.out.println(userService.findPermissions(username));
- return authorizationInfo;
- }
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- String username = (String)token.getPrincipal();
- User user = userService.findByUsername(username);
- if(user == null) {
- throw new UnknownAccountException();//沒找到帳號
- }
- if(Boolean.TRUE.equals(user.getLocked())) {
- throw new LockedAccountException(); //帳號鎖定
- }
- return new SimpleAuthenticationInfo(
- user.getUsername(), //用戶名
- user.getPassword(), //密碼
- ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
- getName() //realm name
- );
- }
- }
此處的UserRealm和《第六章Realm及相關對象》中的UserRealm類似,通過UserService獲取帳號及角色/權限信息。
Web層控制器
Java代碼- @Controller
- public class IndexController {
- @Autowired
- private ResourceService resourceService;
- @Autowired
- private UserService userService;
- @RequestMapping("/")
- public String index(@CurrentUser User loginUser, Model model) {
- Set<String> permissions = userService.findPermissions(loginUser.getUsername());
- List<Resource> menus = resourceService.findMenus(permissions);
- model.addAttribute("menus", menus);
- return "index";
- }
- }
IndexController中查詢菜單在前臺界面顯示,請參考相應的jsp頁面;
Java代碼
- @Controller
- public class LoginController {
- @RequestMapping(value = "/login")
- public String showLoginForm(HttpServletRequest req, Model model) {
- String exceptionClassName = (String)req.getAttribute("shiroLoginFailure");
- String error = null;
- if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
- error = "用戶名/密碼錯誤";
- } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
- error = "用戶名/密碼錯誤";
- } else if(exceptionClassName != null) {
- error = "其他錯誤:" + exceptionClassName;
- }
- model.addAttribute("error", error);
- return "login";
- }
- }
LoginController用於顯示登錄表單頁面,其中shiro authc攔截器進行登錄,登錄失敗的話會把錯誤存到shiroLoginFailure屬性中,在該控制器中獲取後來顯示相應的錯誤信息。
Java代碼
- @RequiresPermissions("resource:view")
- @RequestMapping(method = RequestMethod.GET)
- public String list(Model model) {
- model.addAttribute("resourceList", resourceService.findAll());
- return "resource/list";
- }
[email protected]限信息,其他的都是類似的,請參考源碼。
Web層標簽庫
com.github.zhangkaitao.shiro.chapter16.web.taglib.Functions提供了函數標簽實現,有根據編號顯示資源/角色/組織機構名稱,其定義放在src/main/webapp/tld/zhang-functions.tld。
Web層異常處理器
Java代碼- @ControllerAdvice
- public class DefaultExceptionHandler {
- @ExceptionHandler({UnauthorizedException.class})
- @ResponseStatus(HttpStatus.UNAUTHORIZED)
- public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
- ModelAndView mv = new ModelAndView();
- mv.addObject("exception", e);
- mv.setViewName("unauthorized");
- return mv;
- }
- }
如果拋出UnauthorizedException,將被該異常處理器截獲來顯示沒有權限信息。
spring配置——spring-config.xml
定義了context:component-scan來掃描除web層的組件、dataSource(數據源)、事務管理器及事務切面等;具體請參考配置源碼。
Spring配置——spring-config-cache.xml
定義了spring通用cache,使用ehcache實現;具體請參考配置源碼。
Spring配置——spring-config-shiro.xml
定義了shiro相關組件。
Java代碼- <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter16.realm.UserRealm">
- <property name="credentialsMatcher" ref="credentialsMatcher"/>
- <property name="cachingEnabled" value="false"/>
- </bean>
userRealm組件禁用掉了cache,可以參考https://github.com/zhangkaitao/es/tree/master/web/src/main/java/com/sishuok/es/extra/aop實現自己的cache切面;否則需要在修改如資源/角色等信息時清理掉緩存。
Java代碼
- <bean id="sysUserFilter"
- class="com.github.zhangkaitao.shiro.chapter16.web.shiro.filter.SysUserFilter"/>
sysUserFilter用於根據當前登錄用戶身份獲取User信息放入request;然後就可以通過request獲取User。
Java代碼
- <property name="filterChainDefinitions">
- <value>
- /login = authc
- /logout = logout
- /authenticated = authc
- /** = user,sysUser
- </value>
- </property>
如上是shiroFilter的filterChainDefinitions定義。
Spring MVC配置——spring-mvc.xml
定義了spring mvc相關組件。
Java代碼- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="com.github.zhangkaitao.shiro.chapter16
- .web.bind.method.CurrentUserMethodArgumentResolver"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
[email protected]dexController,從request獲取shiro sysUser攔截器放入的當前登錄User對象。
Spring MVC配置——spring-mvc-shiro.xml
定義了spring mvc相關組件。
Java代碼- <aop:config proxy-target-class="true"></aop:config>
- <bean class="org.apache.shiro.spring.security
- .interceptor.AuthorizationAttributeSourceAdvisor">
- <property name="securityManager" ref="securityManager"/>
- </bean>
定義aop切面,[email protected],進行權限控制。
web.xml配置文件
定義Spring ROOT上下文加載器、ShiroFilter、及SpringMVC攔截器。具體請參考源碼。
JSP頁面
Java代碼- <shiro:hasPermission name="user:create">
- <a href="${pageContext.request.contextPath}/user/create">用戶新增</a><br/>
- </shiro:hasPermission>
使用shiro標簽進行權限控制。具體請參考源碼。
系統截圖
訪問http://localhost:8080/chapter16/;
首先進入登錄頁面,輸入用戶名/密碼(默認admin/123456)登錄:
登錄成功後到達整個頁面主頁,並根據當前用戶權限顯示相應的菜單,此處菜單比較簡單,沒有樹型結構顯示
然後就可以進行一些操作,如組織機構維護、用戶修改、資源維護、角色授權
相關資料
《跟我學spring3》
http://www.iteye.com/blogs/subjects/spring3
《跟開濤學SpringMVC》
http://www.iteye.com/blogs/subjects/kaitao-springmvc
《簡單shiro擴展實現NOT、AND、OR權限驗證》
http://jinnianshilongnian.iteye.com/blog/1864800
《Shiro+Struts2+Spring3 [email protected] [email protected]
http://jinnianshilongnian.iteye.com/blog/1850425
更復雜的權限請參考我的《JavaEE項目開發腳手架》:http://github.com/zhangkaitao/es,提供了更加復雜的實現。
Shiro學習(16)綜合實例