• (转)shiro权限框架详解06-shiro与web项目整合(下)


    http://blog.csdn.net/facekbook/article/details/54962975

    shiro和web项目整合,实现类似真实项目的应用

    • web项目中认证
    • web项目中授权
    • shiro缓存
    • sessionManager使用
    • 验证码功能实现
    • 记住我功能实现

    web项目中认证

    实现方式

    修改CustomRealm 的 doGetAuthenticationInfo 方法,从数据库中获取用户信息,CustomRealm 返回查询到的用户信息,包括(加密后的密码字符串和salt以及上文中的菜单)。

    修改 doGetAuthenticationInfo 方法

    @Autowired
    private SysService sysService;
    
    /**
     * 用于认证
    */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
                throws AuthenticationException {
        //第一步:通过token获取身份信息
        String userCode = (String) token.getPrincipal();
    
        //从数据库中查询账号信息是否存在
        SysUser sysUser = null; 
        try {
            sysUser = sysService.findSysUserByUserCode(userCode);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    
        //如果查询不到返回null
        if(sysUser==null){
            return null;
        }
    
        //第二步:通过获取的身份信息进行数据库查询
        String password = sysUser.getPassword();
    
        //组装ActiveUser类
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsercode(sysUser.getUsercode());
        activeUser.setUsername(sysUser.getUsername());
    
        //查询菜单信息
        List<SysPermission> menus = null;
        try {
            menus = sysService.findMenuListByUserId(sysUser.getUsercode());
        } catch (Exception e) {
            e.printStackTrace();
        }
        activeUser.setMenus(menus);
    
        //得到盐
        String salt = sysUser.getSalt();
    
        //如果查询到结果返回AuthenticationInfo
        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(activeUser, password,ByteSource.Util.bytes(salt), "");
    
        return authenticationInfo;
    }
    • 1

    设置凭证匹配器

    在我们的数据库存储的是MD5散列值,在自定义的realm中需要自定义设置散列算法以及散列次数。这里和前面介绍的散列认证的配置方式类似。

    <!-- 自定义的realm -->
    <bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
    <!--将匹配器设置到realm中 -->
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>
    <!--定义凭证匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <!-- 设置hash散列算法 -->
        <property name="hashAlgorithmName" value="md5" />
        <!-- 设置hash散列次数 -->
        <property name="hashIterations" value="1" />
    </bean>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    验证认证功能

    数据库存在两条用户数据,具体如下: 
    这里写图片描述 
    其中:张三 的密码是 111111。当然也可以自己修改密码:

    SELECT MD5('密码'+'盐')
    • 1
    • 1

    如果可能正常登录则没有问题。

    授权

    实现方式

    修改 CustomRealm 中的 doGetAuthorizationInfo 方法从数据库中查询授权信息。 
    这里讲解注解式授权和jsp标签授权方法。

    修改 doGetAuthorizationInfo 方法

    /**
      * 用于授权
      */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
        //获取身份信息,这个字段是在认证通过后返回的,也就是通过执行认证方法返回的AuthenticationInfo类的第一个属性
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
    
        //通过userId查询数据库获取该身份信息的所有权限。
        List<SysPermission> permissionList = null;
        try {
            permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
           } catch (Exception e) {
            e.printStackTrace();
           }
        List<String> permissions = new ArrayList<>();
        if(permissionList!=null){
            for(SysPermission p:permissionList){
                permissions.add(p.getPercode());
            }
        }
    
        //查询到权限信息,然后返回权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //将查询到的授权信息填充到SimpleAuthorizationInfo中
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }
    • 1

    controller类的AOP支持

    <!--开启aop,对类代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 开启shiro注解支持 -->
    <bean
         class="
    org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在ItemsController类方法上添加注解

    //商品信息方法
    @RequestMapping("/queryItems")
    @RequiresPermissions("item:query")//通过注解的方式进行授权
    public ModelAndView queryItems(HttpServletRequest request) throws Exception {
    
        System.out.println(request.getParameter("id"));
    
        //调用service查询商品列表
        List<ItemsCustom> itemsList = itemsService.findItemsList(null);
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("itemsList", itemsList);
        // 指定逻辑视图名
        modelAndView.setViewName("itemsList");
    
        return modelAndView;
    }
    • 1

    jsp标签授权

    在jsp页面添加shiro taglib

    <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
    • 1
    • 1

    shiro包括的jsp标签

    标签名称标签条件(均显示标签内容)
    <shiro:authenticated> 登录之后
    <shiro:notAuthenticated> 不在登录状态时
    <shiro:guest> 用户在没有RememberMe时
    <shiro:user> 用户在RememberMe时
    <shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时
    <shiro:hasRole name="abc"> 拥有角色abc
    <shiro:lacksRole name="abc"> 没有角色abc
    <shiro:hasPermission name="abc"> 拥有权限资源abc
    <shiro:lacksPermission name="abc"> 没有abc权限资源
    <shiro:principal> 显示用户身份名称
    <shiro:principal property="username"/> 显示用户身份中的属性值

    修改itemsList.jsp文件

        <!-- 具有item:update权限才显示修改链接,没有的话不显示。相当于if(hasPermission(item:update)) -->
    <shiro:hasPermission name="item:update">
        <a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a>
    </shiro:hasPermission>

    授权测试

    当调用controller的一个方法时,由于该方法加了@RequiresPermissions("item:query") 注解,shiro会调用realm获取数据库中的权限信息,看 item:query 是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过

    当展现一个jsp页面时,页面中如果遇到 <shiro:hasPermission name="item:update"> 标签,shiro调用realm获取数据库中的权限信息,看item:update 是否在权限数据中存在,如果不存在就不显示标签包含内容,如果存在则显示。

    在这里只要遇到注解或shiro jsp标签授权,就会调用realm查询数据库,在这里需要引入缓存解决。

    shiro缓存

    针对授权时频繁查询数据库的问题,引入shiro缓存。

    缓存流程

    用户认证通过。 
    用户第一次授权:调用realm查询数据库。 
    用户第二次授权:不调用realm查询数据库,直接从缓存中读取授权信息。

    使用 ehcache

    添加Ehcache的jar包 
    这里写图片描述 
    配置Ehcache配置文件: 
    新建shiro-ehcache.xml

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <!--diskStore:缓存数据持久化的目录 地址  -->
        <diskStore path="E:developehcache" />
        <defaultCache 
            maxElementsInMemory="1000" 
            maxElementsOnDisk="10000000"
            eternal="false" 
            overflowToDisk="false" 
            diskPersistent="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120" 
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>
    • 1

    配置cacheManager

    <!--securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    <!-- 定义shiro缓存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>
    • 1

    清空缓存

    当用户权限修改后,用户再次登录shiro会自动调用realm从数据库获取权限数据,如果在修改权限后想立即清除缓存则可以调用realm的clearCache方法清除。 
    CustomRealm 中定义clearCached方法:

        /**
         * 清除缓存方法
         */
        public void clearCache(){
            PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
            super.clearCache(principals);   
    
        }
    • 1

    验证码功能实现

    实现方式

    shiro使用FormAuthenticationFilter进行表单认证,验证码校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验。

    自定义FormAuthenticationFilter

    public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{
    
        /**
         * 原AuthenticationFilter验证方法
         */
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    
            //获取正确的验证码和用户输入的验证码进行比对
            HttpServletRequest httpServletRequest = (HttpServletRequest)request;
    
            HttpSession session = httpServletRequest.getSession();
    
            //从session获取正确验证码
            String validateCode = (String) session.getAttribute("validateCode");
            //取出页面的验证码
            String randomcode = (String) httpServletRequest.getParameter("randomcode");
            if(validateCode!=null && randomcode!=null && !validateCode.equals(randomcode)){
                //验证码不相同,给shiroLoginFailure属性设置值
                request.setAttribute("shiroLoginFailure","randomcodeError");
                //拒绝访问,不再校验账号和密码
                return true;
            }
    
            return super.onAccessDenied(request, response);
        }
    
    }
    • 1

    配置自定义的FormAuthenticationFilter

    在applicationContext-shiro.xml文件中配置

        <!-- 自定义form认证过滤器 -->
        <bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter">
            <!--表单中账号的name属性的值-->
            <property name="usernameParam" value="account"/>
            <!--表单中账号的password属性的值-->
            <property name="passwordParam" value="accountPassword"/>
        </bean>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    修改 shiroFilter 配置

        <!-- web.xml中shiro的filter对应的bean -->
        <!-- shiro的web过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <!-- logiUrl认证提交地址,如果没有认证通过将会请求此地址进行认证,请求此地址将由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>
                    <entry key="authc" value-ref="formAuthenticationFilter"/>
                </map>
            </property>
    • 1

    修改 LoginController 的 login 方法

        @RequestMapping("/login")
        public String login(HttpServletRequest request)throws Exception{
    
            //如果登录失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
            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

    在登录页面添加验证码

        <TR>
            <TD>用户名:</TD>
            <TD colSpan="2"><input type="text" id="usercode" name="account" style="WIDTH: 130px" />   </TD>
        </TR>
        <TR>
            <TD>密 码:</TD>
            <TD><input type="password" id="pwd" name="accountPassword" style="WIDTH: 130px" /></TD>
        </TR>
        <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> 
    • 1

    实现记住我功能

    用户登录选择”记住我”选项,本次登录成功会向cookie写身份信息,下次登录从cookie中取出身份信息实现自动登录。

    用户身份信息相关类实现 java.io.Serializable 接口

    向cookie记录身份信息的对象需要实现序列号接口,如下:

    public class ActiveUser implements java.io.Serializable {
    public class SysPermission implements java.io.Serializable{

    配置 rememberMeManager

    <!--配置记住我cookie-->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- rememerMe是cookie名称 -->
        <constructor-arg value="rememberMe"/>
        <property name="maxAge" value="2592000"/>
    </bean>
    <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
            <property name="cookie" ref="rememberMeCookie"/>
    </bean>

    添加到securityManager中

        <!--securityManager安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="customRealm" />
            <property name="cacheManager" ref="cacheManager"/>
            <!-- 记住我 -->
            <property name="rememberMeManager" ref="rememberMeManager"/>
        </bean>
    • 1

    修改登录页面

    <TR>
        <TD></TD>
        <TD><input type="checkbox" name="rememberMe">记住我</TD>
    </TR> 

    修改rememberMe的input名称

    在前面的配置中修改了账号和密码的input的name属性,”记住我”的name属性值也可以修改

        <!-- 自定义form认证过滤器 -->
        <bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter">
            <!--表单中账号的name属性的值-->
            <property name="usernameParam" value="account"/>
            <!--表单中账号的password属性的值-->
            <property name="passwordParam" value="accountPassword"/>
            <!-- 修改记住我的name属性的值 -->
            <property name="rememberMeParam" value="rememberMe"/>
        </bean>
    • 1

    测试记住我功能

    选择自动登录后,需要查看cookie是否有rememberMe 
    这里写图片描述

    使用UserFilter

    在前一篇博客中有说明UserFilter的功能如下:

    user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查

    我们修改applicationContext-shiro.xml配置文件

        <!-- shiro的web过滤器 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <!-- logiUrl认证提交地址,如果没有认证通过将会请求此地址进行认证,请求此地址将由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>
                    <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:update] -->
    
                    <!-- 配置记住我或认证通过可以访问地址 -->
                    /index.jsp = user
                    /first.action = user
                    /welcome.jsp = user
                    <!-- /**=authc 表示所有的url都需要认证才能访问 -->
                    /** = authc
                </value>
            </property>
        </bean>

    blog项目的下载地址

    点击进入下载页面

  • 相关阅读:
    Python进阶06 循环对象
    Python进阶05 循环设计
    Python进阶 函数的参数对应
    Python进阶01 词典
    Python基础 反过头来看看
    Python基础08 面向对象的基本概念
    利用zepto.js实现移动页面图片全屏滑动
    数组弃重方法
    fcc筆記
    文字颜色渐变效果
  • 原文地址:https://www.cnblogs.com/telwanggs/p/7118172.html
Copyright © 2020-2023  润新知