shiro权限框架实战

shiro框架作为一种开源的权限框架,通过将身份认证和授权从具体的业务逻辑中分离出来极大地提高了我们的开发速度,它的易用性使得它越来越受到人们的青睐。与之前的ACL权限框架相比,shiro能更容易的实现权限控制,而且作为基于RBAC的权限管理框架通过与shiro标签结合使用,能够让开发人员在更加细粒度的层面上进行控制。举个例子来讲,之前我们使用基于ACL的权限控制大多是控制到连接(这里的连接大家可以简单的认为是页面,下同)层面,也就是通过给用户授权让这个用户对某些连接拥有权限,这种情况显然不太适合具体的项目开发,因为在某些情况下,某个用户可能只对某个连接的某个部分有权限,比如这个连接的页面上有增删改查四个按钮,而当前登录用户对这个页面有查看的权限,但是没有增删改的权限,如果用之前的基于ACL的权限管理,我们手动控制某个按钮的显示,某些按钮的不显示是十分麻烦的,shiro通过标签就很好的解决了这个问题。shiro不但能细化控制粒度,而且通过加密算法能够更加安全的保证用户密码的安全性,下面结合实例介绍一下shiro的具体使用。

1.spring集成shiro

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>

<!-- 加载spring的配置****begin -->
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>classpath*:config/spring/appCtx-*.xml</param-value>
    </context-param>
<!-- 加载spring的配置****end -->

<!-- 加载Log4j的配置****begin -->
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>
    <listener>
    	<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
<!-- 加载Log4j的配置****end -->    

<!--
  解决Hibernate的Session的关闭与开启问题
  功能是用来把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。目的是为了实现"Open Session in View"的模式。例如: 它允许在事务提交之后延迟加载显示所需要的对象
 -->
    <filter>
        <filter-name>openSessionInViewFilter</filter-name>
         <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
    </filter>
	<filter-mapping>
        <filter-name>openSessionInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

<!-- 加载shiro的配置*********begin***** -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
<!-- 加载shiro的配置*********end***** -->       

<!-- 加载struts2的配置******begin****** -->
     <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
<!-- 加载struts2的配置*******end********* -->
</web-app>

2.shiro的主要配置文件shiro.xml文件:

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       ">
<!-- 自动扫描加载spring的bean*****begin********* -->
   <context:annotation-config />
   <context:component-scan base-package="com" />
<!-- 自动扫描加载spring的bean*****end********* -->

<!-- 加载spring的properties文件*****begin********* -->
   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="fileEncoding" value="utf-8" />
	    <property name="locations">
			<list>
		       <value>classpath*:/config/properties/deploy.properties</value>
		    </list>
	    </property>
	</bean>
<!-- 加载spring的properties文件*****end******** -->      	

<!-- 加载数据库的相关连接****************begin********** -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${datasource.url}" />
        <property name="username" value="${datasource.username}" />
        <property name="password" value="${datasource.password}" />
	    <property name="driverClassName" value="${datasource.driverClassName}"></property>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${druid.initialPoolSize}" />
        <property name="minIdle" value="${druid.minPoolSize}" />
        <property name="maxActive" value="${druid.maxPoolSize}" />

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${druid.maxWait}" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

		<property name="validationQuery" value="${druid.validationQuery}" />
		<property name="testWhileIdle" value="${druid.testWhileIdle}" />
		<property name="testOnBorrow" value="${druid.testOnBorrow}" />
		<property name="testOnReturn" value="${druid.testOnReturn}" />

		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
		<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
		<property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />

		<!-- 配置监控统计拦截的filters,如需防御SQL注入则加入wall -->
		<property name="filters" value="${druid.filters}" />
		<property name="connectionProperties" value="${druid.connectionProperties}" />
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
<!--    	<property name="packagesToScan">-->
<!--			<list>-->
<!--				<value>com.wenc.*.po</value>-->
<!--			</list>-->
<!--		</property>-->
		<property name="packagesToScan"
			value="com.wenc.core.po" />
<!--		<property name="mappingLocations"> 此处添加Java类和数据库表的映射关系|mappingLocations代替mappingResources  -->
<!--			<list>-->
<!--				<value>classpath:/com/wec/po/**/*.hbm.xml</value>	-->
<!--			</list>-->
<!--		</property>-->
        <property name="hibernateProperties">
            <props>
               <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.query.substitutions">${hibernate.query.substitutions}</prop>
                <prop key="hibernate.default_batch_fetch_size">${hibernate.default_batch_fetch_size}</prop>
                <prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop>
                <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
                <prop key="hibernate.bytecode.use_reflection_optimizer">${hibernate.bytecode.use_reflection_optimizer}</prop>
                <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
                <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
                <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
                <prop key="net.sf.ehcache.configurationResourceName">${net.sf.ehcache.configurationResourceName}</prop>
                <prop key="hibernate.cache.use_structured_entries">${hibernate.cache.use_structured_entries}</prop>
            </props>
        </property>
	</bean>
