SpringMVC+Spring+Hibernate+Mybatis+Shiro等整合及开发(3)

shiro简介

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

    功能内部图:



Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;

SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。


二 再来看一个shiro认证流程:

     首先发出验证请求给Subject.login(用户名密码),Subject委托给SecurityManager验证,SecurityManager要拿到比较的对象,就向Realm索取,Realm是一般是自己定义的,我是从数据库里面取的。取出来返回回去,进行对比。



PS:上面有一部分内容摘自网上。

    经过上面的介绍知道,要实现简单的shiro应用我们着重关注:Realm数据获取。

但是我想实现的是基于url的权限控制;又自定义了鉴权filter。

    三.spring和shiro整合并自定义鉴权和Realm

<?xml version="1.0" encoding="UTF-8"?>
<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" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- Shiro生命周期处理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <!--shiro web过滤bean-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
        <property name="loginUrl" value="/login.do" />
        <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
        <!-- <property name="successUrl" value="/first.action"/> -->
        <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面-->
        <property name="unauthorizedUrl" value="/refuse.jsp" />

        <property name="filters">
            <util:map>
                <!--权限控制自定义filter-->
                <entry key="perms" value-ref="urlAuthorizationFilter" />
            </util:map>
        </property>

        <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 对静态资源设置访问,不然都拦截了 -->
                /images/** = anon
                /js/** = anon
                /css/** = anon
                /dologin* = anon

                <!-- 请求logout.action地址,shiro清除session -->
                /logout.do=logout
                <!-- /** = authc所有url都可以认证通过才能访问 -->
                /a.do = perms
                /** = authc
                <!-- /** = anon所有url都可以匿名访问 -->
                <!--/** = anon-->
            </value>
        </property>
    </bean>

    <!--自定义权限控制filter-->
    <bean id="urlAuthorizationFilter" class="com.yanghs.shiro.filter.UrlAuthorizationFilter"/>

    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--自定义用户信息获取器-->
        <property name="realm" ref="userRealm" />
        <!--session管理器-->
        <property name="sessionManager" ref="sessionManager"/>
        <!-- 注入缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>

    </bean>

    <!-- 自定义的realm -->
    <bean id="userRealm" class="com.yanghs.shiro.realm.UserRealm">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <!--<property name="credentialsMatcher" ref="credentialsMatcher"/>-->
        <!--开启用户缓存-->
        <property name="authenticationCachingEnabled" value="false"/>
        <!--用户缓存-->
        <!--<property name="authenticationCacheName" value=""/>-->
        <!--开启权限缓存-->
        <property name="authorizationCachingEnabled" value="true"/>
        <!--权限缓存-->
        <property name="authorizationCacheName" value="authenticationCache"/>
    </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:ehcache.xml"/>
    </bean>

    <!--session管理器-->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!--多久检查session 的有效性 1800000毫秒 半小时-->
        <property name="sessionValidationInterval" value="1800000"/>
        <!--session 有效时间-->
        <property name="globalSessionTimeout" value="1800000"/>

        <!--session 监听器 可以设置多个-->
        <property name="sessionListeners">
            <util:list>
                <ref bean="sessionListener"/>
            </util:list>
        </property>
        <!-- 间隔多少时间检查,不配置是60分钟 -->
        <!--<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>-->
        <!-- 是否开启 检测,默认开启 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <!-- 是否删除无效的,默认也是开启 -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 会话Cookie模板 -->
        <property name="sessionIdCookie" ref="simpleCookie"/>

    </bean>



    <!--session监听器-->
    <bean id="sessionListener" class="com.yanghs.shiro.listener.MySessionListener"></bean>

    <!--会话session id 生成器-->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

    <!--会话Cookie模板-->
    <bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!--cookie name-->
        <constructor-arg value="JSESSIONID"/>
        <!--如果设置为 true,则客户端不会暴露给客户端脚本代码,使用 HttpOnly cookie
            有助于减少某些类型的跨站点脚本攻击;
            此特性需要实现了 Servlet 2.5 MR6 及以上版本的规范的 Servlet 容器支持;-->
        <property name="httpOnly" value="true"/>
        <!--cookie的最大时间 关闭浏览器失效 设置时间的话为秒-->
        <property name="maxAge" value="-1"/>
    </bean>


</beans>

自定义Realm

package com.yanghs.shiro.realm;

import com.yanghs.common.entity.hbm.*;
import com.yanghs.common.service.IRoleService;
import com.yanghs.common.service.IUserService;
import com.yanghs.shiro.token.UserToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 javax.annotation.Resource;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author yanghs
 * @Description:自定义数据获取
 * @date 2018/3/1 13:57
 */
public class UserRealm extends AuthorizingRealm {
    @Resource(name = "userService")
    IUserService userService;
    @Resource(name = "roleService")
    IRoleService roleService;
    /**
     * 授权逻辑获取
     * @param principalCollection
     * @return
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Userinfo userinfo = (Userinfo) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo sa = new SimpleAuthorizationInfo();
        try {
            Collection<Authority> authorityCollection = roleService.getRoleByUser(userinfo);
            Iterator<Authority> authorityIterator = authorityCollection.iterator();
            while (authorityIterator.hasNext()){
                Authority authority = authorityIterator.next();
                sa.addStringPermission(authority.getUrl());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sa;
    }

    /**
     * 用户登录逻辑实现
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UserToken token = (UserToken) authenticationToken;
        Userinfo userInfo = null;
        try {
            userInfo =  userService.getUser(new Userinfo(null,token.getUsername(),null,null));
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(userInfo == null){
            throw new AccountException("用户不存在");
        }
        if(!userInfo.getPassword().equals(token.getPwd())){
            throw new AccountException("用户密码错误");
        }
        AuthenticationInfo authcinfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),this.getName());
        return  authcinfo;

    }

    /**
     * 清除认证缓存
     */
    public void clearCachedAuthenticationInfo() {
        super.clearCachedAuthenticationInfo(SecurityUtils.getSubject().getPrincipals());
    }

    /**
     * 清除权限缓存
     */
    public void clearCachedAuthorizationInfo() {
        super.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }
}

鉴权filter

package com.yanghs.shiro.filter;

import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author yanghs
 * @Description:重写权限控制filter让其支持url权限控制
 * @date 2018/3/5 14:06
 */
public class UrlAuthorizationFilter extends PermissionsAuthorizationFilter {
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String path =  httpServletRequest.getContextPath();
        String uri = httpServletRequest.getRequestURI();
        uri =  uri.substring(path.length(),uri.length());

        String[] perms = new String[1];
        perms[0] = uri;
        return super.isAccessAllowed(request, response, perms);
    }
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值