MyException - 我的异常网
当前位置:我的异常网» 开源软件 » shiro权限管理(2)

shiro权限管理(2)

www.MyException.Cn  网友分享于:2015-08-26  浏览:0次
shiro权限管理(二)

 

shiro权限管理(二)

        本篇主要是把权限控制在rest接口端,欢迎圈错,共同学习。

        采用maven的整体构造,提供sql和ws模块。(maven+spring+mybatis+Xmemcached+jetty)。

        贴图为部分代码,文末附上整个项目源码。

 

一、图示流程及效果图

            1.先看数据库表关系和内容


          2.从上图所知道

                用户名为table的用户 ,角色为管理员 ,  拥有user-select 权限;

                用户名为table1的用户, 角色为一般用户 ,不拥有 user-select权限;

         3.用户名table登录

                   

                   登录之后获取角色id和认证头bearer  去访问user-select 权限对应的接口url

                   

                   正常访问得到数据的情况

       

              4.用户名table1登录 没有权限访问该接口的情况
 

       5.总结

             1.该版权限控制在rest接口上,效果如上述所示。这只是简单的实例,根据具体的项目需要丰富表内容。

             2.前台调用后台提供接口,需先访问登录接口,获取bearer认证头和角色ID作为参数传入到每个接口。

             3.认证头为缓存实现为5分钟,避免了获取一次,永久访问的情况。

 

