第一节:shiro授权
1. 重点:讲解授权流程并实现对用户授权。
2. 课程实际内容
a) 简介:回顾shiro架构及相关对象以及认证流程
b) 重点:shiro授权流程
c) 简介:授权方式
d) 重点:授权实现
e) 简介:测试
3. 重点:课堂总结
a) 授权实现
练习20分钟
第二节:自定义Realm实现授权
1. 重点:自定义Realm实现授权。
2. 课程实际内容
a) 简介:配置文件实现授权的缺点
b) 重点:通过自定义Realm实现动态授权
c) 重点:自定义Realm实现授权步骤
d) 简介:测试
3. 重点:课堂总结
a) 自定义Realm实现授权步骤
第三节:shiro与项目集成开发
1. 重点:shiro整合spring web要点
2. 课程实际内容
a) 简介:shiro整合web项目的必要性
b) 重点:在项目整合shiro
c) 简介:shiro过滤器
d) 重点:通过shiro实现登录
e) 简介:通过shiro实现退出
3. 重点:课堂总结
a) Shiro整合项目的步骤
第四节:在集成项目中实现认证及授权
1. 重点:如何在集成项目实现授权,Controller中如何控制,jsp中如何控制
2. 课程实际内容
a) 重点:认证实现
b) 重点:修改自定义Realm
c) 重点:权限注解控制
d) 重点:jsp标签控制
3. 重点:课堂总结
a) 项目中认证和授权的实现步骤及对比之前学习的认证和授权
练习30分钟
第五节:缓存管理、验证码及记住我实现
1. 重点:缓存管理的必要性及缓存管理的实现
2. 课程实际内容
a) 简介:为什么需要缓存管理
b) 重点:缓存管理的实现
c) 重点:验证码实现
d) 简介:记住我实现
3. 重点:课堂总结
a) 总结缓存管理的好处。
授权在整个权限管理中的重要性,shiro中的授权流程,如何实现授权,如何在自定义Realm中实现授权。
重点:
1. 在自定义Realm中实现授权
难点:
1. 在自定义Realm中实现授权
互动:在前一天的课程中,实现了权限管理中的身份认证,接下来讨论另外一个非常重要的点------授权。
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作
等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、
角色(Role)。
主体
主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访
问相应的资源。
资源
在应用中用户可以访问的任何东西,比如访问JSP 页面、查看/编辑某些数据、访问某个业
务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的
权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)打印文档等等。。。
角色
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权
限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总
监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
流程如下:
1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而
SecurityManager接着会委托给Authorizer;
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过
PermissionResolver把字符串转换成相应的Permission实例;
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的
角色/权限;
4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给
ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否
则返回false表示授权失败。
Shiro支持三种方式的授权:
编程式:通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 } |
注解式:通过在执行的Java方法上放置相应的注解完成:
@RequiresRoles("admin") public void hello() { //有权限 } |
没有权限将抛出相应的异常;
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
|
[users] zhangsan=1111,role1,role2 lisi=1111,role1 [roles] role1=user:create,user:update role2=user:create,user:delete |
规则:“用户名=密码,角色1,角色2” “角色=权限1,权限2”,即首先根据用户名找
到角色,然后根据角色再找到权限;即角色是权限集合;Shiro 同样不进行权限的维护,需
要我们通过Realm返回相应的权限信息。只需要维护“用户——角色”之间的关系即可。
权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
/** * * @author邹波 * @version 1.0 * @date 2016-1-21 */ public class ShiroTest { //用户登录和退出 @Test public void testPermission(){ // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境 Factory //通过工厂获得SecurityManager实例 SecurityManager securityManager = factory.getInstance(); //将securityManager设置到运行环境中 SecurityUtils.setSecurityManager(securityManager); //获取subject实例 Subject subject = SecurityUtils.getSubject(); //创建用户名,密码身份验证Token UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111"); try { //登录,即身份验证 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); //身份认证失败 } // 用户认证状态 boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用户认证状态:" + isAuthenticated);
//判断拥有角色:role1 Assert.assertTrue(subject.hasRole("role1")); //判断拥有角色:role1 and role2 Assert.assertTrue(subject.hasAllRoles(Arrays.asList("role1", "role2"))); //判断拥有角色:role1 and role2 and !role3 boolean[] result = subject.hasRoles(Arrays.asList("role1", "role2", "role3")); Assert.assertEquals(true, result[0]); Assert.assertEquals(true, result[1]); Assert.assertEquals(false, result[2]);
//判断拥有权限:user:create Assert.assertTrue(subject.isPermitted("user:create")); //判断拥有权限:user:update and user:delete Assert.assertTrue(subject.isPermittedAll("user:update", "user:delete")); //判断没有权限:user:view Assert.assertFalse(subject.isPermitted("user:view")); } }
|
通过自定义Realm实现动态授权。
重点:
1. 自定义Realm实现
难点:
1. 自定义Realm实现
与上边认证自定义realm一样,大部分情况是要从数据库获取权限数据,这里直接实现基于资源的授权。
/** * 自定义Realm实现 * @author邹波 * @version 1.0 * @date 2016-1-21 */ public class UserRealm extends AuthorizingRealm { @Override public String getName() { return "UserRealm"; } //用于认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //从token中获取身份信息 String username = (String)token.getPrincipal(); //根据用户名到数据库中取出用户信息 如果查询不到 返回null String password = "1111";//假如从数据库中获取密码为1111 //返回认证信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, this.getName()); return simpleAuthenticationInfo; } //用于授权 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //获取身份信息 String username = (String)principals.getPrimaryPrincipal(); //根据身份信息获取权限数据 //模拟 List permissions.add("user:save"); permissions.add("user:delete"); //将权限信息保存到AuthorizationInfo中 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for(String permission:permissions){ simpleAuthorizationInfo.addStringPermission(permission); } return simpleAuthorizationInfo; } } |
增加了红色部分代码
[main] #自定义 realm userRealm=cn.siggy.realm.UserRealm #将realm设置到securityManager securityManager.realms=$userRealm |
/** * 自定义Realm实现 * @author邹波 * @version 1.0 * @date 2016-1-21 */ public class UserRealm extends AuthorizingRealm { @Override public String getName() { return "UserRealm"; } //用于认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //从token中获取身份信息 String username = (String)token.getPrincipal(); //根据用户名到数据库中取出用户信息 如果查询不到 返回null String password = "1111";//假如从数据库中获取密码为1111 //返回认证信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, this.getName()); return simpleAuthenticationInfo; } //用于授权 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { //获取身份信息 String username = (String)principals.getPrimaryPrincipal(); //根据身份信息获取权限数据 //模拟 List permissions.add("user:save"); permissions.add("user:update"); permissions.add("user:delete"); //将权限信息保存到AuthorizationInfo中 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for(String permission:permissions){ simpleAuthorizationInfo.addStringPermission(permission); } return simpleAuthorizationInfo; } } |
在之前的学习中,虽然学习了shiro架构中非常重要的几块知识,但是只是在java SE项目中使用。那么如何在集成项目中使用shiro,是接下来我们要去讨论的问题。
重点:
1. 在spring web中整合shiro
难点:
1. 在spring web中整合shiro
互动:在这之前我们把shiro的相关技术点已经弄完了。那么接下来我们看看在项目中如何使用shiro。
shiro与springweb项目整合在“基于url拦截实现的工程”基础上整合,基于url拦截实现的工程的技术架构是springmvc+mybatis,整合注意两点:
1、shiro与spring整合
2、加入shiro对web应用的支持
去掉springmvc.xml中配置的LoginInterceptor和PermissionInterceptor拦截器。
<filter> <filter-name>shiroFilterfilter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
<init-param> <param-name>targetFilterLifecycleparam-name> <param-value>trueparam-value> init-param>
<init-param> <param-name>targetBeanNameparam-name> <param-value>shiroFilterparam-value> init-param> filter> <filter-mapping> <filter-name>shiroFilterfilter-name> <url-pattern>/*url-pattern> filter-mapping>
|
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.do" />
<property name="successUrl" value="/index.do"/>
<property name="unauthorizedUrl" value="/refuse.do" />
<property name="filterChainDefinitions"> <value>
/login.do=authc /logout=logout
/** = anon
value> property> bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm">property> <property name="sessionManager" ref="sessionManager">property> bean>
<bean id="customRealm" class="cn.sxt.shiro.realm.CustomRealm">
<property name="credentialsMatcher" ref="credentialsMatcher">property> bean> |
securityManager:这个属性是必须的。
loginUrl:没有登录认证的用户请求将跳转到此地址进行认证,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
unauthorizedUrl:没有权限默认跳转的页面。
此realm先不从数据库查询权限数据,当前需要先将shiro整合完成,在上边章节定义的realm基础上修改。
public class CustomRealm extends AuthorizingRealm{ @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Override public String getName() { return "CustomRealm"; } /** * 完成认证 * */ protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //完成认证信息验证 String userName = token.getPrincipal().toString(); User currentUser=null; try { currentUser = userService.login(userName); //设置菜单 currentUser.setMenus(permissionService.findMenuByUser(currentUser.getId())); //设置权限 currentUser.setPermissions(permissionService.findPermissionByUser(currentUser.getId())); } catch (Exception e) { e.printStackTrace(); } if(currentUser==null){ return null; } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), ByteSource.Util.bytes(currentUser.getSalt()),getName()); return info; }
/** * 完成授权 * */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户名 User currentUser = (User)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set for(Permission p:currentUser.getPermissions()){ permission.add(p.getPercode()); } info.addStringPermissions(permission); return info; } }
|
// 用户登陆提交 @RequestMapping("/login") public String loginsubmit(Model model, HttpServletRequest request) throws Exception {
// shiro在认证过程中出现错误后将异常类路径通过request返回 String exceptionClassName = (String) request .getAttribute("shiroLoginFailure"); if(exceptionClassName!=null){ if (UnknownAccountException.class.getName().equals(exceptionClassName)) { throw new CustomException("账号不存在"); } else if (IncorrectCredentialsException.class.getName().equals( exceptionClassName)) { throw new CustomException("用户名/密码错误"); } else if("randomCodeError".equals(exceptionClassName)){ throw new CustomException("验证码错误"); } else{ throw new Exception();//最终在异常处理器生成未知错误 } } return"login"; } |
由于session由shiro管理,需要修改首页的controller方法,将session中的数据通过model传到页面。
//系统首页 @RequestMapping("/index") public ModelAndView index(ModelMap map){ Subject subject = SecurityUtils.getSubject(); User currentUser = (User)subject.getPrincipal(); map.addAttribute("currentUser", currentUser); return new ModelAndView("index"); } |
由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可。
/logout.action = logout |
当用户无操作权限,shiro将跳转到refuse.jsp页面。
在上一节的内容中,将shiro整合进了集成项目。实现了登录,接下来实现在集成项目中认证和授权的实现。
重点:
1. 在集成项目中实现权限控制
难点:
1. 在集成项目中实现权限控制
添加凭证匹配器实现md5加密校验。
修改applicationContext-shiro.xml:
<bean id="customRealm" class="cn.sxt.shiro.realm.CustomRealm">
<property name="credentialsMatcher" ref="credentialsMatcher">property> bean>
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5">property> <property name="hashIterations" value="1">property> bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> |
修改realm代码从数据库中查询用户身份信息,将sysService注入realm。
public class CustomRealm extends AuthorizingRealm{ @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Override public String getName() { return "CustomRealm"; } /** * 完成认证 * */ protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //完成认证信息验证 String userName = token.getPrincipal().toString(); User currentUser=null; try { currentUser = userService.login(userName); //设置菜单 currentUser.setMenus(permissionService.findMenuByUser(currentUser.getId())); //设置权限 currentUser.setPermissions(permissionService.findPermissionByUser(currentUser.getId())); } catch (Exception e) { e.printStackTrace(); } if(currentUser==null){ return null; } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), ByteSource.Util.bytes(currentUser.getSalt()),getName()); return info; }
} |
修改realm代码从数据库中查询权限信息,将sysService注入realm。
public class CustomRealm extends AuthorizingRealm{ @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Override public String getName() { return "CustomRealm"; } /** * 完成授权 * */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户名 User currentUser = (User)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set for(Permission p:currentUser.getPermissions()){ permission.add(p.getPercode()); } info.addStringPermissions(permission); return info; }
} |
在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:
<aop:config proxy-target-class="true">aop:config>
<bean class=" org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> bean> |
商品查询controller方法添加权限(item:query):
// 查询商品列表 @RequestMapping("/queryItem") @RequiresPermissions("item:query") public ModelAndView queryItem() throws Exception { |
上边代码@RequiresPermissions("item:query")表示必须拥有“item:query”权限方可执行。
同理,商品修改controller方法添加权限(item:update):
@RequestMapping(value = "/editItem") @RequiresPermissions("item:update") public String editItem(@RequestParam(value = "id", required = true) Integer id, Model model) throws Exception
// 商品修改提交 @RequestMapping("/editItemSubmit") @RequiresPermissions("item:update") public String editItemSubmit(@ModelAttribute("item") Items items,BindingResult result, MultipartFile pictureFile,Model model,HttpServletRequest request) throws Exception
|
Jsp页面添加:
<%@tagliburi="http://shiro.apache.org/tags" prefix="shiro"%>
标签名称 |
标签条件(均是显示标签内容) |
|
登录之后 |
|
不在登录状态时 |
|
用户在没有RememberMe时 |
|
用户在RememberMe时 |
|
在有abc或者123角色时 |
|
拥有角色abc |
|
没有角色abc |
|
拥有权限资源abc |
|
没有abc权限资源 |
|
显示用户身份名称 |
如果有商品修改权限页面显示“修改”链接。
<shiro:hasPermission name="item:update"> <a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改a> shiro:hasPermission> |
Shiro除了提供认证授权外还提供验证码,记住我等其他常见功能。
需要在验证账号和名称之前校验验证码。
public class MyFormAuthenticationFilter extends FormAuthenticationFilter { protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
// 校验验证码 // 从session获取正确的验证码 HttpSession session = ((HttpServletRequest)request).getSession(); //页面输入的验证码 String randomcode = request.getParameter("randomcode"); //从session中取出验证码 String validateCode = (String) session.getAttribute("validateCode"); if (randomcode!=null && validateCode!=null) { if (!randomcode.equals(validateCode)) { // randomCodeError表示验证码错误 request.setAttribute("shiroLoginFailure", "randomCodeError"); //拒绝访问,不再校验账号和密码 return true; } } return super.onAccessDenied(request, response, mappedValue); } }
|
修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置。
n 在shiroFilter中添加filters:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="filters"> <map>
<entry key="authc" value-ref="formAuthenticationFilter" /> map> property> |
n formAuthenticationFilter定义
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.MyFormAuthenticationFilter ">
<property name="usernameParam" value="username" />
<property name="passwordParam" value="password" /> bean> |
添加验证码:
<TR> <TD>验证码:TD> <TD><input id="randomcode" name="randomcode" size="8" /> <img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align='absMiddle' /> <a href=javascript:randomcode_refresh()>刷新a>TD> TR> |
修改applicationContext-shiro.xml:
用户登陆选择“自动登陆”本次登陆成功会向cookie写身份信息,下次登陆从cookie中取出身份信息实现自动登陆。
向cookie记录身份信息需要用户身份信息对象实现序列化接口,如下:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm" /> <property name="sessionManager" ref="sessionManager" /> <property name="cacheManager" ref="cacheManager"/>
bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie" /> bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe" />
<property name="maxAge" value="2592000" /> bean> |
修改formAuthenticationFitler添加页面中“记住我checkbox”的input名称:
<bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
<property name="usernameParam" value="usercode" />
<property name="passwordParam" value="password" /> bean> |
在login.jsp中添加“记住我”checkbox。
<TR> <TD>TD> <TD> <input type="checkbox" name="rememberMe" />自动登陆 TD> TR> |