Shiro-和Spring的整合

最近换的这个新公司比较闲,所以回家可以学点自己的东西(我是根据尚硅谷的视频学的,如果有侵权的行为,请联系我删除),本身一直是对Spring自己的安全框架感兴趣,但是实在是有点难度,所以退而求其次,先学学Shiro吧,shiro感觉入门比较简单,这里我们就不上概念了,直接上和Spring的整合,谈谈自己的理解,以后也好有个笔记。

截几张图大概说下吧:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Spring和Shiro的整合:

这里我们暂时使用最落后的整合spring的配置文件+shiro的整合,springboot的整合后续会更新。

  1. 首先我们来看下web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	
	<!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- Shiro Filter is defined in the spring application context: -->
	<!-- 
	1. 配置  Shiro 的 shiroFilter.  
	2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和 
	<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id. 
	-->
    <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>
	
</web-app>

看到上面的配置大家不难理解,这就是最基本的spring监听器和springMVC的核心控制器。
注意一点的是:

如果springMVC的配置没有指明配置文件路径,那么他的默认位置就应该在WEB-INF包下面,命名规则就是
< servlet-name>-servlet.xml

像上面的配置,我们的springMVC的配置文件路径就应该是:
在这里插入图片描述
对应的是web.xml中的 < servlet-name>spring< /servlet-name>

这里我们可能陌生的是shiro的过滤器ShiroFilter。

注意一点:
这里的shiroFilter的名字,即< filter-name>的值并不是随便写的,他对应着配置的applicationContext.xml中的配置的bean的id

applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    1. 配置 SecurityManager!
    -->     
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>

        <property name="sessionMode" value="native"/>
        <property name="realms" ref="jdbcRealm"/>
        
        <!-- <property name="rememberMeManager.cookie.maxAge" value="10"></property> -->
    </bean>

    <!--  
    2. 配置 CacheManager. 
    2.1 需要加入 ehcache 的 jar 包及配置文件. 
    -->     
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
    </bean>
    

    <!-- 
    	3. 配置 Realm 
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
    -->      
    <bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">

    </bean>


    <!--  
    4. 配置 LifecycleBeanPostProcessor. 可以自动的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. 
    -->       
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->
    <!--  
    5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用. 
    -->     
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!--  
    6. 配置 ShiroFilter. 
    6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                      若不一致, 则会抛出: NoSuchBeanDefinitionException. 
                      因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
    -->     
    <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="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
        <!-- <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> -->
        
        <!--  
        	配置哪些页面需要受保护. 
        	以及访问这些页面需要的权限. 
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面. 
        	3). logout 登出.
        	4). roles 角色过滤器
        -->
         
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /user.jsp = authc
                /shiro/login = anon
                /shiro/logout = logout
                
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
                
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
       
    </bean>
   
</beans>

这里我们讲解下
< bean id=“shiroFilter” class=“org.apache.shiro.spring.web.ShiroFilterFactoryBean”>这个bean的定义,这里我们看到该bean的id对应着web.xml中定义的shiro过滤器的名字,我们在这里可以定义路由的过滤。

  1. [urls]部分的配置,其格式是:url=拦截器[参数] 拦截器[参数]
    如:
    /login.jsp = anon
    /user.jsp = authc
  2. 如果当前请求的url匹配[urls]部分的某个url模式,将会执行其配置的拦截器
  3. anno表示拦截器接受匿名访问,表示不需要登录就可以访问
  4. authc表示拦截器需要身份认证通过后才能访问
  5. logout表示登出的过滤器(访问指定的url,就登出)
  6. user:表示认证或者是记住我登陆的都可以访问
  7. 我们的优先级是上面的优先级高,接受url然后从上到下依次匹配,如果匹配成功则不会向下继续匹配
  8. 如果配置的是authc,但是我们未登录,则验证不通过,默认会跳转到 < property name=“loginUrl” value="/login.jsp"/>代表的页面中

