飞哥路径
1.1shiro与springweb项目的整合
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.DelegatingFilterProxy
targetFilterLifecycle
true
targetBeanName
shiroFilter
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过滤器实现,原理如下:
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 = logout1.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
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没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查
注:
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="/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={ValidGroup1.class}) @ModelAttribute(value="itemsCustom") ItemsCustom itemsCustom,
BindingResult bindingResult,
//上传图片
MultipartFile pictureFile
)
1.12.5 jsp 标签授权
Jsp页面添加:
<%@ tagliburi=”http://shiro.apache.org/tags” prefix=”shiro” %>
标签名称 标签条件
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: