Shiro权限框架详解

      Shiro是java的一个安全框架,提供了认证、授权、加密和会话管、与web集成、缓存理等功能,对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且Shiro相对简单些。其基本功能点如下图所示:

 Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可

从应用程序角度Shiro是如何完成工作的?

也就是说对于我们而言,最简单的一个 Shiro 应用:

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;

  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

Shiro的架构理解

扫描二维码关注公众号,回复: 10604859 查看本文章

        个人是这样看的,不使用框架时,需要我们自定义的完成认证(用户校验)与授权。现在由Shiro帮助我们完成这些功能,简化了我们的开发,只需要按照Shiro的API进行操作即可。直接上代码:

Spring整合Shiro时需要导入的依赖

    <dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-core</artifactId>
		<version>1.2.3</version>
	</dependency>

	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-web</artifactId>
		<version>1.2.3</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring</artifactId>
		<version>1.2.3</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-ehcache</artifactId>
		<version>1.2.3</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-quartz</artifactId>
		<version>1.2.3</version>
	</dependency>

在web.xml中配置shiroFilter

 <!-- 配置 shiro 拦截器 DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来
	  -->
     <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      <!--  设置由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>

添加spring-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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
			http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
			http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置 shiro 的核心组件:securityManager -->
	<bean id="securityManager" 
	class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	    <!-- 配置缓存 -->
	     <property name="cacheManager" ref="cacheManager"/> 
	    <!-- 配置域realm,用户名,密码,角色都保存在域里:实现从数据库中获取用户信息,需要我们自己创建一个类(实现Realm接口) -->
	    <property name="realm" ref="usershiroRealm"/>
	    <!-- 会话管理 -->
	    <property name="sessionManager" ref="sessionManager"></property>
	</bean>
    
<!-- 配置ehcache缓存bean,导入ehcache并新建配置文件 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean> 

<!-- 配置自己域realm实现  -->
    <bean id="usershiroRealm" class="自己义realm的类路径">
    	<property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>
<!--加密器-->
	<bean id="credentialsMatcher" 
	class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
		<!--加密算法-->
		<!-- <property name="hashAlgorithmName" value="md5" /> -->
		<!--散列次数-->
		<!-- <property name="hashIterations" value="2" /> -->
	</bean>
