Shiro(1.3.2)——集成Spring以及具体实现

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>

2.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">

    <!-- =========================================================
         Shiro Core Components - Not Spring Specific
         ========================================================= -->
    <!-- Shiro's main business-tier object for web-enabled applications
         (use DefaultSecurityManager instead when there is no web environment)-->
    <!--  
    1. 配置 SecurityManager!
    -->     
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    			<ref bean="secondRealm"/>
    		</list>
        </property>
        
        <!--  
        设置 cookie的有效时长
        -->  
        <property name="rememberMeManager.cookie.maxAge" value="10"></property>
    </bean>

    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
    <!--  
    2. 配置 CacheManager. 
    2.1 需要加入 ehcache 的 jar 包及配置文件. 
    -->     
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.: -->
        <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>

    <!-- Used by the SecurityManager to access security data (users, roles, etc).
         Many other realm implementations can be used too (PropertiesRealm,
         LdapRealm, etc. -->
    <!-- 
    	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">
    			<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>

    <!-- =========================================================
         Shiro Spring-specific integration
         ========================================================= -->
    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don't have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <!--  
    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>

    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
         to wire things with more control as well utilize nice Spring things such as
         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
    <!--  
    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
                /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>

将权限抽取出来:

package com.atguigu.shiro.factory;

import java.util.LinkedHashMap;

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("/user.jsp", "authc,roles[user]");
		map.put("/admin.jsp", "authc,roles[admin]");
		map.put("/list.jsp", "user");
		
		map.put("/**", "authc");
		
		return map;
	}
	
}

3.ehcache.xml:

<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <diskStore path="java.io.tmpdir"/>
    
    <cache name="authorizationCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authenticationCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-activeSessionCache"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <!--Predefined caches.  Add your cache configuration settings here.
        If you do not have a configuration for your cache a WARNING will be issued when the
        CacheManager starts

        The following attributes are required for defaultCache:

        name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->

    <!-- Sample cache named sampleCache1
        This cache contains a maximum in memory of 10000 elements, and will expire
        an element if it is idle for more than 5 minutes and lives for more than
        10 minutes.

        If there are more than 10000 elements it will overflow to the
        disk cache, which in this configuration will go to wherever java.io.tmp is
        defined on your system. On a standard Linux system this will be /tmp"
        -->
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <!-- Sample cache named sampleCache2
        This cache contains 1000 elements. Elements will always be held in memory.
        They are not expired. -->
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> -->

    <!-- Place configuration for your caches following -->

</ehcache>

4.SpringMVC配置:

<?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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		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-4.0.xsd">
	
	<context:component-scan base-package="com.atguigu.shiro"></context:component-scan>
	
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	<mvc:annotation-driven></mvc:annotation-driven>
	<mvc:default-servlet-handler/>

</beans>

5.ShiroFilter工作原理:

6.Url:

6.1 配置:

格式:url=拦截器[参数],拦截器[参数]。

如果当前请求的url匹配[urls]部分的某个url,将会执行其配置的拦截器。

anon(anonymous)拦截器表示匿名访问(即不需要登录即可访问)。

authc(authentication)拦截器表示需要身份认证通过后才能访问。

6.2 匹配模式:

url匹配模式使用Ant风格模式。

Ant路径通配符支持?、*、**,不包括目录分隔符/。

-?:匹配一个字符,如/admin?将匹配/admin1但是不匹配/admin和/admin/;

-*:匹配零个或多个字符,如/admin将匹配/admin、/admin123但不匹配/admin/1;

