shiro与springmvc整合

飞哥路径
1.1shiro与springweb项目的整合
【shiro与springmvc整合】shiro 与 springweb 基于 url拦截,整合注意两点 :
1、shiro 与 spring整合
2、加入shiro对web应用的支持
1.2 加入shiro的jar包
shiro-spring(依赖web)
shiro-web
shiro-core
1.3 web.xml添加shiro filter

shiroFilter org.springframework.web.filter.DelegatingFilterProxytargetFilterLifecycletrue targetBeanNameshiroFilter shiroFilter /*

1.4 applicationContext-shiro.xml
/logout.action = logout/refuse.jsp = anon/item/list.action = roles[item],authc/js/** anon /images/** anon /styles/** anon /validatecode.jsp anon /item/* authc/** = authc

1.5 静态资源
对静态资源设匿名访问:
/js/** anon /images/** anon /styles/** anon

1.6 登录
1.6.1 原理
使用FormAuthenticationFilter过滤器实现,原理如下:
  • 将用户没有认证 时,请求loginurl进行认证。用户身份和用户密码提交到loginurl。
  • FormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)。
  • FormAuthenticationFilter调用realm传入一个token(username和password)。
  • realm认证时根据username查询用户信息(在Activeuser中存储,包括menus、userid、usercode、username)。
  • 如果查询不到,realm返回null。
1.6.2 登录页面
由于FormAuthenticationFilter 的用户身份和密码的input的默认值(username和password),修改页面的账号和密码的input的名称为username和password。
1.6.2 实现
// 用户登陆提交
//登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致
@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(); //最终在异常处理器生成未知错误 } }

// 此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
// 登陆失败还到login页面
return "login"; }

1.6.3 认证拦截过滤器
在applicationContext-shiro.xml
/** = authc
1.7 退出
不用我们去实现退出,只要去访问一个退出的url(该url是可以不存在的),由LogoutFilter拦截住,自动消除Session
/logout.action = logout 1.8认证信息在页面显示
1.8.1 修改realm设置完整认证信息
从数据库查询 用户信息,将用户菜单、usercode、username等设置在SimpleAuthenticationInfo中
protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {// token是用户输入的用户名和密码 // 第一步从token中取出用户名 String userCode = (String) token.getPrincipal(); // 第二步:根据用户输入的userCode从数据库查询 SysUser sysUser = null; try { sysUser = sysService.findSysUserByUserCode(userCode); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); }// 如果查询不到返回null if(sysUser==null){// return null; } // 从数据库查询到密码 String password = sysUser.getPassword(); //盐 String salt = sysUser.getSalt(); // 如果查询到返回认证信息AuthenticationInfo//activeUser就是用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setUsername(sysUser.getUsername()); //..//根据用户id取出菜单 List menus= null; try { //通过service取出菜单 menus = sysService.findMenuListByUserId(sysUser.getId()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //将用户菜单 设置到activeUser activeUser.setMenus(menus); //将activeUser设置simpleAuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( activeUser, password,ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; }

1.8.2 将认证信息在首页显示
由于session 由shiro管理,需要修改首页的controller方法,将session中的数据通过model传到页面。
//系统首页 @RequestMapping("/index") public String index(Model model)throws Exception{ //主体 Subject subject = SecurityUtils.getSubject(); //身份 ActiveUser activeUser = (ActiveUser) subject.getPrincipal(); model.addAttribute("activeUser", activeUser); return "/index"; }

1.9 授权过滤器的测试
1.9.1 使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所对应的权限。
测试流程
1、在applicationContext-shiro.xml中配filter规则
/items/queryItems.action = perms[item:query]
2、用户在认证通过后,请求/items/queryItems.action
3、被PermissionsAuthorizationFilter拦截,发现需要”item:query”权限
4、PermissionsAuthorizationFilter 调用realm中的doGetAuthorizationInfo
5、PermissionsAuthorizationFilter 对item:query和从realm中获取 的权限进行对比,如果”item:query”在realm返回的权限列表中,授权通过。
1.9.2 创建refuse.jsp
1.9.3 问题总结
1、在applicationContext-shiro.xml中配置过滤器连接,需要将全部的url和权限对应起来进行配置,比较麻烦,不方便使用。
2、每次授权都需要调用realm查询数据库,对于性能有很大影响,可以通过shiro缓存来解决。
1.10 shiro 过滤器
过滤器简称 对应的java类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
  • anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
roles:例子/admins/user/* = roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/*=roles[“admin,guest”],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/* = perms[user:add:],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/=perms[“user:add:,user:modify:”],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/* = rest[user],根据请求的方法,相当于/admins/user/*=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/** = port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查
注:
  • anon,authcBasic,auchc,user是认证过滤器,
  • perms,roles,ssl,rest,port是授权过滤器
1.11 认证
1.11.1 需求
修改realm的doGetAuthenticationInfo从数据库查询用户信息,realm返回的用户信息中包括(md5加密后的串和salt),实现让shiro进行散列串 的校验 。
添加凭证匹配器
1.11.2 修改doGetAuthenticationInfo从数据库中获取
//realm的认证方法,从数据库查询用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用户输入的用户名和密码 // 第一步从token中取出用户名 String userCode = (String) token.getPrincipal(); // 第二步:根据用户输入的userCode从数据库查询 SysUser sysUser = null; try { sysUser = sysService.findSysUserByUserCode(userCode); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); }// 如果查询不到返回null if(sysUser==null){// return null; } // 从数据库查询到密码 String password = sysUser.getPassword(); //盐 String salt = sysUser.getSalt(); // 如果查询到返回认证信息AuthenticationInfo//activeUser就是用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setUsername(sysUser.getUsername()); //..//根据用户id取出菜单 List menus= null; try { //通过service取出菜单 menus = sysService.findMenuListByUserId(sysUser.getId()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //将用户菜单 设置到activeUser activeUser.setMenus(menus); //将activeUser设置simpleAuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( activeUser, password,ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; }

1.11.3 设置凭证匹配器
添加凭证匹配器实现md5加密校验。
修改applicationContext-shiro.xml:


1.12 授权
1.12.1 需求
修改realm的doGetAutheticationInfo从数据库查询用户信息,realm返回的用户信息中包括(md5加密后的串和salt),实现让shiro进行散列串 的校验 。
使用注解式授权方法
使用jsp标签授权方法
1.12.2 doGetAutheticationInfo从数据库查询用户信息
1、将SysService注入到realm中
public class CustomRealm extends AuthorizingRealm {
//注入service
@Autowired
private SysService sysService;
2、调用SysService方法 查询用户
//realm的认证方法,从数据库查询用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException ){
// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询 SysUser sysUser = null; try { sysUser = sysService.findSysUserByUserCode(userCode); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); }// 如果查询不到返回null if(sysUser==null){// return null; } // 从数据库查询到密码 String password = sysUser.getPassword(); //盐 String salt = sysUser.getSalt(); // 如果查询到返回认证信息AuthenticationInfo//activeUser就是用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setUsername(sysUser.getUsername()); //..//将activeUser设置simpleAuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( activeUser, password,ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; }// 用于授权 @Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从 principals获取主身份信息 //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型), ActiveUser activeUser =(ActiveUser) principals.getPrimaryPrincipal(); //根据身份信息获取权限信息 //从数据库获取到权限数据 List permissionList = null; try { permissionList = sysService.findPermissionListByUserId(activeUser.getUserid()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //单独定一个集合对象 List permissions = new ArrayList(); if(permissionList!=null){ for(SysPermission sysPermission:permissionList){ //将数据库中的权限标签 符放入集合 permissions.add(sysPermission.getPercode()); } }/*List permissions = new ArrayList(); permissions.add("user:create"); //用户的创建 permissions.add("item:query"); //商品查询权限 permissions.add("item:add"); //商品添加权限 permissions.add("item:edit"); //商品修改权限

*/ //…
//查到权限数据,返回授权信息(要包括 上边的permissions) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //将上边查询到授权信息填充到simpleAuthorizationInfo对象中 simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; }