二、流程详解

         1.采用maven架构搭建整个工程,主要采用spring+mybatis配置后台,从数据库封装一个查询接口,提供一个restful

            具体代码略,会提供rest接口的自然懂……

                    rest接口步骤:                 测试工具:
                             1.dto                firefox插件(restClient/Httprest)
                             2.dao               secureCRT 后台查看工具(本地测试采用控制台)
                             3.daoimpl           
                             4.dto.xml
                             5.ws

            与一般的底层增删改查一致,就是ws层根据实际项目需要返回,一般返回json或者xml格式的数据

 

       2.搭建好之后,集成memcached,采用客户端为xmemcached(详见memcached客户端使用的三种方式)

      

       3.集成shiro

            1.web.xml中加入shiro过滤器,拦截rest下的所有url

            

	 <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>/rest/*</url-pattern>
	</filter-mapping>

          2.把请求的url交给shiro之后,由于我们要把权限控制在接口上,shiro.xml相应变化;

             每次请求来交给一个自定义过滤器,除开登录的url,拦截web.xml中过来的所有请求。

 

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
			
		<property name="filters">
            <util:map>
                <entry key="custom" value-ref="customFilter"/>
            </util:map>
        </property>
        
		<property name="filterChainDefinitions">
			<value>
				/rest/login = anon
				/** = custom
			</value>
		</property>
	</bean>
	<!-- 自定义过滤器 -->
	<bean id="customFilter" class="com.sss.shiro.CustomFilter"/>

            

          3.请求交给自定义过滤器

                     1.  自定义过滤器继承AuthenticatingFilter,此时会强制重写createTokenonAccessDenied两个方法。

               A. createToken :表示创建一个用户身份验证的一个令牌,在其shiro底层有两个拦截器继承了AuthenticatingFilter分别是BasicHttpAuthenticationFilterFormAuthenticationFilter这两个同时实现了createToken方法,createToken方法里面是通过每次代入用户名和密码和把请求的requestresponse放入进去生成即可(需要了解的可以仔细看看源码)。我这里用这个令牌的目的就是替换底层实现的这一套,每次在登陆的接口里面设置,登陆成功之后随机生成一个UUID作为认证的bearer

 

	/***
	 * 创建一个令牌
	 *    每次请求的信息封装在token里面,在realm中的认证方法中获取和比对
	 */
	@Override
	protected AuthenticationToken createToken(ServletRequest request,
			ServletResponse response) throws Exception {
		String name = request.getParameter("name");
		String bearer = request.getParameter("bearer");
		String roleId = request.getParameter("roleId");
		return new CustomToken(name, bearer,roleId);
	} 

    认证的token中主要的一个部分bearer是在登录接口中放入的随机UUID

 

                        user.setPwd(encryptPwd);
			user.setIp(ip);
			String bearer = UUID.randomUUID().toString();
			user.setBearer(bearer);
			user.setRoleId(roleId);
			memcached.addCache("bearer", user, 5);
			memcached.addCache("permissionList", permissions, 1);

        这是登录接口返回的user 。 缓存中放入认证头bearer,其目的就是客户端和shiro的realm中的认证方法里拿到的realm进行比对。避免一次请求,永久访问。

       权限放入缓存,是方便查询,在shiro的realm中授权方法里面比对该登录的用户是否有访问该接口的权限,先从缓存里取,加快查询速度,缓存失效拿不到再通过

       roleId查询数据中比对之后放入缓存。

       

        B.onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。

      也是该自定义过滤器的入口,主要判断认证信息是否存在,存在即交给处理方法进行处理。

     

	@Override
	protected boolean onAccessDenied(ServletRequest request,
			ServletResponse response) throws Exception {
		logger.info("访问控制拦截器中传入参数:{}",request.getParameterMap());
		String bearer = request.getParameter("bearer");
		boolean isLogin = false;
		if (StringUtils.isNotEmpty(bearer)) {
			isLogin = executeLogin(request, response);
		}
		return isLogin;
	  }  

           B-1.此处处理涉及了一个executeLogin方法得到当前的用户,通过subject.login(token);交给realm中的认证方法来认证,由于错误信息不友好,自定义executeLogin方法  覆盖掉它。

           B-2.若认证失败给接口提示,若认证成功,通过boolean isPermitted = subject.isPermitted(httpRequest.getRequestURI());把当前访问的rest接口路径交给realm中的授权方法判断是否有该接口的访问权限。有访问权限出数据,没有就给提示。

           B-3.通过subject.login(token) 这里token就是自定义的token,通过createToken创建。通过shiro的realm中的认证方法可以知道,当前认证接受的token值为AuthenticationToken。所以我们这定义的token类就去实现它。属性若干,根据实际需要定义。

 

     public class CustomToken implements AuthenticationToken {

	private static final long serialVersionUID = 1L;

	private String username;
	private String roleId;
	private String bearer;
	private User user;
	
	public CustomToken(String username,String clientDigest,String roleId){
		this.bearer = clientDigest;
		this.username = username;
		this.roleId = roleId;
	}
	public CustomToken(User user){
		 this.user= user;
	}
	
           B-4.其中的roleId是需要的。

 

 

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {

		logger.info("enter---shiro的认证方法");
		CustomToken CustomToken = (CustomToken) token;
		String bearer = CustomToken.getBearer();
		String roleId = CustomToken.getRoleId();
		User user = null;
		try {
			user = (User) memcachedUtils.findCache("bearer");
		} catch (Exception e) {
			logger.debug("缓存读取认证头失败:{}", user);
			return null;
		}
		// 然后进行客户端消息摘要和服务器端消息摘要的匹配
		SimpleAuthenticationInfo aa = new SimpleAuthenticationInfo(roleId,
				user.getBearer(), getName());
		return aa;

	}
        此处主要从登陆接口中放入的缓存头,来获取,如果有比对认证(也可以在此处通过用户名比对一次密码),认证之后返回这个认证信息,带入orderId.

        认证通过之后,在执行方法里面判断是否有权限访问该url

 

        boolean isPermitted = subject.isPermitted(httpRequest.getRequestURI());
	if(!isPermitted){
	     sendChallenge(response, "该用户没有权限访问,请联系管理员用户",null);
             return false;
	}

     通过isPermitted进入到realm的授权方法

 

protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// 先读取缓存数据,再通过roleId查询数据加入缓存
		//此处的授权参数就是接口带入的roleId,以便缓存失效时重新获取数据库中的权限集合
		logger.info("授权参数:{}", principals);
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		Set<String> permissions = new HashSet<>();
		try {
			if (memcachedUtils.findCache("permissionList") == null) {
				logger.debug("从数据库读取");
				//通过roleId来读取权限集合
				String roleId = principals.toString();
				Map<String, Object> whereMap = new HashMap<String, Object>();
				whereMap.put("roleId", roleId);
				List<Map<String, Object>> allPermissUrl = userDao
						.selectPermissionByName(whereMap);
				for (Map<String, Object> map : allPermissUrl) {
					permissions.add(dom4jReadXML(map.get("permission_name")
							.toString()));
				}
			} else {
				logger.debug("从缓存信息读取");
				//从缓存读权限集合
				ArrayList<String> permissionLists = (ArrayList) memcachedUtils
						.findCache("permissionList");
				for (String str : permissionLists) {
					permissions.add(dom4jReadXML(str));
				}
			}
			info.setStringPermissions(permissions);
		} catch (Exception e) {
			logger.debug("权限缓存放入失败");
			e.printStackTrace();
		}
		return info;
	}

        此处通过自己也有个疑问,shiro的认证和登录是两个独立的过程,但是此处的授权参数是认证方法第一个参数传入对应的属性roleId。(需要深入看看源码)

    如果登录接口的缓存放入的权限还未失效,直接比对,否则查库比对。