<!-- 加载数据库的相关连接****************end********** -->

<!-- spring的事务控制****************begin********** -->
    <!-- 开启AOP监听 只对当前配置文件有效 -->
	<aop:aspectj-autoproxy expose-proxy="true"/>

	<!-- 开启注解事务 只对当前配置文件有效 -->
  	<tx:annotation-driven transaction-manager="transactionManager"/>

	<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref bean="sessionFactory" />
		</property>
		<property name="globalRollbackOnParticipationFailure" value="true" />
	</bean>

    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
			<tx:method name="do*" propagation="REQUIRED" />
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="up*" propagation="REQUIRED" />
			<tx:method name="del*" propagation="REQUIRED" />
			<tx:method name="sear*"  propagation="REQUIRED" read-only="true" />
			<tx:method name="search*"  propagation="REQUIRED" read-only="true" />
			<tx:method name="find*"  propagation="REQUIRED" read-only="true" />
			<tx:method name="get*"  propagation="REQUIRED" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <aop:config expose-proxy="true" proxy-target-class="true">
		<aop:pointcut id="txPointcut" expression="execution(* com.wenc.*.service.*.*(..))" />
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="txPointcut" order="1"/>
    </aop:config>
<!-- spring的事务控制****************end********** -->

<!-- shiro的配置*************************begin********** -->
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 自定义的realm -->
        <property name="realm" ref="sampleRealmService"/>
    </bean>

     <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

   <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- 登陆页面的连接 -->
        <property name="loginUrl" value="/login.jsp"/>
        <!-- 身份验证后跳转的连接 -->
        <property name="successUrl" value="/loginAction.action"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="authc">
                    <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
                </entry>
            </util:map>
        </property>
        <!-- 指定过滤器
        	Anon:不指定过滤器,不错是这个过滤器是空的,什么都没做,跟没有一样。
			Authc:验证,这些页面必须验证后才能访问,也就是我们说的登录后才能访问。
			这里还有其他的过滤器,我没用,比如说授权
         -->
        <property name="filterChainDefinitions">
            <value>
                /loginAction.action=anon
                /** = authc
            </value>
        </property>
    </bean>
<!-- shiro的配置*************************end********** -->
</beans>

3.主要的实现类有三个分别是PersonAction,UserPermissionInterceptor,SampleRealmService,这三个之间的相互协作完成了shiro的整个认证和授权过程,下面我们来看各个类的作用:

package com.wenc.test.service.web;

@Controller
public class PersonAction extends BaseAction implements ModelDriven<User> {

	private static Logger logger =Logger.getLogger(SampleRealmService.class);

	@Autowired
	private PersonService personService;

	public String login()throws Exception{
		//对用户输入的密码进行MD5加密
		String newPassword = CipherUtil.MD5Encode(info.getPassword());
		logger.info(info.getUsername()+"="+info.getPassword());
		Subject currentUser = SecurityUtils.getSubject();  

		UsernamePasswordToken token = new UsernamePasswordToken(  info.getUsername(), newPassword);
                //token.setRememberMe(true); //是否记住我
                try {
        	    /**currentUser.login(token) 提交申请,验证能不能通过,也就是交给shiro。这里会回调reaml(或自定义的realm)里的一个方法
                                protected AuthenticationInfo doGetAuthenticationInfo() */
                     currentUser.login(token);
                } catch (AuthenticationException e) { //验证身份失败
                     logger.info("验证登陆客户身份失败!");
                     this.addActionError("用户名或密码错误,请重新输入!");
                     return "fail";
                }  

                /**Shiro验证后,跳转到此处,这里判断验证是否通过 */
                if(currentUser.isAuthenticated()){  //验证身份通过
        	     return SUCCESS;
                }else{
        	     this.addActionError("用户名或密码错误,请重新输入!");
                     return "fail";
                 }  

	}

}

