`
TableMiao
  • 浏览: 73849 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

 

 

 

 

 

  • 大小: 21.6 KB
  • 大小: 213.1 KB
  • 大小: 324.9 KB
  • 大小: 197.6 KB
  • 大小: 446.8 KB
  • 大小: 217.4 KB
分享到:
评论
1 楼 yihengvip 2014-12-26  
沙发

相关推荐

Global site tag (gtag.js) - Google Analytics