shiro spring整合(一)

来源:互联网 发布:注册facebook网络错误 编辑:程序博客网 时间:2024/05/21 22:38

shiro介绍

(1)简介

Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。

整合

(一)第一步. 在web.xml中配置filter

<!-- shiro的filter -->    <!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->    <filter>        <filter-name>shiroFilter</filter-name>        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>        <!-- 设置true由servlet容器控制filter的生命周期 -->        <init-param>            <param-name>targetFilterLifecycle</param-name>            <param-value>true</param-value>        </init-param>        <!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->        <init-param>            <param-name>targetBeanName</param-name>            <param-value>shiroFilter</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>shiroFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>
通常将这段代码中的filter-mapping放在所有filter-mapping之前,以达到shiro是第一个对web请求进行拦截过滤之目的。这里的<filter-name>shiroFilter</filter-name>中的值shiroFilter,应该要和第二步中配置的java bean的id一致。

第二步 ,在applicationContext-shiro.xml 中配置web.xml中fitler对应spring容器中的bean

<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans-3.2.xsd         http://www.springframework.org/schema/mvc         http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd         http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context-3.2.xsd         http://www.springframework.org/schema/aop         http://www.springframework.org/schema/aop/spring-aop-3.2.xsd         http://www.springframework.org/schema/tx         http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "><!-- web.xml中shiro的filter对应的bean --><!-- Shiro 的Web过滤器 -->    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <property name="securityManager" ref="securityManager" />        <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->        <property name="loginUrl" value="/login.action" />        <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->        <property name="successUrl" value="/first.action"/>        <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面-->        <property name="unauthorizedUrl" value="/refuse.jsp" />        <!-- 自定义filter配置 -->        <property name="filters">            <map>                <!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->                <entry key="authc" value-ref="formAuthenticationFilter" />            </map>        </property>        <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->        <property name="filterChainDefinitions">            <value>                <!-- 对静态资源设置匿名访问 -->                /images/** = anon                /js/** = anon                /styles/** = anon                <!-- 验证码,可匿名访问 -->                /validatecode.jsp = anon                <!-- 请求 logout.action地址,shiro去清除session-->                /logout.action = logout                <!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->                <!-- /items/queryItems.action = perms[item:query]                /items/editItems.action = perms[item:edit] -->                <!-- 配置记住我或认证通过可以访问的地址 -->                /index.jsp  = user                /first.action = user                /welcome.jsp = user                <!-- /** = authc 所有url都必须认证通过才可以访问-->                /** = authc                <!-- /** = anon所有url都可以匿名访问 -->            </value>        </property>    </bean><!-- securityManager安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <property name="realm" ref="customRealm" />        <!-- 注入缓存管理器 -->        <property name="cacheManager" ref="cacheManager"/>        <!-- 注入session管理器 -->        <property name="sessionManager" ref="sessionManager" />        <!-- 记住我 -->        <property name="rememberMeManager" ref="rememberMeManager"/>    </bean><!-- realm --><bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">    <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->    <property name="credentialsMatcher" ref="credentialsMatcher"/></bean><!-- 凭证匹配器 --><bean id="credentialsMatcher"    class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">    <property name="hashAlgorithmName" value="md5" />    <property name="hashIterations" value="1" /></bean><!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>    </bean><!-- 会话管理器 -->    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">        <!-- session的失效时长,单位毫秒 -->        <property name="globalSessionTimeout" value="600000"/>        <!-- 删除失效的session -->        <property name="deleteInvalidSessions" value="true"/>    </bean><!-- 自定义form认证过虑器 --><!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->    <bean id="formAuthenticationFilter"     class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">        <!-- 表单中账号的input名称 -->        <property name="usernameParam" value="username" />        <!-- 表单中密码的input名称 -->        <property name="passwordParam" value="password" />        <!-- 记住我input的名称 -->        <property name="rememberMeParam" value="rememberMe"/> </bean><!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">        <property name="cookie" ref="rememberMeCookie" />    </bean>    <!-- 记住我cookie -->    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">        <!-- rememberMe是cookie的名字 -->        <constructor-arg value="rememberMe" />        <!-- 记住我cookie生效时间30天 -->        <property name="maxAge" value="2592000" />    </bean></beans>

第三步,登录

  • 登录原理
使用FormAuthenticationFilter过虑器实现 ,原理如下:当用户没有认证时,请求loginurl进行认证,用户身份和用户密码提交数据到loginurlFormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)FormAuthenticationFilter调用realm传入一个token(username和password)realm认证时根据username查询用户信息(在Activeuser中存储,包括 userid、usercode、username、menus)。如果查询不到,realm返回null,FormAuthenticationFilter向request域中填充一个参数(记录了异常信息)
  • 登录页面