这个类的login方法是当我们输入用户名和密码之后,点击登录按钮所执行的方法,由于在数据库中用户的密码是密文形式,所以在进行用户身份验证,我们必须以同样的加密方式来加密用户在页面上输入的密码,然后将用户名和加密后的密码放入令牌(也就是token中),之后shiro会通过比对token中的用户名和密码是否与数据库中存放的真正的用户名和密码来确定用户是否为合法用户,而这个验证过程是shiro为我们完成的,当执行currentUser.login(token)方法的时候会触发验证过程,但是通常情况下这个验证过程是通过我们来自定义完成的,为此我们必须自己写一个realm类来继承shiro的AuthorizingRealm类并覆盖其AuthenticationInfo
doGetAuthenticationInfo(AuthenticationToken authcToken)方法,来看SampleRealmService类,这个就是继承AuthorizingRealm并覆盖其方法后的类:

package com.wenc.core.service;

@Component
public class SampleRealmService extends AuthorizingRealm {

	private static Logger logger =Logger.getLogger(SampleRealmService.class);
	@Autowired
    private PersonDAO personDAO;

	public SampleRealmService() {
    	logger.info("-------AAA1------------------");
        setName("sampleRealmService");
       // setCredentialsMatcher(new Sha256CredentialsMatcher());
    }

    /**
     * 身份验证
     * @param authcToken 登陆Action封装的令牌
     */
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        /**查询对应的用户是否存在*/
        User user =personDAO.getUser(token.getUsername(), token.getPassword().toString());
        logger.info(user);
        if( user != null ) {
            return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
        } else {
            return null;
        }
    }
    /**
     * 授权
     * 注意:统一在struts的拦截器中处理,见UserPermissionInterceptor.java
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Integer userId = (Integer) principals.fromRealm(getName()).iterator().next();
        logger.info("用户ID:"+userId);
        User user = personDAO.getUser(userId);
        if( user != null ) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            for( Role role : user.getRoles() ) {
                info.addRole(role.getName());
                Set<Perms> set= role.getPermissions();
                logger.info(set);
                for(Perms perm:set){
                	info.addStringPermission(perm.getActionName());
                }
            }
            return info;
        } else {
            return null;
        }
    }

}

如同上面介绍的那样执行验证的过程就进入了身份认证方法体中,也就是在这里讲数据库中查询出来的真实的用户信息和token中的用户信息进行比对,当验证成功后跳转至strut.xml中配置的index.jsp页面,截图如下:

至此我们完成了用户身份验证过程,接下来我们介绍授权过程和通过shiro标签来介绍细粒度的权限控制。当我们点击“主页2”这个超链接的时候会被struts.xml文件中定义的拦截器拦截,拦截器UserPermissionInterceptor代码如下:

package com.wenc.core.web.interceptor;

public class UserPermissionInterceptor extends AbstractInterceptor {

	private static final long serialVersionUID = -2185920708747626659L;
	private static final Log logger = LogFactory.getLog(UserPermissionInterceptor.class);

	 @Override
	public String intercept(ActionInvocation invocation) throws Exception {
		ActionContext ac = invocation.getInvocationContext();
		Map map = ac.getParameters();

		String actionName = ac.getName();
 		String methodName = "";
                String[] _methodName = (String[]) map.get("method");
    		if (_methodName != null) {
			methodName = _methodName[0];
  		}
		logger.info("actionName:"+actionName+",方法名:"+methodName);

		Subject currentUser = SecurityUtils.getSubject();
		/**判断是否已经授权*/
		if(!currentUser.isPermitted(actionName)){
	    	 logger.info("没有有权限");
	     }
 		return invocation.invoke();
	 }
}

当点击“主页2”之后会首先被该拦截器拦截,拦截的过程中会将当前请求(即点击“主页2”对应的action)的action名称取出,我们要验证的就是该用户是否享有对该action的权限,执行到currentUser.isPermitted(actionName)方法的时候就触发了shiro的授权认证功能,同样我们也对这个方法进行了重写,进入的是SampleRealmService类中的授权方法AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principals),在这个函数中我们取出了数据库中配置的该用户的权限,并将用户的所有权限加入到info中,然后返回请求页面,当加载请求页面的时候执行到shiro标签的时候会再次触发授权(注意这次将不被拦截),相当于再次从数据库中将该用户的权限加载了一遍,并且放入到info中,然后shiro标签会根据shiro:hasPermission或者是shiro:hasRole进行比对,如果存在则显示,否则不显示,当然shiro标签除了这两种方式外还有很多种其他的方式,大家可以自行探索。至此整个shiro身份认证和授权介绍完毕,谢谢阅读,欢迎指正。

时间: 2024-10-11 07:15:03

shiro权限框架实战的相关文章

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

