shiro与spring web项目整合

第1章 shiro与spring web项目整合

1.1shiro与springweb项目的整合

shiro 与 springweb 整合在”基于url拦截实现的工程”基础上整合,基于 url拦截实现的工程技术架构是springmvc+mybatis ,整合注意两点 :
1、shiro 与 spring整合
2、加入shiro对web应用的支持

1.2 加入shiro的jar包

  • shiro-spring(依赖web)
  • shiro-web
  • shiro-core

1.3 web.xml添加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>

1.4 applicationContext-shiro.xml

<!-- 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" />
                 <!-- 通过unauthoeizedUrl 指定没有权限操作时跳转页面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp" />
        <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 退出拦截,请求logout.action执行退出操作 -->
                /logout.action = logout
                <!-- 无权访问页面 -->
                /refuse.jsp = anon
                <!-- roles[XX]表示有XX角色才可访问 -->
                /item/list.action = roles[item],authc
                                 <!--对静态资源的匿名访问-->
                /js/** anon
                /images/** anon
                /styles/** anon
                /validatecode.jsp anon
                /item/* authc
                <!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
                /** = authc
            </value>
        </property>
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
    </bean>

    <!-- 自定义 realm -->
    <bean id="userRealm" class="cn.ty.ssm.realm.CustomRealm1"></bean>

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 实现
// 用户登陆提交
    @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();//最终在异常处理器生成未知错误
            }
        }
        return "login";

    }
1.6.3 认证拦截过滤器

在applicationContext-shiro.xml
/** = authc

1.7 退出

不用我们去实现退出,只要去访问一个退出的url(该url是可以不存在的),由LogoutFilter拦截住,消除Session

<property name="filterChainDefinitions">
            <value>
<!-- 退出拦截,请求logout.action执行退出操作 ,放到最上面,shiro去清除session
-->
                /logout.action = logout
            </value>
        </property>

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<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;
    }
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
   <!-- 通过unauthoeizedUrl 指定没有权限操作时跳转页面 -->
   <property name="unauthorizedUrl" value="/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<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;
    }

1.11.3 设置凭证匹配器

添加凭证匹配器实现md5加密校验。
修改applicationContext-shiro.xml:

<!-- 凭证匹配器 -->
    <bean id="credentialsMatcher"
        class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="1" />
    </bean>

<!-- 自定义 realm -->
    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

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());
        //..

        //根据用户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;
    }
1.12.3 开启controller类aop的支持

对 系统中类的方法给用户授权,建议 在controller层进行方法授权。在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:

<!-- 开启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.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<ItemsCustom> 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时
<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"/> 显示用户身份中的属性值

如果有商品修改权限页面显示“修改”链接。


<shiro:hasPermission name="item:update">
    <a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a>
    </shiro:hasPermission>
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
1.13.2.1 添加Ehcache的jar包
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.3</version>
</dependency>
1.13.2.2 配置cacheManager

在applicationContext-shiro.xml中配置缓存管理器。

<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

<!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>
1.13.2.3 配置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>
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:

<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="sessionManager" ref="sessionManager" />
    </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>

1.15 验证码

1.15.1 思路

shiro使用FormAuthenticationFilter进行表单验证,验证校验的功能应该加在FormAuthenticationFilter中m,在认证之前进行验证码校验。
需要些FormAuthenticationFilter的子类,继承FormAuthenticationFilter,改写它的认证方法,在认证之前进行验证码校验。

1.15.2 自定义FormAuthenticationFilter
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {

    //原FormAuthenticationFilter的认证方法
    @Override
    protected boolean onAccessDenied(ServletRequest request,
            ServletResponse response) throws Exception {
        //在这里进行验证码的校验

        //从session获取正确验证码
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpSession session =httpServletRequest.getSession();
        //取出session的验证码(正确的验证码)
        String validateCode = (String) session.getAttribute("validateCode");

        //取出页面的验证码
        //输入的验证和session中的验证进行对比 
        String randomcode = httpServletRequest.getParameter("randomcode");
        if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
            //如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
            httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
            //拒绝访问,不再校验账号和密码 
            return true; 
        }
        return super.onAccessDenied(request, response);
    }

}
1.15.3 FormAuthenticationFilter配置

修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置。
- 在shiroFilter中添加filters:

<!-- 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>
  • formAuthenticationFilter定义
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
    <bean id="formAuthenticationFilter" 
    class="com.ty.filter.authc.MyFormAuthenticationFilter ">
        <!-- 表单中账号的input名称 -->
        <property name="usernameParam" value="username" />
        <!-- 表单中密码的input名称 -->
        <property name="passwordParam" value="password" />
 </bean>
1.15.4 在登录页面添加验证码

<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.15.5 在filter配置匿名访问验证码jsp
        <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 对静态资源设置匿名访问 -->
                /images/** = anon
                /js/** = anon
                /styles/** = anon
                <!-- 验证码,可匿名访问 -->
                /validatecode.jsp = anon

猜你喜欢

转载自blog.csdn.net/fd2025/article/details/80452695