由于FormAuthenticationFilter的用户身份和密码的input的默认值(username和password),修改页面的账号和密码 的input的名称为username和password
  • 登陆代码实现
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/** *  * <p>Title: LoginController</p> * <p>Description: 登陆和退出</p> * @version 1.0 */@Controllerpublic class LoginController {    @Autowired    private SysService sysService;    //用户登陆提交方法    /**     *      * <p>Title: login</p>     * <p>Description: </p>     * @param session     * @param randomcode 输入的验证码     * @param usercode 用户账号     * @param password 用户密码      * @return     * @throws Exception     */    /*@RequestMapping("/login")    public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{        //校验验证码,防止恶性攻击        //从session获取正确验证码        String validateCode = (String) session.getAttribute("validateCode");        //输入的验证和session中的验证进行对比         if(!randomcode.equals(validateCode)){            //抛出异常            throw new CustomException("验证码输入错误");        }        //调用service校验用户账号和密码的正确性        ActiveUser activeUser = sysService.authenticat(usercode, password);        //如果service校验通过,将用户身份记录到session        session.setAttribute("activeUser", activeUser);        //重定向到商品查询页面        return "redirect:/first.action";    }*/    //登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致    @RequestMapping("login")    public String login(HttpServletRequest request)throws Exception{        //如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");        //根据shiro返回的异常类路径判断,抛出指定异常信息        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";    }/*  //用户退出,此方法不用写    @RequestMapping("/logout")    public String logout(HttpSession session)throws Exception{        //session失效        session.invalidate();        //重定向到商品查询页面        return "redirect:/first.action";    }*/}
  • 退出,使用LogoutFilter
不用我们去实现退出,只要去访问一个退出的url(该 url是可以不存在),由LogoutFilter拦截住,清除session。在applicationContext-shiro.xml配置LogoutFilter:

这里写图片描述

第四步,自定义relam

import java.util.ArrayList;import java.util.List;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.ByteSource;import org.springframework.beans.factory.annotation.Autowired;/** *  * <p> * Title: CustomRealm * </p> * <p> * Description:自定义realm * </p> */public class CustomRealm extends AuthorizingRealm {    //注入service    @Autowired    private SysService sysService;    // 设置realm的名称    @Override    public void setName(String name) {        super.setName("customRealm");    }    // 用于认证    //没有连接数据库的方法    /*@Override    protected AuthenticationInfo doGetAuthenticationInfo(            AuthenticationToken token) throws AuthenticationException {        // token是用户输入的用户名和密码         // 第一步从token中取出用户名        String userCode = (String) token.getPrincipal();        // 第二步:根据用户输入的userCode从数据库查询        // ....        // 如果查询不到返回null        //数据库中用户账号是zhangsansan        if(!userCode.equals("zhangsansan")){//            return null;        }        // 模拟从数据库查询到密码        String password = "111111";        // 如果查询到返回认证信息AuthenticationInfo        //activeUser就是用户身份信息        ActiveUser activeUser = new ActiveUser();        activeUser.setUserid("zhangsan");        activeUser.setUsercode("zhangsan");        activeUser.setUsername("张三");        //..        //根据用户id取出菜单        //通过service取出菜单         List<SysPermission> menus  = null;        try {            menus = sysService.findMenuListByUserId("zhangsan");        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        //将用户菜单 设置到activeUser        activeUser.setMenus(menus);        //将activeUser设置simpleAuthenticationInfo        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(                activeUser, password, this.getName());        return simpleAuthenticationInfo;    }*/    //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<SysPermission> 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;    }    // 用于授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(            PrincipalCollection principals) {        //从 principals获取主身份信息        //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),        ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();        //根据身份信息获取权限信息        //从数据库获取到权限数据        List<SysPermission> permissionList = null;        try {            permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        //单独定一个集合对象         List<String> permissions = new ArrayList<String>();        if(permissionList!=null){            for(SysPermission sysPermission:permissionList){                //将数据库中的权限标签 符放入集合                permissions.add(sysPermission.getPercode());            }        }    /*  List<String> permissions = new ArrayList<String>();        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;    }    //清除缓存    public void clearCached() {        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();        super.clearCache(principals);    }}

第五步,在applicationContext-shiro.xml中配置url所对应的权限。

1、在applicationContext-shiro.xml中配置filter规则    <!--商品查询需要商品查询权限  -->    /items/queryItems.action = perms[item:query]2、用户在认证通过后,请求/items/queryItems.action3、被PermissionsAuthorizationFilter拦截,发现需要“item:query”权限4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限5、PermissionsAuthorizationFilter对item:query 和从realm中获取权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。

这里写图片描述

第六步, 问题总结
写到这里我们的shiro就已经整合到spring中了,但是还存在着问题。

1、在applicationContext-shiro.xml中配置过虑器链接,需要将全部的url和权限对应起来进行配置,比较发麻不方便使用。2、每次授权都需要调用realm查询数据库,对于系统性能有很大影响,可以通过shiro缓存来解决。

第七步,shiro缓存

针对上边授权频繁查询数据库,需要使用shiro缓存。

  • 缓存流程
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要研究授权信息缓存,因为授权的数据量大。用户认证通过。该 用户第一次授权:调用realm查询数据库该 用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
  • 配置cacheManager
    在applicationContext-shiro.xml中添加缓存配置

这里写图片描述

  • shiro-ehcache.xml文件配置
    需要设置添加缓存配置文件,内容如下
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">    <!--diskStore:缓存数据持久化的目录 地址  -->    <diskStore path="F:\develop\ehcache" />    <defaultCache         maxElementsInMemory="1000"         maxElementsOnDisk="10000000"        eternal="false"         overflowToDisk="false"         diskPersistent="false"        timeToIdleSeconds="120"        timeToLiveSeconds="120"         diskExpiryThreadIntervalSeconds="120"        memoryStoreEvictionPolicy="LRU">    </defaultCache></ehcache>

第八步,打完收工

shiro还有许多内容没有整理到上面来,给大家推荐个shiro博客,写的很不错,很详细。
博客链接地址

原创粉丝点击