shiro的认证过程(简单理解就是登陆验证用户名密码是否正确)

  1. 获取当前的Subject,调用SecurityUtils.getSubject();
  2. 测试当前的用户是否已经被认证,即是否已经登录,调用Subject的isAuthenticated()方法
  3. 若没有被验证,则把用户名和密码封装成UsernamePasswordToken对象
    (1)创建一个表单页面
    (2)把请求提交到SpringMVC的Handler
    (3)获取用户名和密码
  4. 执行登录,调用Subject的login()方法,其实是调用AuthenticationToken的方法
  5. 自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro
    (1)实际上需要继承AuthenticatingRelam类
    (2)实现doGetAuthenicationInfo() 方法
  6. 由shiro完成密码的对比

代码说明:

login.jsp
<body>
	
	<h4>Login Page</h4>
	
	<form action="shiro/login" method="POST">
		username: <input type="text" name="username"/>
		<br><br>
		
		password: <input type="password" name="password"/>
		<br><br>
		
		<input type="submit" value="Submit"/>
	</form>
	
</body>
shiroHandler
@RequestMapping("/shiro")
public class ShiroHandler {
	
	@RequestMapping("/login")
	public String login(@RequestParam String username, @RequestParam String password) {
		Subject currentUser = SecurityUtils.getSubject();
		if(!currentUser.isAuthenticated()) {
			// 把用户名和密码封装成UsernamePasswordToken对象
			UsernamePasswordToken token = new UsernamePasswordToken(username, password);
			// 设置Remember me
			token.setRememberMe(true);
			// 执行登录
			 try {
	            	// 登录,判断传入的用户名密码是否正确,依赖于shiro.ini文件的配置
	                currentUser.login(token);
	            } 
	            // 未知的账户
	            catch (UnknownAccountException uae) {
	                System.out.println("----> There is no user with username of " + token.getPrincipal()); 
	            } 
	            // 密码错误
	            catch (IncorrectCredentialsException ice) {
	                System.out.println("----> Password for account " + token.getPrincipal() + " was incorrect!");
	            } 
	            // 用户被锁定
	            catch (LockedAccountException lae) {
	                System.out.println("The account for username " + token.getPrincipal() + " is locked.  " +
	                        "Please contact your administrator to unlock it.");
	            }
	            // ... catch more exceptions here (maybe custom ones specific to your application?
	            // 其他异常(认证时候异常的父类) 
	            catch (AuthenticationException ae) {
	                //unexpected condition?  error?
	            }
		}
		return "redirect:/list.jsp";
	}
	
}

其中currentUser.login(token)其中的token值传给了Realm中,与其继承AuthenticatingRealm的实现的方法中的参数是一个值

ShiroRealm
public class ShiroRealm extends AuthenticatingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 1. 把AuthenticationToken的token转换成UsernamePasswordToken
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		// 2. 从UsernamePasswordToken中来获取username
		String username = upToken.getUsername();
		// 3. 调用数据库的方法,从数据库中查询username对应的用户记录(这里省略不做)
		System.out.println("从数据库中获取username:" + username + " 所对应的用户信息");
		// 4. 若用户不存在,则可以抛出UnknowAccountException异常
		if("unknown".equals(username)) {
			throw new UnknownAccountException("用户不存在");
		}
		// 5. 根据用户的信息情况,可以决定是否抛出其他的异常
		if("monster".equals(username)) {
			throw new LockedAccountException("用户锁定");
		}
		// 6. 根据用户的情况,来构建AuthenticationInfo对象并且返回,通常使用的实现类是:SimpleAuthenticationInfo
		// 以下信息是从数据库中获取的
		// (1) principal: 认证的实体信息,可以是username, 也可以是数据库表对应的用户的实体类对象
		Object principal = username;
		// (2) credentials: 密码(指的是数据库查询的正确的密码)
		Object credentials = "123456";
		// (3) realmName:当前的realm对象的name,调用父类的getName()方法就行
		String realmName = getName();
		
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
		return info;
	}

}

具体的realm的代码的意思可以参考上面的注释,已经详细给出每个代码的意思。当我们的用户名是unknown和monster的时候,会抛出各自的异常,当是其他用户名的时候,且密码是123456的时候,会允许登录,登录成功,这里本身是要与数据库做交互的,这里我们偷懒暂时不做交互。

注意。通常我们前台传入的密码应该是加密的,不应该直接是明文,且应该是不可逆的。那么如何加密呢?

