十六章 综合实例——《跟我学Shiro》
目录贴:跟我学Shiro目录贴
简单的实体关系图
简单数据字典
用户(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之外的关键接口:
- 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实现
- 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层控制器
- @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页面;
- @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属性中,在该控制器中获取后来显示相应的错误信息。
- @RequiresPermissions("resource:view")
- @RequestMapping(method = RequestMethod.GET)
- public String list(Model model) {
- model.addAttribute("resourceList", resourceService.findAll());
- return "resource/list";
- }
在控制器方法上使用@RequiresPermissions指定需要的权限信息,其他的都是类似的,请参考源码。
Web层标签库
com.github.zhangkaitao.shiro.chapter16.web.taglib.Functions提供了函数标签实现,有根据编号显示资源/角色/组织机构名称,其定义放在src/main/webapp/tld/zhang-functions.tld。
Web层异常处理器
- @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相关组件。
- <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切面;否则需要在修改如资源/角色等信息时清理掉缓存。
- <bean id="sysUserFilter"
- class="com.github.zhangkaitao.shiro.chapter16.web.shiro.filter.SysUserFilter"/>
sysUserFilter用于根据当前登录用户身份获取User信息放入request;然后就可以通过request获取User。
- <property name="filterChainDefinitions">
- <value>
- /login = authc
- /logout = logout
- /authenticated = authc
- /** = user,sysUser
- </value>
- </property>
如上是shiroFilter的filterChainDefinitions定义。
Spring MVC配置——spring-mvc.xml
定义了spring mvc相关组件。
- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="com.github.zhangkaitao.shiro.chapter16
- .web.bind.method.CurrentUserMethodArgumentResolver"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
此处注册了一个@CurrentUser参数解析器。如之前的IndexController,从request获取shiro sysUser拦截器放入的当前登录User对象。
Spring MVC配置——spring-mvc-shiro.xml
定义了spring mvc相关组件。
- <aop:config proxy-target-class="true"></aop:config>
- <bean class="org.apache.shiro.spring.security
- .interceptor.AuthorizationAttributeSourceAdvisor">
- <property name="securityManager" ref="securityManager"/>
- </bean>
定义aop切面,用于代理如@RequiresPermissions注解的控制器,进行权限控制。
web.xml配置文件
定义Spring ROOT上下文加载器、ShiroFilter、及SpringMVC拦截器。具体请参考源码。
JSP页面
- <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 加上@RequiresPermissions 后@Autowired失效》
http://jinnianshilongnian.iteye.com/blog/1850425
更复杂的权限请参考我的《JavaEE项目开发脚手架》:http://github.com/zhangkaitao/es,提供了更加复杂的实现。
示例源代码:https://github.com/zhangkaitao/shiro-example;可加群 231889722 探讨Spring/Shiro技术
十六章 综合实例——《跟我学Shiro》的更多相关文章
- 第十六章 综合实例——《跟我学Shiro》
简单的实体关系图 简单数据字典 用户(sys_user) 名称 类型 长度 描述 id bigint 编号 主键 username varchar 100 用户名 password varchar 1 ...
- 第十四章 SSL——《跟我学Shiro》
目录贴:跟我学Shiro目录贴 对于SSL的支持,Shiro只是判断当前url是否需要SSL登录,如果需要自动重定向到https进行访问. 首先生成数字证书,生成证书到D:\localhost.key ...
- Gradle 1.12 翻译——第十六章. 使用文件
有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...
- 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁
原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测 ...
- Gradle 1.12用户指南翻译——第三十六章. Sonar Runner 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- 《HTTP 权威指南》笔记:第十六章&第十七章 国际化、内容协商与转码
<HTTP 权威指南>笔记:第十六章 国际化 客户端通过在请求报文中的 Accept-Language 首部和 Accept-Charset 首部来告知服务器:“我理解这些语言.”服务器通 ...
- 读《构建之法》一、二、十六章随笔a
第一章 概论 “软件团队要从需求分析开始,把合适的需求梳理出来,然后逐步开展后续工作”:——p3 问题:好的用户体验要从软件分析开始,那么软件分析仅仅是从用户的需求出发吗? 我的看法:需求分析是 ...
- SpringBoot | 第二十六章:邮件发送
前言 讲解了日志相关的知识点后.今天来点相对简单的,一般上,我们在开发一些注册功能.发送验证码或者订单服务时,都会通过短信或者邮件的方式通知消费者,注册或者订单的相关信息.而且基本上邮件的内容都是模版 ...
- 进击的Python【第十六章】:Web前端基础之jQuery
进击的Python[第十六章]:Web前端基础之jQuery 一.什么是 jQuery ? jQuery是一个JavaScript函数库. jQuery是一个轻量级的"写的少,做的多&quo ...
随机推荐
- fs模块
fs.readdir(path, callback) 异步读取目录下文件 path - 文件路径. callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,fil ...
- GIT的创建和使用
1.创建git文件模式 https://www.cnblogs.com/wupeiqi/p/7295372.html 参考地址 先看这个 初始化仓库 git init 在本地新建一个rep ...
- [ARIA] What is Accessible Name Calculation?
What's in a name? In this lesson, I'll explain the concept of naming interactive elements for screen ...
- svn diff 只显示文件名
svn diff --summarize
- QT5 文件读写操作
QFile Class 1.read读文件 加载文件对象 QFile file("文件地址"); 打开加载的文件file.open(打开方式); 操作文件 关闭打开的文件file ...
- bzoj 3498
统计三元环 很多代码在bzoj都T诶 #include <iostream> #include <cstdio> #include <algorithm> #inc ...
- mysql中Numeric类型和int类型的区别
首先记一下,Numeric数字数据只包含 数字.数字数据包括正数.负数.小数.分数和整数 例子如下: Numeric(6,2) Numeric(16,6) Numeric(16,0) 从左到右,第一个 ...
- python ros 警报上报
#!/usr/bin/env python2. # -*- coding: utf- -*- import rospy import time from common_msgs.msg import ...
- python生成二维码(简易)
首先要的配置: pillow image qrcode zxing 然后直接上代码: import PIL import qrcode # 实例化二维码生成类 qr = qrcode.QRCode( ...
- 【SPOJ】Longest Common Substring II
[SPOJ]Longest Common Substring II 多个字符串求最长公共子串 还是将一个子串建SAM,其他字符串全部跑一边,记录每个点的最大贡献 由于是所有串,要对每个点每个字符串跑完 ...