shiro与springmvc整合

飞哥路径
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过滤器实现,原理如下:

  • 将用户没有认证 时,请求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="/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:


    
    


    
    
    
    

你可能感兴趣的:(shiro与springmvc整合)