我们的加密用的是AuthenticatingRealm的credentialsMatcher属性来进行的密码匹对,因此我们只需要替换credentialsMatcher属性就行,这里shiro提供了很多的子类可以完成对密码的加密
(1)使用HashCredentialsMatcher对象,并且设置加密算法既可完成加密

在其配置文件中作出以下改变
(1) MD5加密

    <bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<!-- 采用MD5加密 -->
    			<property name="hashAlgorithmName" value="MD5"></property>
    			<!-- 指定加密的次数 -->
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>
  • 通过上面的配置,可以自动将前台输入的密码转换成md5加密后的密码,从而进行匹对。
  • 我们上面的配置不仅指定了我们要加密的方式,还指明了我们要加密的次数。
  • 在比较的时候,我们也应该将数据库中的密码进行加密,不能只用传来的前端密码进行加密,而不对数据库中取到的正确密码进行加密,两者都加密之后,再用加密的进行匹对
        // 指明加密的方式是MD5
		String hashAlgorithmName = "MD5";
		// 密码的明文
		Object credentials = "123456";
		// 加盐(一般就是一个随机字符串)
		Object salt = null;
		// 加密的次数
		int hashIterations = 1024;
		// 加密操作(result就是加密之后的密码)
		Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);

(2) 加盐的加密
我们通常会对其加密再加盐,所以这里我们自己实现的Realm就应该改一下

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, sault, realmName);

上面的代码就是返回的值,其中不同的是多加了一个参数,盐(sault)

一般使用ByteSource.Util.bytes()来计算盐值,且盐值应该是唯一的,一般使用随机字符串或者userId

多Realm验证
  1. 在xml中配置多个Realm对象(配置多个bean)
  2. 将配置的多个bean依赖到ModularRealmAuthenticator类中
  3. 将上面的类的id依赖到securitymanager的类中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--  
    1. 配置 SecurityManager!
    -->     
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    			<ref bean="secondRealm"/>
    		</list>
        </property>
    <!--     <property name="sessionMode" value="native"/>
        <property name="realms" ref="jdbcRealm"/> -->
        
        <!-- <property name="rememberMeManager.cookie.maxAge" value="10"></property> -->
    </bean>


    <!--  
    2. 配置 CacheManager. 
    2.1 需要加入 ehcache 的 jar 包及配置文件. 
    -->     
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
    </bean>


     <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <!-- 修改认证策略 -->
    	<property name="authenticationStrategy">
    		<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    	</property>
    </bean>  

    <!-- 
    	3. 配置 Realm 
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
    -->      
    <bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<!-- 采用MD5加密 -->
    			<property name="hashAlgorithmName" value="MD5"></property>
    			<!-- 指定加密的次数 -->
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>
    
    <bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="SHA1"></property>
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>

    <!--  
    4. 配置 LifecycleBeanPostProcessor. 可以自动的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. 
    -->       
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!--  
    5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用. 
    -->     
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!--  
    6. 配置 ShiroFilter. 
    6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                      若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
    -->     
    <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="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
        <!-- <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> -->
        
        <!--  
        	配置哪些页面需要受保护. 
        	以及访问这些页面需要的权限. 
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面. 
        	3). logout 登出.
        	4). roles 角色过滤器
        -->
         
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /user.jsp = authc
                /shiro/login = anon
                /shiro/logout = logout
                
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
                
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
       
    </bean>
    
    <!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
    <!-- <bean id="filterChainDefinitionMap" 
    	factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean>
    
    <bean id="filterChainDefinitionMapBuilder"
    	class="com.atguigu.shiro.factory.FilterChainDefinitionMapBuilder"></bean>
    
    <bean id="shiroService"
    	class="com.atguigu.shiro.services.ShiroService"></bean> -->

</beans>

除了上述配置外,还需要自己实现jdbcRealm(MD5加密)和secondRealm(SHA1加密)的类.

  • 认证策略,上面我们动态的指出了认证策略
     <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <!-- 修改认证策略 -->
    	<property name="authenticationStrategy">
    		<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    	</property>
    </bean>  

我们来简单的介绍下shiro的认证策略

AuthenticationStrategy:

