shiro spring整合(一)

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进行认证,用户身份和用户密码提交数据到loginurl
FormAuthenticationFilter拦截住取出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
 */
@Controller
public 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.action
3、被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博客,写的很不错,很详细。
博客链接地址

猜你喜欢

转载自blog.csdn.net/qq_36306340/article/details/78779793
今日推荐