超简单的Apache Shiro快速入门

文章目录

1、前言

1.1 简介

  Apache Shiro 是 Java 的一个安全(权限)框架。可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等功能。
  官方网站:http://shiro.apache.org/

1.2 功能

在这里插入图片描述
  Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
  Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  Web Support:Web 支持,可以非常容易的集成到Web 环境;
  Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  Testing:提供测试支持;
  Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

1.3 架构

1.3.1 外部视角

  从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作:
在这里插入图片描述
  Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者;
  SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色;
  Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource。

1.3.2 内部视角

在这里插入图片描述
  Subject:任何可以与应用交互的“用户”;
  SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。
  Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm;
  SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web环境,也可以用在如普通的 JavaSE 环境;
  CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;
  Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

2、HelloWorld

2.1 导入依赖

<dependencies>
	<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-all</artifactId>
		<version>1.3.2</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/log4j/log4j -->
	<dependency>
		<groupId>log4j</groupId>
		<artifactId>log4j</artifactId>
		<version>1.2.16</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>1.6.1</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
		<version>1.6.1</version>
	</dependency>

</dependencies>

2.2 配置文件

  shiro.ini:

# -----------------------------------------------------------
# 用户及其分配的角色
# -----------------------------------------------------------
[users]
# 用户名“root”,密码为“secret”,角色为“admin”
root = secret, admin

# 用户名“guest”,密码为“guest”,角色为“guest”
guest = guest, guest

# 用户名“presidentskroob”,密码为“12345”,角色为“president”
presidentskroob = 12345, president

# 用户名“darkhelmet”,密码为“ludicrousspeed”,角色为“darklord”和”schwartz“
darkhelmet = ludicrousspeed, darklord, schwartz

# 用户名“lonestarr”,密码为“vespa”,角色为“goodguy”和”schwartz“
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------
# 具有指定权限的角色
# -----------------------------------------------------------
[roles]
# “admin”角色拥有所有权限,由通配符“*”表示
admin = *

# “schwartz”角色可以用任何lightsaber做任何事情(*):
schwartz = lightsaber:*
# “goodguy”角色允许“delete”(操作)用户(输入)
# 车牌“张三”(实例指定id)
goodguy = user:delete:zhangsan

2.3 测试代码