AuthenticationStrategy接口的默认实现:

  • FirstSuccessfulStrategy
    只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略。
  • AtLeastOneSuccessfulStrategy:
    只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息。
  • AllSuccessfulStrategy:
    所有的Realm验证成功才算失败,且返回所有的Realm沈岑验证成功的认证信息,如果一个失败就失败了。

ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略

授权方式:

shiro支持三种方式的授权:
  • 编程式:通过写if/else授权代码块完成
  • 注解式:通过在执行的java方法上放置相应的注解完成,没有权限将抛出相应的异常
  • JSP/GSP标签:在JSP/GSP页面通过相应的标签完成
<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="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
        <!-- <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> -->
        
        <!--  
        	配置哪些页面需要受保护. 
        	以及访问这些页面需要的权限. 
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面. 
        	3). logout 登出.
        	4). roles 角色过滤器
        -->
         
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /user.jsp = authc
                /shiro/login = anon
                /shiro/logout = logout
                
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
                
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
       
    </bean>

如上xml中的配置可以看到。roles是一个角色过滤器,当角色是user的时候可以访问user.jsp,角色是admin的时候可以访问admin.jsp。

那么我们如何实现授权的操作呢?

  1. 授权需要继承 AuthorizingRealm类,并实现其doGetAuthorizationInfo方法
  2. AuthorizingRealm类继承自AuthenticatingRealm,但没有实现AuthenticatingRealm中的dogetAuthenticationInfo方法
  3. 认证和授权只需要继承AuthorizingRealm就可以,同时实现他的两个抽象方法

如果是多realm授权用的是ModularRealmAuthorizer类的方法(不需要继承他,还是继承AuthorizingRealm类即可),只要有一个授权通过就会return true;
并且注意一点,多Realm的获取用户信息的顺序是与xml中配置的顺序一致的,因为我们在java中存储的时候用的是LinkedHashMap来存储的。

在这里插入图片描述