<!-- 配置shiro的一些拦截规则,id必须和web.xml中的 shiro 拦截器名一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<!-- shiro的核心安全接口 -->
		<property name="securityManager" ref="securityManager" />
		<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,
		请求此地址将由formAuthenticationFilter进行表单认证 --> 
		 <property name="loginUrl" value="登录方法的路径" /> 
		<!-- 权限认证失败,则跳转到指定页面  及 登录后访问没有权限的页面后跳转的页面 --> 
		 <property name="unauthorizedUrl"  value="/index.jsp" />   
		<!-- Shiro连接约束配置,即过滤链的定义  
		         注意:规则是有顺序的,从上到下,拦截范围必须是从小到大的 -->
		<property name="filterChainDefinitions">
			<value>
			<!--  url = 拦截规则(anon为匿名,authc为要登录后,才能访问,logout登出过滤) -->
				<!-- 匿名访问 -->
				login/forget.html=anon
				<!-- 静态资源放行 -->
				/static/**=anon
				/page/**=anon
				<!-- 不需要拦截的方法 -->
				/login=anon
				<!--退出   请求这个地址,shiro会自动清除session  -->
				/logout = logout
				<!-- 配置记住我,设置此路径可以在下一次登录时成功 -->
				
				<!--所有的URL都必须认证才可以访问  
				认证时第一个参数是用户名是可以生成cookie.如果是用户对象user 就会失败,-->
			 	 /**=authc 
				<!--/**= anon 所有的URL都可以匿名访问-->
			</value>
		</property>
	</bean>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
	<!--shiro权限异常处理-->
	<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<property name="exceptionMappings">
			<props>
				<!-- 异常处理器,当系统抛出该异常之后跳转的页面。 -->
				<prop key="org.apache.shiro.authz.UnauthorizedException">redirect:/nopermission.jsp</prop>
			</props>
		</property>
	</bean>
	 <!-- session管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="300000"/><!--单位毫秒  -->
        <property name="deleteInvalidSessions" value="true"/><!--删除无效session  -->
	</bean>
	<!--配置authc过滤器  -->
	<bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
	<!--改变默认的username,password  -->
	<!-- <property name="usernameParam" value="name"></property>
	<property name="passwordParam" value="pwd"></property> -->
	</bean>
  </beans>

ehcache配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.-->
    <diskStore path="d:\\tempDirectory"/>
    <!-- 设置缓存的默认数据过期策略 -->
	<defaultCache
			maxElementsInMemory="1000"
			eternal="false"
			timeToIdleSeconds="120"
			timeToLiveSeconds="120"
			memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>
 <!--
        name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
        maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目

        eternal: 设置对象是否为永久的, true表示永不过期, 此时将忽略timeToIdleSeconds 和         timeToLiveSeconds属性; 默认值是false
       timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地                  处于空闲状态。
        timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中.
                该属性值必须大于或等于 timeToIdleSeconds 属性值

        overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
    -->

 自定义的ShiroRealm域

public class UserShiro extends AuthorizingRealm{
	@Autowired
	private UserService  userServiceImp;
	
	@Autowired
	private PersissionService persissionServiceImp;
	
	//设置Realm名字
	public void setName(String name) {
		super.setName("usershiroRealm");
	}
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 从token中取出身份信息  token是用户输入的信息
		String userCode = (String) token.getPrincipal();
		System.out.println("========AuthenticationInfo身份信息======userCode: "+userCode);
		//从数据库查出来的数据      根据用户输入的userCode从数据库查询
		User user = userServiceImp.findUserByName(userCode);
		System.out.println("=====从数据库查出来的user========="+user);
	    //如果用户不存在 返回null
		if(user == null) {
			throw new UnknownAccountException("用户名或密码有误!");
		}
		//如果用户存在,返回认证信息AuthenticationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = 
				new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
		System.out.println("执行了认证方法");
		return simpleAuthenticationInfo;
	}
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//从principals中获取主身份信息   在认证时已经设置
		 User user =  (User) principals.getPrimaryPrincipal();
		System.out.println("授权部分:"+user);
		if(null == user) {
			return null;
		}
		List<Persission> persission = persissionServiceImp.findPersissionById(user.getUid());
		if(persission== null || persission.size()==0) {
			return null;
		}
		//查到权限数据,返回
		SimpleAuthorizationInfo simpleAuthorizationInfo =
				new SimpleAuthorizationInfo();
		//将查到的授权信息填充到simpleAuthorizationInfo对象中
		for(Persission s:persission) {
			simpleAuthorizationInfo.addStringPermission(s.getPercode());
			System.out.println("===查到权限数据:======"+s.getPercode());
		}
		return simpleAuthorizationInfo;
	}
	//清理缓存的方法
	/*@Override  //默认清理缓存的方法  也可以自己通过当前用户去获取
	protected void clearCache(PrincipalCollection principals) {
		super.clearCache(principals);
	}*/
	public void clearCache() {
		Subject subject = SecurityUtils.getSubject();
		super.clearCache(subject.getPrincipals());
	}

}

常用的权限注解

@RequiresAuthentication
表示当前 Subject 已经通过 login 进行了身份验证;即 Subject. isAuthenticated()返回 true

@RequiresUser
表示当前 Subject 已经身份验证或者通过记住我登录的。

@RequiresGuest
表示当前 Subject 没有身份验证或通过记住我登录过,即是游客身份

@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)
表示当前 Subject 需要角色 admin 和 user。

@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
表示当前 Subject 需要权限 user:a 或 user:b

Controller层

@SystemControllerLog(operatePoint=OperatePointConstants.LOGIN, operateType=OperateTypeConstants.LOGIN)
   @PostMapping("/login")
   @CsrfToken(remove = true)
   @ResponseBody
   public Object loginPost(String username, String password) {
       logger.info("POST请求登录");

       if (StringUtils.isBlank(username)) {
           return renderError("用户名不能为空");
       }
       if (StringUtils.isBlank(password)) {
           return renderError("密码不能为空");
       }else{
       password = SymmetricEncoder.encryptStr(secretkey, password);
        LOGGER.info(password);
       }
       Subject user = SecurityUtils.getSubject();
       UsernamePasswordToken token = new UsernamePasswordToken(username,password);
       Result result=new Result();
       try {
           user.login(token);                                                                                                                                                          //在这一步跳入入自己实现的域即shiroDbRealm中验证
           return renderSuccess();
       } catch (UnknownAccountException e) {
       result.setMsg("账号不存在");
       return result;
           /*throw new RuntimeException("账号不存在!", e);*/
       } catch (DisabledAccountException e) {
       result.setMsg("账号未启用");
       return result;
           /*throw new RuntimeException("账号未启用", e);*/
       } catch (IncorrectCredentialsException e) {
       result.setMsg("密码错误,请重试");
       return result;
           /*throw new RuntimeException("密码错误,请重试", e);*/
       } catch (Throwable e) {
       result.setMsg("未知错误,请联系管理员");
       return result;
          throw new RuntimeException("未知错误,请联系管理员", e);
       }
      
   }
发布了171 篇原创文章 · 获赞 1 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/duan196_118/article/details/105183794