最后给出数据或者提示。

 

 

                  C.从上面两张图可以看到 shiro 提供了很多拦截器,实现这里可以继承很多不同的拦截器,onAccessDenied就是AccessControlFilter中的。

      继承AuthenticatingFilter主要是为了替换底层实现的这一套createToken

 

                D.最后每次访问返回出错的信息,同一由一个方法输出。这个方法在多个shiro的拦截器中实现,我们替换掉,通过接口转换成json格式输出。

	/***
	 * 认证失败,之后的提示
	 * 
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
			AuthenticationException e, ServletRequest request,
			ServletResponse response) {
		HttpServletRequest httpRequest = WebUtils.toHttp(request);
		
		try {
			String msg = "客户端[" + InetAddress.getLocalHost().getHostAddress().toString() + "]访问["
					+ httpRequest.getRequestURI() + "]认证错误.";
			sendChallenge(response, msg, null);
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
		return super.onLoginFailure(token, e, request, response);
	}

 


 三、总结

           1.主要流程也是先认证,后比对权限。认证的时候随机生成的UUID,缓存时间随需要设置。

           2.替换内部一套拦截器,按需要的逻辑简化认证授权

           3.缓存很重要

 

四、参考

          http://jinnianshilongnian.iteye.com/blog/2041909

 

 

 

 

 

1 楼 yihengvip 2014-12-26  
沙发

文章评论

我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员必看的十大电影
程序员必看的十大电影
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
漫画:程序员的工作
漫画:程序员的工作
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
一个程序员的时间管理
一个程序员的时间管理
程序员都该阅读的书
程序员都该阅读的书
每天工作4小时的程序员
每天工作4小时的程序员
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
Java程序员必看电影
Java程序员必看电影
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
中美印日四国程序员比较
中美印日四国程序员比较
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
 程序员的样子
程序员的样子
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
如何成为一名黑客
如何成为一名黑客
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
10个调试和排错的小建议
10个调试和排错的小建议
我的丈夫是个程序员
我的丈夫是个程序员
那些争议最大的编程观点
那些争议最大的编程观点
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
编程语言是女人
编程语言是女人
程序员的鄙视链
程序员的鄙视链
老程序员的下场
老程序员的下场
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
代码女神横空出世
代码女神横空出世
为什么程序员都是夜猫子
为什么程序员都是夜猫子
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
我是如何打败拖延症的
我是如何打败拖延症的
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有