授权代码:

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[FirstRealm] doGetAuthenticationInfo");
		
		//1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
		//2. 从 UsernamePasswordToken 中来获取 username
		String username = upToken.getUsername();
		
		//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
		System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
		
		//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
		if("unknown".equals(username)){
			throw new UnknownAccountException("用户不存在!");
		}
		
		//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
		if("monster".equals(username)){
			throw new LockedAccountException("用户被锁定");
		}
		
		//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
		//以下信息是从数据库中获取的.
		//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
		Object principal = username;
		//2). credentials: 密码. 
		Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
		if("admin".equals(username)){
			credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)){
			credentials = "098d2c478e9c11555ce2823231e02ec1";
		}
		
		//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
		String realmName = getName();
		//4). 盐值. 
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}

	public static void main(String[] args) {
		String hashAlgorithmName = "MD5";
		Object credentials = "123456";
		Object salt = ByteSource.Util.bytes("user");;
		int hashIterations = 1024;
		
		Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
		System.out.println(result);
	}

	/**
	 * 授权的方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		// 1. 从PrincipalCollection中获取登录用户的信息
		// 注意:这里获取的用户信息顺序与配置文件中配置的多Realm的顺序是一致的,用的是LinkedHashMap来存储的
		Object principal = principals.getPrimaryPrincipal();
		
		// 2. 利用登录的用户的信息来为当前用户的角色赋值权限(可能需要查询数据库,这里我们就不必要查询,测试直接赋值权限)
		// 如果是admin用户赋值admin和user权限,如果是user用户赋值user权限
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)) {
			roles.add("admin");
		}
		
		// 3. 创建SimpleAuthorizationInfo,并设置其roles属性
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
		return info;
	}

}
shiro标签
  • Shiro提供了JSTL标签用于在JSP页面上进行权限控制,如根据登录用户显示相应的页面按钮。
  • guest标签:用户没有身份验证时显示相应信息,即游客访问信息:
<shiro:guest>
	欢迎游客访问,< a  href="login.jsp">登录< /a>
</shiro:guest>
  • user标签:用户已经经过认证/记住我登录后显示相应的信息。
<shiro:user>
	欢迎【<shiro:principal>】登录,<a href="logout">退出</a>
</shiro:user>

还有很多的标签这里不做详细的介绍,因为现在这种方式已经不常用了,基本都是前后端分离,没有用jsp的了

权限注解
  • @RequiresAuthentication
    当前Subject已经通过login进行身份验证;即Subject.isAuthenticated()返回true
  • @RequiresUser
    表示当前的Subject已经身份验证或者通过记住我登录的,和上面的注解的区别就是多了通过记住我登录的。
  • @RequiresGuest
    表示当前的Subject没有身份验证或者通过记住我登陆过的,即是游客身份。
  • @RequiresRoles(value={“admin”, “user”}),logical=Logical.AND)
    表示当前Subject需要角色admin和user
  • @RequuresPermissions(value={“user:a”, “user.b”}, logical=Logical.OR)
    表示当前Subject需要权限user:a或user:b,user用户的a权限或者b权限

注意:

我们通常会在Service层上加上@Transactional注解,开启事务,事务的原理是AOP生成代理对象进行控制,因此我们不能讲Shiro的注解加到service层上,否则就是代理对象的代理对象 ,这明显是不合理的,因此通常我们会将Shiro的注解加到Controller层上去

我们上面讲的页面的权限配置都是将其直接在xml中配置,哪一个需要配置什么权限,哪一个需要登录,如:

<property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /shiro/login = anon
                /shiro/logout = logout
                
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
                
                # everything else requires authentication:
                /** = authc
            </value>
        </property>

这种方式通常是不合理的,我们应该将其权限配置在数据库中,然后通过数据库的访问,将其配置出来。

那么如何做呢?

我们只需要构建一个Map并且将其配置成FilterChainDefinitionMap的属性就可以了
eg. xml:

<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="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>

<!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
<bean id="filterChainDefinitionMap" 
    factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean>

<bean id="filterChainDefinitionMapBuilder"
    class="com.atguigu.shiro.factory.FilterChainDefinitionMapBuilder"></bean>

public class FilterChainDefinitionMapBuilder {
	
	public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){
		LinkedHashMap<String, String> map = new LinkedHashMap<>();
		map.put("/login.jsp", "anon");
		map.put("/shiro/login", "anon");
		map.put("/shiro/logout", "logout");
		map.put("/**", "authc");
		return map;
	}
	
}
Shiro的会话管理
  • Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器的Tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关集群,失效/过期支持,对web的透明支持,SSO单点登录的支持等特性
  • 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    因为我们在Service层中无法使用session,所以有了Shiro的session,这里的session和HttpSession是一个,当我们在HttpSession中赋值的时候,我们可以从shiro的session中取值。这样我们就可以从service层中获得session
认证和记住我
  • Shiro提供了记住我的功能,比如访问一些网站的时候,关闭了浏览器,下次打开的时候还是能记住你是谁,下次访问时,无需再次登录即可访问,基本流程如下:
    (1)首先在登录页面中选中RememberMe然后登录成功,如果是浏览器登录,一般会把RememberMe的Cookie写到客户端并保存下来;
    (2)关闭浏览器再重新打开,发现浏览器还是记住我的
    (3)访问一般的网页服务器端还是知道你是谁,而且能正常访问
    (4)但是一般安全性的页面还是需要进行身份验证的。

注意:认证和记住我是互斥的,二者只能选其一,在登录的时候,你要么是认证过的,要么是记住我通过的,所以要么subject.isAuthenticated()==true,要么subject.isRememberer()==true

如何实现呢?

public class FilterChainDefinitionMapBuilder {
	
	public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){
		LinkedHashMap<String, String> map = new LinkedHashMap<>();
		map.put("/login.jsp", "anon");
		map.put("/shiro/login", "anon");
		map.put("/shiro/logout", "logout");
		// user表示账户通过记住我或者认证通过的都可以访问该页面
		map.put("/list.jsp", "user");
		map.put("/**", "authc");
		return map;
	}
	
}

上面是配置了那个页面可以通过记住我登陆
设置记住我的方法就是调用

  • setRememberMe(true)
    表示的是记住我

我们也可以设置其配置文件设置最大记住我的时间

   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    			<ref bean="secondRealm"/>
    		</list>
        </property>
        <property name="rememberMeManager.cookie.maxAge" value="10"></property>
    </bean>

上面的设置表示10s后过期

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/88983902