-**:匹配路径中的零个或多个路径,如/admin/**将匹配/admin/a、/admin/a/b;

6.3 匹配顺序:

URL权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的URL模式对应的拦截器链。

如:路径是/bb/aa;

url配置是:/bb/**=filter1和/bb/aa=filter2;

则会使用filter1进行拦截。

7.认证过程:

1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();

2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated() ;

3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象;

1). 创建一个表单页面。

2). 把请求提交到 SpringMVC 的 Handler。

3). 获取用户名和密码。

4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法;

5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro;

1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类。

2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法。

6. 由 shiro 完成对密码的比对;

通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!

7. 把一个字符串加密为 MD5;

替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可。

8. 使用 MD5 盐值加密;

1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器。

2). 使用 ByteSource.Util.bytes() 来计算盐值。

3). 盐值需要唯一:一般使用随机字符串或 user id。

4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值。

Controller:

package com.atguigu.shiro.handlers;

import javax.servlet.http.HttpSession;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.atguigu.shiro.services.ShiroService;

@Controller
@RequestMapping("/shiro")
public class ShiroHandler {
	
	@Autowired
	private ShiroService shiroService;
	
	@RequestMapping("/testShiroAnnotation")
	public String testShiroAnnotation(HttpSession session){
		session.setAttribute("key", "value12345");
		shiroService.testMethod();
		return "redirect:/list.jsp";
	}

	@RequestMapping("/login")
	public String login(@RequestParam("username") String username, 
			@RequestParam("password") String password){
		Subject currentUser = SecurityUtils.getSubject();
		
		if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // rememberme
            token.setRememberMe(true);
            try {
            	System.out.println("1. " + token.hashCode());
                currentUser.login(token);
            } 
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            	System.out.println(ae.getMessage());
            }
        }
		
		return "redirect:/list.jsp";
	}
	
}

Realm:

package com.atguigu.shiro.realms;

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[FirstRealm] doGetAuthenticationInfo");
		
        //转UsernamePasswordToken
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
        //得到username
		String username = upToken.getUsername();
		
        //从数据库中得到username
		System.out.println("数据库中的username: " + username);
		
        //若用户不存在抛出异常
		if("unknown".equals(username)){
			throw new UnknownAccountException(“用户不存在”);
		}
		
        //根据用户信息抛出其他异常
		if("monster".equals(username)){
			throw new LockedAccountException(“用户被锁定”);
		}
		
        //根据用户情况来构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo
        //principal:认证的实体信息,可能是username也可能是数据库对应的用户实体类对象
		Object principal = username;
        //credentials:密码
		Object credentials = null; 
		if("admin".equals(username)){
			credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)){
			credentials = "098d2c478e9c11555ce2823231e02ec1";
		}
		
        //realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName = getName();
        //盐值
		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) {
        //从PrincipalCollection获取登录用户的信息
		Object principal = principals.getPrimaryPrincipal();
		
        //利用登录的用户信息来获取用户当前的角色和权限(可能需要查询数据库)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}
		
        //创建SimpleAuthorizationInfo,设置roles属性
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
		
		return info;
	}
}

8.多Realm:

secondRealm:

package com.atguigu.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;

public class SecondRealm extends AuthenticatingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[SecondReaml] doGetAuthenticationInfo");
		 
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
		String username = upToken.getUsername();
		
		System.out.println("数据库中的username: " + username);
		
		if("unknown".equals(username)){
			throw new UnknownAccountException(“用户不存在”);
		}
		
		if("monster".equals(username)){
			throw new LockedAccountException(“用户被锁定”);
		}
		
		Object principal = username;
		Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
		if("admin".equals(username)){
			credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
		}else if("user".equals(username)){
			credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
		}
		
		String realmName = getName();
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
		info = new SimpleAuthenticationInfo("secondRealmName", credentials, credentialsSalt, realmName);
		return info;
	}

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

8.1 多Realm认证策略(AuthenticationStrategy):

AuthenticationStrategy 接口的默认实现:

FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第 一个 Realm 身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略。

9.授权:

授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限 (Permission)、角色(Role)。

主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权 后才允许访问相应的资源。

资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允 许。

Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限, 即实例级别的)

角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等 都是角色,不同的角色拥有一组不同的权限。

9.1 授权方式:

Shiro 支持三种方式的授权:

– 编程式:通过写if/else 授权代码块完成

– 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相 应的异常

– JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成

9.2 默认拦截器:

Shiro 内置了很多默认的拦截器,比如身份验证、授权等 相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:

身份验证相关的过滤器:

授权相关过滤器:

其他:

1. 授权需要继承 AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法。

2. AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法。

package com.atguigu.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class TestRealm extends AuthorizingRealm {

	//用于授权 
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	//用于认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
		return null;
	}

}

9.3 权限(Permissions):

规则:资源标识符:操作:对象实例 ID 即对哪个资源的哪个 实例可以进行什么操作。其默认支持通配符权限字符串,“:”表示资源/操作/实例的分割;“,”表示操作的分割,“*” 表示任意资 源/操作/实例。

多层次管理:

– 例如:user:query、user:edit

– 冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分 是权限被操作的领域(打印机),第二部分是被执行的操作

– 多个值:每个部件能够保护多个值。因此,除了授予用户user:query和 user:edit 权限外,也可以简单地授予他们一个:user:query, edit

– 还可以用 * 号代替所有的值,如:user:* , 也可以写:*:query,表示 某个用户在所有的领域都有 query 的权限

Shiro的Permissions:

实例级访问控制

– 这种情况通常会使用三个部:域、操作、被付诸实施的实例。如:user:edit:manager

– 也可以使用通配符来定义,如:user:edit:*user:*:*user:*:manager

– 部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit :*user 等价于 user:*:*

– 注意:通配符只能从字符串的结尾处省略部件,也就 是说 user:edit 并不等价于 user:*:edit

9.4 授权流程:

1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给

SecurityManager,而 SecurityManager 接着会委托给 Authorizer;

2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过

PermissionResolver 把字符串转换成相应的 Permission 实例;

3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角

色/权限用于匹配传入的角色/权限;

4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果 有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断, 如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示 授权失败。

ModularRealmAuthorizer 进行多 Realm 匹配流程:

– 1、首先检查相应的 Realm 是否实现了实现了Authorizer;

– 2、如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole* 接口进行匹配;

– 3、如果有一个Realm匹配那么将返回 true,否则返回 false。

10.Shiro标签:

Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。

guest 标签:用户没有身份验证时显示相应信息,即游客访问信息:

user 标签:用户已经经过认证/记住我登录后显示相应的信息。

authenticated 标签:用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的

notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。

pincipal 标签:显示用户身份信息,默认调用Subject.getPrincipal() 获取,即 Primary Principal。

hasRole 标签:如果当前 Subject 有角色将显示 body 体内容:

hasAnyRoles 标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容。

lacksRole:如果当前 Subject 没有角色将显示 body 体内容

hasPermission:如果当前 Subject 有权限将显示 body 体内容

lacksPermission:如果当前Subject没有权限将显示body体内容。

11.权限注解:

@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。

可以用在Service:

package com.atguigu.shiro.services;

import java.util.Date;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.session.Session;

public class ShiroService {
	
	@RequiresRoles({"admin"})
	public void testMethod(){
		System.out.println("testMethod, time: " + new Date());
		
		Session session = SecurityUtils.getSubject().getSession();
		Object val = session.getAttribute("key");
		
		System.out.println("Service SessionVal: " + val);
	}
	
}

但是一般在项目中,Service上面会有事务注解,这个时候就不能够使用Shiro注解了,因为这个时候这个Service已经是一个代理对象了,如果在进行注解就会是一个代理的代理,就会出错。这个时候就需要将注解用在Web层上。

12.会话(Session):

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容 器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境 都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明 支持、SSO 单点登录的支持等特性。

12.1 相关API:

Subject.getSession():即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建 Session 对象会创建 一个;Subject.getSession(false),如果当前没有创建 Session 则返回null

session.getId():获取当前会话的唯一标识

session.getHost():获取当前Subject的主机地址

session.getTimeout() & session.setTimeout(毫秒):获取/设置当 前Session的过期时间

session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每 次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。

session.touch() & session.stop():更新会话最后访问时 间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用 HttpSession. invalidate()也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会 话

session.setAttribute(key, val) & session.getAttribute(key) & session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作

12.2 会话监听器:

会话监听器用于监听会话创建、过期及停止事件

12.3 SessionDao:

AbstractSessionDAO 提供了 SessionDAO 的基础实现, 如生成会话ID等

CachingSessionDAO 提供了对开发者透明的会话缓存的 功能,需要设置相应的 CacheManager

MemorySessionDAO 直接在内存中进行会话维护

EnterpriseCacheSessionDAO 提供了缓存功能的会话维 护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话。

配置SessionDao:

配置缓存:

创建Session数据表:

create table sessions (
id varchar(200),
session varchar(2000),
constraint pk_sessions primary key(id)
) charset=utf8 ENGINE=InnoDB;

实现:

package com.atguigu.shiro.realms;

import java.io.Serializable;
import java.util.List;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

public class MySessionDao extends EnterpriseCacheSessionDAO {

	@Autowired
	private JdbcTemplate jdbcTemplate = null;

	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = generateSessionId(session);
		assignSessionId(session, sessionId);
		String sql = "insert into sessions(id, session) values(?,?)";
		jdbcTemplate.update(sql, sessionId,
				SerializableUtils.serialize(session));
		return session.getId();
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {
		String sql = "select session from sessions where id=?";
		List<String> sessionStrList = jdbcTemplate.queryForList(sql,
				String.class, sessionId);
		if (sessionStrList.size() == 0)
			return null;
		return SerializableUtils.deserialize(sessionStrList.get(0));
	}
	
	@Override
	protected void doUpdate(Session session) {
		if (session instanceof ValidatingSession
				&& !((ValidatingSession) session).isValid()) {
			return; 
		}
		String sql = "update sessions set session=? where id=?";
		jdbcTemplate.update(sql, SerializableUtils.serialize(session),
				session.getId());
	}

	@Override
	protected void doDelete(Session session) {
		String sql = "delete from sessions where id=?";
		jdbcTemplate.update(sql, session.getId());
	}
}

序列化工具类:

package com.atguigu.shiro.realms;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.Session;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableUtils {

	public static String serialize(Session session) {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(session);
			return Base64.encodeToString(bos.toByteArray());
		} catch (Exception e) {
			throw new RuntimeException("serialize session error", e);
		}
	}

	public static Session deserialize(String sessionStr) {
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(
					Base64.decode(sessionStr));
			ObjectInputStream ois = new ObjectInputStream(bis);
			return (Session) ois.readObject();
		} catch (Exception e) {
			throw new RuntimeException("deserialize session error", e);
		}
	}
	
}

12.4 会话验证:

Shiro 提供了会话验证调度器,用于定期的验证会话是否

已过期,如果过期将停止会话

出于性能考虑,一般情况下都是获取会话时来验证会话是 否过期并停止会话的;但是如在 web 环境中,如果用户不 主动退出是不知道会话是否过期的,因此需要定期的检测 会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler

Shiro 也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler

13.缓存:

13.1 CacheManagerAware接口:

Shiro 内部相应的组件(DefaultSecurityManager)会自 动检测相应的对象(如Realm)是否实现了CacheManagerAware 并自动注入相应的CacheManager。

13.2 Realm缓存:

Shiro 提供了 CachingRealm,其实现了CacheManagerAware 接口,提供了缓存的一些基础实现;

AuthenticatingRealm 及 AuthorizingRealm 也分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓存。

13.3 Session缓存:

如 SecurityManager 实现了 SessionSecurityManager, 其会判断 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把CacheManager 设置给它。

SessionManager 也会判断相应的 SessionDAO(如继承 自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它

设置了缓存的 SessionManager,查询时会先查缓存,如 果找不到才查数据库。

14.Remember Me:

Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝 等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问,基本流程如下:

1、首先在登录页面选中 RememberMe 然后登录成功;如果是 浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并 保存下来;

2、关闭浏览器再重新打开;会发现浏览器还是记住你的;

3、访问一般的网页服务器端还是知道你是谁,且能正常访问;

4、但是比如我们访问淘宝时,如果要查看我的订单或进行支付 时,此时还是需要再进行身份认证的,以确保当前用户还是你。

14.1 认证和记住我:

subject.isAuthenticated() 表示用户进行了身份验证登录的,

即使有 Subject.login 进行了登录;

• subject.isRemembered():表示用户是通过记住我登录的, 此时可能并不是真正的你(如你的朋友使用你的电脑,或者 你的cookie 被窃取)在访问的

• 两者二选一,即 subject.isAuthenticated()==true,则subject.isRemembered()==false;反之一样。

访问一般网页:如个人在主页之类的,我们使用user 拦截 器即可,user 拦截器只要用户登录

(isRemembered() || isAuthenticated())过即可访问成功;

• 访问特殊网页:如我的订单,提交订单页面,我们使用authc 拦截器即可,authc 拦截器会判断用户是否是通过Subject.login(isAuthenticated()==true)登录的,如 果是才放行,否则会跳转到登录页面叫你重新登录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值