package pers.klb.shiro.quickstart;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // 创建具有已配置领域、用户、角色和权限的Shiro
        // SecurityManager的最简单方法是使用简单的INI配置。
        // 我们将使用一个工厂,它可以读取一个.ini文件并返回一个SecurityManager实例:

        // 使用类路径根目录下的 shiro.ini 文件
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // 对于这个简单的示例quickstart,可以将SecurityManager作为一个JVM单例进行访问。
        // 大多数应用程序不会这样做,而是依赖于它们的容器配置或web.xml。
        // 这超出了这个简单的快速入门的范围,所以我们只做最基本的,这样你就可以继续对事物有一个感觉。
        SecurityUtils.setSecurityManager(securityManager);

        // 现在,简单的Shiro环境已经设置好了,让我们看看您可以做些什么:

        // 获取当前执行的用户(也就是subject):
        // 调用 SecurityUtils.getSubject()获取当前的 Subject;
        Subject currentUser = SecurityUtils.getSubject();

        // 使用Session做一些事情(不需要web或EJB容器!!)
        // 测试使用 Session
        // Subject对象的getSession()方法获取 Session:
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("---> 获取到了正确的值! [" + value + "]");
        }

        // 让我们登录当前用户,这样我们就可以检查角色和权限:
        // 测试当前的用户是否已经被认证. 即是否已经登录.
        // 调动 Subject 的 isAuthenticated()
        if (!currentUser.isAuthenticated()) {
            // 把用户名和密码封装为 UsernamePasswordToken 对象
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            // rememberme
            token.setRememberMe(true);
            try {
                // 执行登录.
                currentUser.login(token);
            }
            // 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常.
            catch (UnknownAccountException uae) {
                log.info("----> 没有用户名为 " + token.getPrincipal() + " 的用户 ");
                return;
            }
            // 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
            catch (IncorrectCredentialsException ice) {
                log.info("----> 用户为 " + token.getPrincipal() + " 的密码不正确!");
                return;
            }
            // 用户被锁定的异常 LockedAccountException
            catch (LockedAccountException lae) {
                log.info("用户为 " + token.getPrincipal() + " 的账户已锁定.  " + "请与管理员联系解锁.");
            }
            // ... 在这里捕获更多的异常(可能是特定于您的应用程序的自定义异常?
            // 所有认证时异常的父类.
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //打印它们的标识主体(在本例中为用户名):
        log.info("----> 用户 [" + currentUser.getPrincipal() + "] 登陆成功!");

        //test a role:
        // 调用 Subject 的 hasRole 方法测试是否有某一个角色.
        if (currentUser.hasRole("schwartz")) {
            log.info("----> May the Schwartz be with you!");
        } else {
            log.info("----> Hello, mere mortal.");
            return;
        }

        //test a typed permission (not instance-level)
        // 调用 Subject 的 isPermitted() 方法测试用户是否具备某一个行为
        if (currentUser.isPermitted("lightsaber:weild")) {
            log.info("----> You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        // 测试用户是否具备某一个行为.
        if (currentUser.isPermitted("user:delete:zhangsan")) {
            log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        // 执行登出. 调用 Subject 的 Logout() 方法.
        System.out.println("---->" + currentUser.isAuthenticated());

        currentUser.logout();

        System.out.println("---->" + currentUser.isAuthenticated());

        System.exit(0);
    }
}

3、Shiro与web的集成

3.1 集成原理

   Shiro 提供了与 Web 集成的支持,其通过一个ShiroFilter 入口来拦截需要安全控制的URL,然后进行相应的控制。
  JavaWeb项目的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">
	
	<!-- 加载 spring 的配置文件 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- 配置监听器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- springMVC 的前端控制器 -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- Shiro Filter的实现类以bean的形式定义在spring配置文件中 -->
	<!-- 
	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>

  ShiroFilter 类似于如 Strut2/SpringMVC 这种web 框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini 配置文件),然后判断URL是否需要登录/权限等工作。

3.2 Shiro的工作原理

在这里插入图片描述

3.3 ShiroFilter

  DelegatingFilterProxy 作用是自动到 Spring 容器查找名字为 shiroFilter(filter-name)的 bean 并把所有 Filter的操作委托给它。
  在web.xml配置文件中:

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

3.4 spring配置文件

<?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 核心组件 - 不是 spring 必须的
        ========================================================= -->
    <!-- Shiro的主要业务层对象,用于支持web的应用程序
         (当没有web环境时,使用DefaultSecurityManager)-->
    <!--  
    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"/>
    		</list>
        </property> 
        <property name="rememberMeManager.cookie.maxAge" value="10"></property>
    </bean>

    <!-- 让我们使用一些企业缓存支持来提高性能。你可以用任何你喜欢的企业缓存框架实现来代替它(Terracotta+Ehcache, Coherence, GigaSpaces,等等) -->
    <!--  
    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>
    
    <!--配置authenticator,策略为AtLeastOneSuccessfulStrategy-->
    <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    	<property name="authenticationStrategy">
    		<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
    	</property>
    </bean>

    <!-- SecurityManager用于访问安全数据(用户、角色等)。还可以使用许多其他域实现(PropertiesRealm,  LdapRealm, etc) -->
    <!-- 
    	3. 配置 Realm 
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
    -->
    <bean id="jdbcRealm" class="pers.klb.shiro.shirospring.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>

   
   <!-- 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
                /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="pers.klb.shiro.shirospring.factory.FilterChainDefinitionMapBuilder"></bean>
    
    <bean id="shiroService"
    	class="pers.klb.shiro.shirospring.services.ShiroService"></bean>

</beans>

  部分细节: [urls] 部分的配置,其格式是: “url=拦截器[参数],拦截器[参数]”; 如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会执行其配置的拦截器。
  anon(anonymous) 拦截器表示匿名访问(即不需要登录即可访问)
  authc (authentication)拦截器表示需要身份认证通过后才能访问。

3.5 shiro中默认的过滤器

在这里插入图片描述

3.6 URL 匹配模式

  url 模式使用 Ant 风格模式,Ant 路径通配符支持 ?***,注意通配符匹配不包括目录分隔符“/”:
  ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin/admin/
  *:匹配零个或多个字符串,如 /admin* 将匹配 /admin/admin123,但不匹配 /admin/1
   **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a/admin/a/b

3.7 URL 匹配顺序

  URL 权限采取第一次匹配优先的 方式,即从头开始使用第一个匹配的 url 模式对应的拦截器链。
   如:
  – /bb/**=filter1
  – /bb/aa=filter2
  – /**=filter3
  如果请求的url是“/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。

4、认证功能

4.1 介绍

  从外部视角看Shiro的架构:
在这里插入图片描述
  认证指的的身份验证,一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
  在 shiro 中,用户需要提供 principals (身份)和credentials(证明)给 shiro,从而应用能验证用户身份:
  principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个
Primary principals,一般是用户名/邮箱/手机号。
  credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
  最常见的 principals 和 credentials 组合就是用户名/密码了。

4.2 基本用法

4.2.1 流程

  1、收集用户身份/凭证,即如用户名/密码;

   2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功;
  3、创建自定义的 Realm 类,继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法。

4.2.2 用法示例

  收集身份信息并调用Subject.login 进行登录:

@RequestMapping("/login")
public String login(@RequestParam("username") String username,
					@RequestParam("password") String password){
	Subject currentUser = SecurityUtils.getSubject();

	if (!currentUser.isAuthenticated()) {
		// 把用户名和密码封装为 UsernamePasswordToken 对象
		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";
}

  继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法:

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
	// 根据 token 获取待验证的身份信息
	// 返回 AuthenticationInfo
}

  spring配置文件中配置realm:

<bean id="jdbcRealm" class="pers.klb.shiro.shirospring.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>

  调用Subject.login方法时,会进入配置的realm中,执行认证逻辑。

4.2.3 AuthenticationException

  如果身份验证失败请捕获 AuthenticationException 或其子类,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
  常见的子类如下:
在这里插入图片描述

4.4 Shiro内部认证逻辑

4.4.1 流程

  完整流程图为:
在这里插入图片描述
  1、首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
  2、SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
  3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证;
  5、Authenticator 会把相应的 token(携带代验证的身份信息) 传入 Realm,从 Realm 获取身份验证信息,如果没有返回或者抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

4.4.2 Authenticator

  Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验
证核心的入口点:如果验证成功,将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 异常。
  SecurityManager 接口继承了 Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定。

4.4.3 AuthenticationStrategy

  AuthenticationStrategy 接口的默认实现:
在这里插入图片描述
  1、FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
  2、AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;
  3、AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
  ModularRealmAuthenticator 默认是AtLeastOneSuccessfulStrategy策略

4.4.4 Realm

  org.apache.shiro.realm.Realm是Shiro的一个接口,Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作
  该接口源码如下:

package org.apache.shiro.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;

public interface Realm {
	// 返回 Realm 唯一的名字
    String getName();

	// 判断此 Realm 是否支持此 Token
    boolean supports(AuthenticationToken var1);

	// 根据 Token 获取待认证的信息
    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}

  它的实现类如下:
在这里插入图片描述
  其中,org.apache.shiro.realm.AuthenticatingRealm继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。我们在自定义Realm时继承它即可,无需实现原始Realm

4.5 源码解析(选看)

4.5.1 自定义Realm

  自定义的Realm:

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;
	}
} 

  spring配置文件:

<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"/>
		</list>
	</property>
</bean>


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


<bean id="jdbcRealm" 
	class="pers.klb.shiro.shirospring.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>

  其中,HashedCredentialsMatcher对象可以设置两个属性:hashAlgorithmNamehashIterations,分别表示密码加密的算法和迭代次数。

4.5.2 开始解析

  根据上面小结的介绍流程,来看Shiro具体是如何走这个流程的,我们全程跟踪token对象,该对象保存了待验证的身份信息。
  就以Controller为入口打个断点:
在这里插入图片描述
  前端过来username和password后,进入Controller,首先调用 Subject.login(token) 进行登录,触发断点:
在这里插入图片描述
  我们传入的token对象,里面保存了待验证的用户身份信息,进入login方法:
在这里插入图片描述
  可以看到,会通过WebDelegatingSubject自动委托给DefaultWebSecurityManager,这个SecurityManager 负责真正的身份验证逻辑,继续看securityManagerlogin方法:
在这里插入图片描述
  SecurityManager会委托给Authenticator 进行身份验证,并返回一个AuthenticationInfo对象,进入这个authenticate方法:
在这里插入图片描述
  继续点进去:
在这里插入图片描述
  继续点进去:
在这里插入图片描述
  Authenticator先从容器中获取所有的Realm,可以看到当前只有一个Realm,就是我们自定义的ShiroRealm
  然后,Authenticator 会把相应的 token 传入 Realm,也就是doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken)
  进入这个doSingleRealmAuthentication
在这里插入图片描述
  调用了realm.getAuthenticationInfo(token),这个realm就是我们自定义的ShiroRealmtoken携带着待认证的用户信息,点进去:
在这里插入图片描述
  这里调用了this.doGetAuthenticationInfo,这个方法就是我们所自定义的,继续在这个函数里面走几步:
在这里插入图片描述
  这个principal变量是从token对象获取的,而这个credentials在实际开发中应该从数据库获取,这一串长字符串其实是“123456”的MD5值。
  继续往下走:
在这里插入图片描述
  我们自定义的Realm创建了SimpleAuthenticationInfo对象,传入构造器的参数有principalcredentialscredentialsSaltrealmName,这四个对象的取值分别为:
  principal = "admin";
  credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
  credentialsSalt = "YWRtaW4=";
  realmName = "pers.klb.shiro.shirospring.realms.ShiroRealm_0"
  至此,我们自定义的Realm已经工作完毕,就是给框架返回一个SimpleAuthenticationInfo对象:
在这里插入图片描述
  那么框架拿到这个对象接着做了什么事情呢?我们继续往下走:
在这里插入图片描述
  调用了this.cacheAuthenticationInfoIfPossible(token, info);方法,token就是一开始我们的追踪对象,现在多了一个info,这个info就是我们自定义的Realm返回的,里面包含了从数据库提取出来的用户密码等信息,进入这个方法看看:
在这里插入图片描述
  纯粹输出一些日志信息,跳出这个方法:
在这里插入图片描述
  这个方法貌似是对比账号密码,我们点进去看看:
在这里插入图片描述
  这里框架创建了一个CredentialsMatcher,看名字叫做凭证匹配器,输入的正是tokeninfo,一个是待认证的信息,一个是数据库的信息。我们点进去这个方法:
在这里插入图片描述
  因为用户传进来的“123456”,而我们开启了MD5加密模式,因此,这里首先把“123456”经过MD5加密,然后把机密后的字符串跟info里面从数据库提取出来的密码比较是否一致。(分析到这一步其实已经很明确整个过程了)
  继续往下走:
在这里插入图片描述
  凭证匹配器无论是否匹配成功,都返回info给调用者,我们继续往下看:
在这里插入图片描述
  可以看到,如果我们没有自定义Realm来处理token,也就不会有info,而这里我们是有info的,它还是把info往上返回给上一级调用者:
在这里插入图片描述
  继续回调:
在这里插入图片描述
  这些全是开始走过的,现在只是拿到info后不断回调而已:
在这里插入图片描述
  又调用了一个方法this.notifySuccess(token, info),这个this指的是ModularRealmAuthenticator,点进去看看:
在这里插入图片描述
  var3的size为0,继续往下走,继续回调:
在这里插入图片描述
  根据tokeninfosubject创建一个Subject对象,
在这里插入图片描述
  createSubject是根据tokeninfosubject创建一个新的Subject,并设置authenticated属性为true。
  接着调用onSuccessfulLogin方法:
在这里插入图片描述
  继续点进去rememberMeSuccessfulLogin方法:
在这里插入图片描述
  这个rmm.onSuccessfulLogin(subject, token, info)里面的subject是上面新创建的loggedIn,返回回去的loggendIn为:
在这里插入图片描述
  往下走,看loggendIn返回给了谁:
在这里插入图片描述
  往下走就是对this的赋值,而this就是和loggendIn相同类型的WebDelegatingSubject
  至此,整个login逻辑执行完毕。如果subject.login(token)执行过程没有任何异常抛出,就验证通过了。

4.5.3 小结

  以token为追踪对象,我们经历了SecurityManagerAuthenticatorRealm,完全符合官方提供的流程图:
在这里插入图片描述

5、授权功能

5.1 相关概念

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

5.1.1 基本概念

  主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
  资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
  角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

5.1.2 Permissions

  语法:资源标识符:操作:对象实例ID,即对哪个资源的哪个实例可以进行什么操作。
  其默认支持通配符权限字符串:
  1、: 表示资源/操作/实例的分割;
  2、, 表示操作的分割;
  3、*表示任意资源/操作/实例
  多层次管理:
  例如:user:queryuser:edit
  冒号 是一个特殊字符,它用来分隔权限字符串的下一 部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。
  多个值: 每个 部件能够保护多个值。因此,除了授予用户 user:queryuser:edit 权限外,也可以简单地授予他们一个:user:query,edit
  还可以用 * 代替所有的值,如:user:* ,也可以写:*:query表示某个用户在所有的领域都有 query 的权限。

5.1.3 Shiro 的 Permissions

  实例级访问控制:
   这种情况通常会使用三个部件: 域、操作、被付诸实施的实例。如:user:edit:manager
  也 可以使用通配符来定义,如:user:edit:*user:*:*user:*:manager
  部分 省略 通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit :*user 等价于 user:*:*
  注意: 通配符只能 从字符串的结尾处省略部件,也就是说 user:edit 并不等价于 user:*:edit
  
  
  
  
  

5.2 授权方式

5.2.1 编程式

  通过写if/else 授权代码块完成。
  继承 AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法:

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		//1. 从 PrincipalCollection 中来获取登录用户的信息
		Object principal = principals.getPrimaryPrincipal();

		//2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}

		//3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);

		//4. 返回 SimpleAuthorizationInfo 对象.
		return info;
	}
}

5.2.2 注解式

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

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);
	}

}

5.2.3 JSP/GSP 标签

  在JSP/GSP 页面通过相应的标签完成。
  比如,在JSP中,导入JSP库:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  

  就可以在页面上使用了:

	<shiro:hasRole name="admin">
	<br><br>
	<a href="admin.jsp">Admin Page</a>
	</shiro:hasRole>
	
	<shiro:hasRole name="user">
	<br><br>
	<a href="user.jsp">User Page</a>
	</shiro:hasRole>

5.2 流程图

在这里插入图片描述

5.3 授权的内部流程

  和前面的认证流程相似。
  1、首先调用 Subject.hasRole或者Subject.isPermitted 接口,其会委托给SecurityManager,而 SecurityManager 接着会委托给 Authorizer
  2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver 把字符串转换成相应的 Permission 实例;
  3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
  4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted或者hasRole会返回true,否则返回false表示授权失败。

5.4 权限注解

5.4.1 @RequiresAuthentication

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

5.4.2 @RequiresUser

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

5.4.3 @RequiresGuest

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

5.4.4 @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND)

  表示当前 Subject 需要角色 adminuser

5.4.5 @RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR)

  表示当前 Subject 需要权限 user:auser:b

5.5 默认拦截器

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

public enum DefaultFilter {
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

	// ...
}

5.5.1 身份验证相关

在这里插入图片描述

5.5.2 授权相关

在这里插入图片描述

5.5.3 会话相关

在这里插入图片描述

5.6 授权开发

5.6.1 定义一个权限过滤规则的类

package pers.klb.shiro.shirospring.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;
	}
}

5.6.2 配置文件

<!--ShiroFilter-->
<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>
</bean>

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

6、会话管理

6.1 概述

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

6.2 会话相关API

  1、Subject.getSession():即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建 Session 对象会创建一个;Subject.getSession(false),如果当前没有创建 Session 则返回null。
  2、session.getId():获取当前会话的唯一标识;
  3、session.getHost():获取当前Subject的主机地址;
  4、session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间;
  5、session.getStartTimestamp() &session.getLastAccessTime()
获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定
期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每
次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。
  6、 session.touch() & session.stop()::更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用 HttpSession. invalidate()也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会话;
  7、session.setAttribute(key, val) &session.getAttribute(key) &
session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作。

6.3 会话监听器

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

package org.apache.shiro.session;

public interface SessionListener {

    void onStart(Session session);

    void onExpiration(Session session);
}

6.4 SessionDao

在这里插入图片描述
  AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等。
  CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager
   MemorySessionDAO 直接在内存中进行会话维护。
  EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话。
  配置示例:
在这里插入图片描述
在这里插入图片描述
  使用方法:
在这里插入图片描述
在这里插入图片描述

6.5 SerializableUtils

在这里插入图片描述

6.6 会话验证

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

7、缓存

7.1 CacheManagerAware

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

7.2 Realm 缓存

  Shiro 提供了 CachingRealm,其实现了CacheManagerAware 接口,提供了缓存的一些基础实现;
  AuthenticatingRealmAuthorizingRealm 也分别提供了对AuthenticationInfoAuthorizationInfo 信息的缓存。

7.3 Session 缓存

   如 SecurityManager 实现了 SessionSecurityManager,其会判断 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把CacheManager 设置给它。
  SessionManager 也会判断相应的 SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它。
   设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库。

8、RememberMe

8.1 概述

  Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问,基本流程如下:
  1、首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并保存下来;
  2、关闭浏览器再重新打开;会发现浏览器还是记住你的;
  3、访问一般的网页服务器端还是知道你是谁,且能正常访问;
  4、但是比如我们访问淘宝时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。

8.2 认证和记住我

  subject.isAuthenticated() 表示用户进行了身份验证登录的,即使有 Subject.login 进行了登录;
   subject.isRemembered():表示用户是通过记住我登录的,此时可能并不是真正的你(如你的朋友使用你的电脑,或者你的cookie 被窃取)在访问的。
   两者二选一,即 subject.isAuthenticated()==true,则subject.isRemembered()==false;反之一样。
  建议:
  访问一般网页:如个人在主页之类的,我们使用user 拦截
器即可,user 拦截器只要用户登录(isRemembered() || isAuthenticated())过即可访问成功;
  访问特殊网页:如我的订单,提交订单页面,我们使用authc 拦截器即可,authc 拦截器会判断用户是否是通过Subject.login(isAuthenticated()==true)登录的,如果是才放行,否则会跳转到登录页面叫你重新登录。

8.3 身份验证相关在这里插入图片描述

8.4 自定义RememeberMe

  需要在登录之前这样创建Token:UsernamePasswordToken(用户名,密码,是否记住我),且调用UsernamePasswordTokentoken.setRememberMe(true)方法。
  配置文件:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值