1.12.3 开启controller类aop的支持
对 系统中类的方法给用户授权,建议 在controller层进行方法授权。在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:



1.12.4 在controller方法中添加注解
//商品信息方法
@RequestMapping("/queryItems")
@RequiresPermissions(“item:query”)//执行queryItems需要"item:query"权限
public ModelAndView queryItems(HttpServletRequest request) throws Exception {
System.out.println(request.getParameter("id")); //调用service查询商品列表 List itemsList = itemsService.findItemsList(null); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("itemsList", itemsList); // 指定逻辑视图名 modelAndView.setViewName("itemsList"); return modelAndView; } //方法返回 字符串,字符串就是逻辑视图名,Model作用是将数据填充到request域,在页面展示 @RequestMapping(value="https://www.it610.com/editItems",method={RequestMethod.GET}) @RequiresPermissions("item:update")//执行此方法需要"item:update"权限 public String editItems(Model model,Integer id)throws Exception{//将id传到页面 model.addAttribute("id", id); //调用 service查询商品信息 ItemsCustom itemsCustom = itemsService.findItemsById(id); model.addAttribute("itemsCustom", itemsCustom); //return "editItem_2"; return "editItem"; } //itemsQueryVo是包装类型的pojo //在@Validated中定义使用ValidGroup1组下边的校验 @RequestMapping("/editItemSubmit") @RequiresPermissions("item:update")//执行此方法需要"item:update"权限 public String editItemSubmit(Model model,Integer id, @Validated(value=https://www.it610.com/article/{ValidGroup1.class}) @ModelAttribute(value="itemsCustom") ItemsCustom itemsCustom, BindingResult bindingResult, //上传图片 MultipartFile pictureFile )

1.12.5 jsp 标签授权
Jsp页面添加:

标签名称 标签条件
shiro:authenticated 登录之后
shiro:notAuthenticated 不在登录状态时
shiro:guest 用户在没有RememberMe时
shiro:user 用户在RememberMe时
在有abc或者123角色时
拥有角色abc
没有角色abc
拥有权限资源abc
没有abc权限资源
shiro:principal 显示用户身份名称
显示用户身份中的属性值
如果有商品修改权限页面显示“修改”链接。

修改

1.12.6 授权测试
当调用controller的一个方法,由于该方法加了@RequiresPermissions(“item:query”),shiro调用realm获取数据库中的权限信息,看”item:query”是否在权限数据库中,如果不存在,就拒绝访问,如果存在就授权通过。
当展示一个jsp页面时,页面中如果遇到,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据库中存在,如果不存在就拒绝访问,如果存在就授权通过。
问题:只要遇到注解或jsp标签的授权,都会调用realm方法查询数据,需要使用缓存解决此问题。
1.13 shiro缓存
针对上边授权频繁查询数据库,需要使用shiro缓存。
1.13.1 缓存流程
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认是开启的。主要研究授权信息缓存,因为授权的数据量大。
用户认证通过:
该用户第一次授权,调用realm查询数据库。
该用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
1.13.2 使用ehcache
EhCache 是一个纯Java的进程内缓存框架,它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,与redis功能类似
1.13.2.1 添加Ehcache的jar包
net.sf.ehcache ehcache-core 2.5.0 org.apache.shiro shiro-ehcache 1.2.3 1.13.2.2 配置cacheManager
在applicationContext-shiro.xml中配置缓存管理器。

1.13.2.3 配置shiro-ehcache.xml






1.13.2.4 缓存清空
如果用户正常退出,缓存自动清空;
如果用户非正常退出,缓存自动清空;
如果修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
需要手动进行编程实现。
在权限修改后调用realm的clearCached方法
realm中定义clearCached方法:
下面的代码正常开发时 要放在service中调用。
在service中,权限修改后调用realm下边的方法。
//清除缓存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,调用clearCached方法。
@Controller
public class ClearShiroCache {
//注入realm @Autowired private CustomRealm customRealm; @RequestMapping("/clearShiroCache") public String clearShiroCache(){//清除缓存,将来正常开发要在service调用customRealm.clearCached() customRealm.clearCached(); return "success"; }

1.14 sessionManager
和shiro整后,使用shiro的session 管理。shiro提供了sessionDao操作,sessionDao操作会话 数据。
配置sessionManager:


    推荐阅读