http://blog.csdn.net/facekbook/article/details/54947730 shiro和web项目整合,实现类似真实项目的应用 本文中使用的项目架构是springMVC+mybatis,所以我们是基于搭建好的项目进行改造的. 将shiro整合到web应用中 登录 退出 认证信息在页面展现,也就是显示菜单 shiro的过滤器 将shiro整合到web应用中 数据库脚步 sql脚步放到项目中,项目上传到共享的资源中,文章最后给出共享url. 去除项目中不使用shi

Shiro权限框架简介

http://blog.csdn.net/xiaoxian8023/article/details/17892041 Shiro权限框架简介 2014-01-05 23:51 3111人阅读 评论(37) 收藏 举报  分类: [java框架](25)  版权声明:本文为博主原创文章,未经博主允许不得转载.如需转载请声明:[转自 http://blog.csdn.net/xiaoxian8023 ] 目录(?)[+] 最近加入了gxpt项目组,被安排做权限模块,所以也有幸第一次接触到了Shiro

shiro权限框架与spring框架轻松整合

2017年06月26日 17:53:30 阅读数:419 shiro是一个权限框架,用于管理网站的权限,大到网站登录过滤,小到一个菜单或按钮是否显示,shiro学习起来非常简单,以下是shiro的执行流程图: 看完不懂的请下载shiro全套视频教程: http://pan.baidu.com/s/1jHOX2MM Subject为当前用户,当它访问系统的时候,就会经过SecurityManager安全管理器,安全管理器类似一个中转站,它实际上会让Realm类来处理用户的认证和授权信息,认证和授权

关于Apache Shiro权限框架的一些使用误区的解释

多了不说了,进入正题,shiro是个权限框架提供权限管理等功能,网上的教程一般都是互相抄,比如<shiro:principal property="xxx"/>这个标签,网上教程告诉你可以用来获取登录用户的任何属性,但现实中如果你这么写,并且按照开涛教程上写的登陆逻辑,肯定百分百报错,这是为什么呢?因为网上教程的登录部分一般这么写: 这是重写authorizingrealm的dogetAuthenticationinfo方法: protected Authenticatio

shiro权限框架

权限的组成部分:用户 资源 角色 权限 数据库关系表设计是根据自己项目需求设计的 account表role表(id,rolename)account_role(id,aid,rid)permission(id,pername)role_permission(id,rid,pid) 没有设置用户和权限的关系,我们可以认为用户的权限是通过角色来决定的 1.导入jar包 shiro-all-1.2.1.jar 2.配置web.xml <!-- 权限过滤器--> <filter> <

Shiro 权限框架使用总结

我们首先了解下什么是shiro ,Shiro 是 JAVA 世界中新近出现的权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势 Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证.授权.管理会话以及密码加密.如下是它所具有的特点: 易于理解的 Java Security API: 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等): 对角

SpringMVC整合Shiro权限框架

尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro>系列,在我学习的过程中发挥了很大的指导作用.学习一个新的东西首先就是做一个demo,多看不如多敲,只有在实践中才能发现自己的欠缺,下面记录下来我整合shiro的过程.如果有不足之处,还望各位看官多多指出. 一.基本名词解释 Apache Shiro是一个强大易用的Java安全框架.它可以帮助我们完成:

(转)shiro权限框架详解02-权限理论介绍

http://blog.csdn.net/facekbook/article/details/54893042 权限管理解决方案 本文主要介绍权限管理的解决方法: 粗颗粒度和细颗粒度 基于url拦截 使用权限管理框架 粗颗粒度和细颗粒度 什么是粗颗粒度和细颗粒度 在上一文中提到粗颗粒度和细颗粒度,但是没有细讲. 对资源类型的管理称为粗颗粒度权限管理,既只控制到菜单.按钮.方法,粗颗粒度的例子比如:用户具有用户管理的权限,具有导出订单的权限.对资源实例的控制称为细颗粒度权限管理,既控制到数据级别,

BOS项目 第7天(shiro权限框架进行认证和授权)

BOS项目笔记 第7天 今天内容安排: 1.权限概述(认证.授权) 2.常见的权限控制的方式(URL拦截权限控制.方法注解权限控制) 3.权限数据模型(权限表.角色表.用户表.角色权限关系表.用户角色关系表) 4.shiro框架入门 5.将shiro应用到bos项目中进行认证和授权 1. 权限概述 系统提供了很多功能,并不是所有的用户登录系统都可以操作这些功能.我们需要对用户的访问进行控制. 认证:系统提供的用于识别用户身份的功能(通常是登录功能)-----让系统知道你是谁?? 授权:系统提供的