8_springboot_shiro_jwt_多端认证鉴权_多Reaml管理

1. 目标

前面一直讨论的是只有一个Reaml的场景,Shiro是可以管理多个Realm的。那么什么场景下,我们需要定义多个Realm,以及Shiro框架是如何管理多个Realm的,他们是如何工作的。本章将会解释上面的问题,最后会配置前面章节中的 SystemAccountRealm 用来做用户名,密码认证, ApiAuthenticationRealm 用来做 api接口访问认证。

2. 多Reaml的场景

在多个 Realm 的场景中,通常是因为存在不同的安全数据源或需要不同的验证策略。以下是一些需要多个 Realm 的示例场景:

2.1 多数据源

系统中存在不同类型的用户群体,如普通用户、管理员、合作伙伴等,它们各自拥有独立的认证方式和数据源。例如,普通用户通过用户名/密码登录,管理员使用数字证书验证身份,而合作伙伴可能通过 API 密钥进行认证。

使用多个 Realm:为每种用户类型创建一个对应的 Realm,如 UsernamePasswordRealm 用于处理普通用户的登录,CertificateRealm 用于管理员的身份验证,以及 ApiKeyRealm 用于合作伙伴的API访问控制。每个 Realm 从各自的用户数据源(如数据库、API密钥存储)中获取和验证用户凭据。

2.2 多策略身份验证

系统要求用户在登录时提供不止一种身份验证信息,如除了输入密码外,还需要通过短信验证码、硬件令牌(如U2F、TOTP)或生物特征(指纹、面部识别)进行二次验证。

使用多个 Realm:可以创建一个主 Realm(如 PasswordRealm)处理基础的用户名/密码认证,再添加一个或多个辅助 Realm(如 SmsCodeRealmHardwareTokenRealm)处理第二因素的验证。Shiro会按照配置的顺序或策略依次调用各个 Realm 进行认证,只有所有 Realm 都成功验证后,用户才被认为已通过多因素认证。

通过配置多个 Realm 并使用 Shiro 的策略组合逻辑(如 AtLeastOneSuccessfulStrategyFirstSuccessfulStrategy 等),可以实现这种复杂的验证逻辑。

2.3 集成外部系统

系统需要与现有的第三方认证服务(如OAuth2、SAML、CAS等)或企业级目录服务(如Active Directory、OpenLDAP)进行集成,允许用户使用外部系统的凭证登录。

使用多个 Realm:配置一个 OAuth2RealmSamlRealmLdapRealm 与相应的外部服务对接,同时保留内部的 DatabaseRealm 或其他自定义 Realm 供本地用户使用。这样,用户可以选择使用系统内置账户或通过单点登录(SSO)使用外部系统的身份进行认证。

2.4 不同的授权需求

有的时候,在一个系统中会存在不同的授权需求,比如:

基于角色的授权:一个Realm 处理基于用户角色的授权,另一个Realm 处理基于权限的授权。

外部服务授权:一个Realm 处理内部资源的授权,另一个Realm 处理外部服务的授权,比如使用REST API。

2.5 遗留系统的整合

当集成遗留系统时,这些系统可能使用不同的身份验证机制和数据源。通过为每个遗留系统配置一个 Realm,可以在一个统一的 Shiro 配置中管理所有身份验证逻辑。比如:

新系统可能采用了新的认证授权机制(如OAuth 2.0、JWT),而旧系统仍依赖传统的用户名/密码认证。为确保平滑过渡,可以在新系统中配置两个Realm:一个用于处理新系统的OAuth/JWT认证,另一个用于兼容旧系统的数据库认证。这样,无论是新老用户都能顺利登录并获得相应的权限。

3. 多Realm工作原理

在 Shiro 中,可以配置一个或多个Realm,并指定它们的顺序。当需要进行身份验证或授权时,Shiro 将按照配置的顺序依次查询这些Realm,直到找到合适的Realm 来处理请求。

3.1 Realm配置

我们可以定义多个Realm的配置,比如:

@Configuration
@Slf4j
public class ShiroConfiguration {@Beanpublic Realm apiKeyRealm() {ApiAuthenticationRealm realm = new ApiAuthenticationRealm();realm.setCachingEnabled(true);realm.setAuthenticationCachingEnabled(true);// 认证缓存的名字,不设置也可以,默认由realm.setAuthenticationCacheName("shiro:authentication:apiKeyCache");return realm;}@Beanpublic Realm userPasswordRealm() {SystemAccountRealm realm = new SystemAccountRealm();realm.setCachingEnabled(true);realm.setAuthenticationCachingEnabled(true);// 认证缓存的名字,不设置也可以,默认由realm.setAuthenticationCacheName("shiro:authentication:userPasswordCache");return realm;}...
}

3.2 SecurityManager与Realm关联

SecurityManager 是 Shiro 的核心组件,负责协调整个安全框架的行为。配置时,将所有定义的 Realm 注册到 SecurityManager 中。在shiro-spring-boot-web-starter 的自动配置中,会将定义的多个Realm配置给 SecurityManager:

自动配置ShiroWebAutoConfiguration 源码:

@Configuration
@AutoConfigureBefore(ShiroAutoConfiguration.class)
@AutoConfigureAfter(ShiroWebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {...// 会到spring容器中查找所有的Realm,然后"注入" 到这个方法上@Bean@ConditionalOnMissingBean@Overrideprotected SessionsSecurityManager securityManager(List<Realm> realms) {return super.securityManager(realms);}...
}//ShiroWebAutoConfiguration 的父类
public class AbstractShiroConfiguration {...protected SessionsSecurityManager securityManager(List<Realm> realms) {SessionsSecurityManager securityManager = createSecurityManager();// 重要: 关键角色之一: Authenticator(认证器)securityManager.setAuthenticator(authenticator());// 重要: 关键角色之二: Authorizer(授权器)securityManager.setAuthorizer(authorizer());// 重要:关键角色之三,Realm.在SecurityManager中,用集合来保存多个RealmsecurityManager.setRealms(realms);securityManager.setSessionManager(sessionManager());securityManager.setEventBus(eventBus);if (cacheManager != null) {securityManager.setCacheManager(cacheManager);}return securityManager;}...// 认证器策略protected AuthenticationStrategy authenticationStrategy() {return new AtLeastOneSuccessfulStrategy();}// 认证器protected Authenticator authenticator() {ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();authenticator.setAuthenticationStrategy(authenticationStrategy());return authenticator;}// 授权器 protected Authorizer authorizer() {ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();if (permissionResolver != null) {authorizer.setPermissionResolver(permissionResolver);}if (rolePermissionResolver != null) {authorizer.setRolePermissionResolver(rolePermissionResolver);}return authorizer;}
}

可以看到,实际的认证器和授权器是 ModularRealmAuthenticatorModularRealmAuthorizer

3.2.1 Authenticator(认证器)

Authenticator 负责对用户提交的认证信息(如用户名、密码、API密钥等)进行验证,判断用户身份是否合法,即是否为系统所认可的用户。它的工作流程如下:

  1. 接收和解析:接收客户端提交的 AuthenticationToken(认证令牌),该令牌封装了用户提供的身份标识和认证凭据。在前面的例子中,AuthenticationToken 是在过滤器中被创建的,然后它就被交给了 SecurityManager ,SecurityManager调度认证器,同时传给他AuthenticationToken 来进行认证
  2. 身份验证:根据令牌信息,调用关联的 Realm(安全数据源)进行实际的认证操作。Realm 从数据源中查找与令牌匹配的用户记录,并验证凭据的有效性。
  3. 结果判定:根据 Realm 返回的认证结果,决定是否认证成功。如果所有关联的 Realm 中至少有一个成功验证了用户身份,通常认为认证成功(具体取决于配置的认证策略)。成功时,Authenticator 会创建或更新 Subject(当前用户主体)的身份信息,并将其绑定到当前会话中。

简而言之Authenticator 的主要任务是验证用户身份,确保只有合法用户能够访问受保护的资源或服务。

认证器策略:

AuthenticationStrategy(认证策略)则是Authenticator中的一个关键组件,用于定义如何处理多个Realm(领域)的认证结果。主要作用是在多个Realm参与认证时,决定如何合并和解释这些Realm的认证结果。当配置了多个Realm时,每个Realm都可能返回自己的认证结果,而AuthenticationStrategy则负责将这些结果整合成一个最终的认证结果。它是这样工作的:

  1. 发起认证:用户提交 AuthenticationToken(如用户名/密码对),Authenticator 开始认证流程。
  2. 遍历 RealmAuthenticator 使用配置的 AuthenticationStrategy,按照一定的顺序或策略遍历已注册的各个 Realm。对于每个 Realm,Authenticator 传递 AuthenticationToken 给 Realm,让其尝试进行认证。
  3. 收集结果:每个 Realm 完成认证后,返回一个 AuthenticationInfo(认证信息,如用户详情和凭据)或抛出异常(表示认证失败)。Authenticator 将这些结果收集起来,传递给 AuthenticationStrategy
  4. 策略评估AuthenticationStrategy 根据收集到的 Realm 认证结果,依据预设策略进行评估。常见的策略有:
    • At least one successful strategy(至少一个成功):只要有至少一个 Realm 认证成功,整个认证过程就算成功。这是最常用的策略,适用于多因素认证或多来源认证的情况,只要一个因素或来源通过即可。
    • All successful strategy(全部成功):所有 Realm 必须全部认证成功,整个认证过程才算成功。适用于需要极高安全性的场景,所有认证途径都必须验证无误。
    • First successful strategy(首个成功):一旦遇到第一个认证成功的 Realm,立即停止后续 Realm 的认证,并认定整个认证过程成功。适用于希望尽快结束认证过程,或者后续 Realm 认证成本较高的情况。
    • 决策反馈:根据策略评估的结果,AuthenticationStrategy 告知 Authenticator 认证是否成功。若成功,Authenticator 将认证成功的 AuthenticationInfo 与当前 Subject 绑定;若失败,Authenticator 可能抛出相应的异常或返回错误信息。

上面的源代码中可以看到,默认使用的是 ModularRealmAuthenticatorAtLeastOneSuccessfulStrategy

实例说明:

假设系统配置了三个 Realm:ApiAuthenticationRealm(API认证)、SystemAccountRealm(管理后台用户认证)、WxRealm(微信客户端认证)采用 At least one successful strategy

  1. 用户提交 AuthenticationToken
  2. Authenticator 首先调用 ApiAuthenticationRealm 进行认证,结果失败。
  3. Authenticator 继续调用 SystemAccountRealm,用户通过 SystemAccountRealm验证成功。
  4. Authenticator 收到成功结果,不再尝试 WxRealm,因为已经有一个 Realm 认证成功。
  5. AuthenticationStrategy 根据策略判断认证成功,因为至少有一个 Realm(SystemAccountRealm)成功。
  6. AuthenticatorSystemAccountRealm 返回的 AuthenticationInfo 与当前 Subject 绑定,用户登录成功。

源码分析:

先看看继承关系:
在这里插入图片描述
在Authenticator接口中定义了方法:

public interface Authenticator {/*** 这个方法会抛出一下几个异常:* @see ExpiredCredentialsException 凭证过期* @see IncorrectCredentialsException 凭证错误* @see ExcessiveAttemptsException 尝试次数过多* @see LockedAccountException 账号被锁定* @see ConcurrentAccessException 并发访问异常* @see UnknownAccountException 账号错误异常*/AuthenticationInfo authenticate(AuthenticationToken authenticationToken)throws AuthenticationException;
}

我们知道Subject的登录操作实际上是委托给了SecurityManager来完成的:

public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager{...public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;...info = authenticate(token);...Subject loggedIn = createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;}   ...// 这个方法实际上是定义在父类 AuthenticatingSecurityManager 中。这里为了方便理解,就写在了public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {return this.authenticator.authenticate(token);}
}

接着看 ModularRealmAuthenticator 类是如何工作的:

public class ModularRealmAuthenticator extends AbstractAuthenticator {...private Collection<Realm> realms;private AuthenticationStrategy authenticationStrategy;...// 这个方法定义在父类 AbstractAuthenticator中,这里为了看起来方便就写在这里public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException{...info = doAuthenticate(token);...}// 执行认证的逻辑protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {assertRealmsConfigured();// realm的集合Collection<Realm> realms = getRealms();if (realms.size() == 1) { // 如果是一个realm,执行doSingleRealmAuthenticationreturn doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);} else { // 如果存在多个 realm 执行 doMultiRealmAuthenticationreturn doMultiRealmAuthentication(realms, authenticationToken);}}  // 单个realm认证protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {if (!realm.supports(token)) { // 判定这个realm是否支持这个token,不支持抛出 UnsupportedTokenException 异常...}// 从 realm 中获取 AuthenticationInfo,即认证信息AuthenticationInfo info = realm.getAuthenticationInfo(token);...return info;}// 真正多个realm认证逻辑protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {// 认证策略AuthenticationStrategy strategy = getAuthenticationStrategy();// 认证之前,在实际的FirstSuccessfulStrategy中,返回的是 nullAuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);...// 遍历所有的realmfor (Realm realm : realms) {try {// 策略中如果抛出 ShortCircuitIterationException 异常则终止遍历aggregate = strategy.beforeAttempt(realm, token, aggregate);} catch (ShortCircuitIterationException shortCircuitSignal) {break;}// 判断这个realm是否支持当前的tokenif (realm.supports(token)) {...// realm 认证info = realm.getAuthenticationInfo(token);...aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);} else { //token不匹配则跳过LOGGER.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);}}// 所有的realm都遍历完成之后,在这里决策这个是否通过aggregate = strategy.afterAllAttempts(token, aggregate);return aggregate;}}

3.2.2 Authorizer(授权器)

Authorizer 负责确定已认证用户(即拥有有效身份的 Subject)是否有权限执行特定的操作或访问特定的资源。它基于预先定义的权限规则和用户角色来做出授权决策。它的主要任务:

  • 权限查询:根据 Subject 的身份信息(如用户ID、角色、权限等),查询关联的 Realm 获取用户的权限数据。这可能包括用户直接拥有的权限,以及通过角色间接继承的权限。
  • 权限检查:对 Subject 进行具体的权限检查。当应用程序需要判断用户是否有权执行某个操作(如访问某个URL、执行特定的数据库操作)时,会向 Authorizer 提交一个权限验证请求,如“用户是否具有 read:document 权限”。Authorizer 根据查询到的权限数据判断用户是否具备所需权限。
  • 角色与权限关系管理:某些 Authorizer 实现可能还支持角色与权限之间的动态关联管理,如基于 RBAC(Role-Based Access Control,基于角色的访问控制)模型进行授权。

简而言之Authorizer 的主要任务是根据用户身份及其关联的权限信息,判断用户是否有权执行特定操作,从而控制对系统资源的访问权限。

源码就不分析了,感兴趣的可以自行分析,入口点从 DefaultWebSecurityManager 继承的 checkXXX或者hasXXX方法开始。

3.3 多 Realm 与 Subject 的交互

在实际运行时,Subject 无需关心底层有多少个 Realm。它通过 SecurityManager 提供的接口进行认证、授权操作。SecurityManager 负责根据配置和策略,透明地与各个 Realm 交互,确保正确的 Realm 被用于处理特定的用户请求。

3.4 会话管理与多 Realm

Shiro 的会话管理功能同样适用于多 Realm 环境。无论用户通过哪个 Realm 登录,Shiro 都会创建一个唯一的 Session,用于跟踪用户的会话状态。后续的会话相关操作(如检查会话有效性、更新会话属性、销毁会话)都由 SecurityManager 统一管理,不受具体 Realm 数量的影响。

3.5 小结

总结来说,Apache Shiro 通过 SecurityManager 将多个 Realm 组织在一起,根据预设的策略协调它们在认证、授权过程中的行为。这种设计使得系统能够轻松应对多样化的安全需求,同时保持内部逻辑的清晰与简洁。开发者只需关注每个 Realm 的具体实现,而无需关心 Realm 间如何协同工作。

4. 案例

本案例将会配置前面章节中的 SystemAccountRealm 用来做用户名,密码认证, ApiAuthenticationRealm 用来做 api接口访问认证,让应用同时支持两种认证方式。

4.1 确定认证策略

现在思考一下用户名,密码认证的方式SystemAccountRealmApiAuthenticationRealm 采用什么策略?很显然它们之间没有关联性,使用默认的AtLeastOneSuccessfulStrategy 策略。因为 SystemAccountRealm支持的token是 UsernamePasswordToken, 而 ApiAuthenticationRealm支持的Token是ApiAuthenticationToken ,每个Realm对应的Token是不一样,也没有关联关系,所以认证的时候,只需要匹配一个即可。

4.2 AuthenticationToken的创建

前面的案例中,我们定义了两个Filter,ApiAuthenticationFilterAuthenticationFilter, 这两个Filter中都创建了自己的Token:

// api认证的filter
public class ApiAuthenticationFilter extends AuthenticatingFilter {...@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest           req                  = WebUtils.toHttp(request);String                       accessKey            = req.getHeader("X-Access-Key");String                       accessTimestamp      = req.getHeader("X-Access-Timestamp");String                       accessSign           = req.getHeader("X-Access-Sign");String                       accessAppId          = req.getHeader("X-Access-AppId");ContentCachingRequestWrapper cachedRequestWrapper = (ContentCachingRequestWrapper) request;String                       requestBody          = StreamUtils.copyToString(cachedRequestWrapper.getInputStream(), Charset.forName("UTF-8"));return new ApiAuthenticationToken(accessKey, accessTimestamp, accessSign, accessAppId, requestBody);}...
}// 用户名,密码认证的filter
public class AuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter{...// 这个方法没有在本类中定义,而是在 org.apache.shiro.web.filter.authc.AuthenticatingFilter 这个父类中定义的protected AuthenticationToken createToken(String username, String password,boolean rememberMe, String host) {return new UsernamePasswordToken(username, password, rememberMe, host);}...
}

实际应用中,一次只能创建一种类型的Token,那过滤器链的顺序就比较重要了。这里有两种思路:

  1. 提供一个公共的Filter,让所有的请求都通过这个Filter,客户端在提交数据的时候,在请求头上加上标识,公共的Filter获取到这个标识后,创建对应的Token。
  2. 每种认证方式都有一个Filter,用URL匹配模式与这个Filter关联,每个Filter内创建自己的AuthenticationToken .

第一种方式需要在客户端请求头上加标识,只有一个Filter。如果后续再添加一个认证方式,就需要修改这个公共的Filter。而第二种方式遇到这种需求只需要新创建一个Filter,配置的时候,匹配一个URL即可,不需要修改原来的类。所以这里使用第2中方式。

如果采用第二种方式,原有Filter代码不用做任何更改,只需要修改配置,就可以适配两种认证方式。

4.3 配置

以下的方法代码,都在项目的 ShiroConfiguration 这个类中

  1. SecurityManager 安全管理器 : 使用ShiroWebAutoConfiguration 这个自动配置,它配置好了 DefaultWebSecurityManager 我们无需自己配置

  2. authenticator 认证器: 使用默认,无需配置

  3. authorizer 授权器: 使用默认,无需配置

  4. realm : 需要使用我们自己的配置

        @Beanpublic Realm apiKeyRealm() {ApiAuthenticationRealm realm = new ApiAuthenticationRealm();realm.setCachingEnabled(true);realm.setAuthenticationCachingEnabled(true);// 认证缓存的名字,不设置也可以,默认由realm.setAuthenticationCacheName("shiro:authentication:apiKeyCache");return realm;}@Beanpublic Realm userPasswordRealm() {SystemAccountRealm realm = new SystemAccountRealm();realm.setCachingEnabled(true);realm.setAuthenticationCachingEnabled(true);// 认证缓存的名字,不设置也可以,默认由realm.setAuthenticationCacheName("shiro:authentication:userPasswordCache");return realm;}
    
  5. shiro CacheManager 缓存管理器: 我们要将认证和授权信息缓存到Redis中,所以要自己配置。前面代码已经写好了,直接配置.

        @Beanpublic CacheManager cacheManager(RedisTemplate redisTemplate) {RedisSerializer<String> stringSerializer = RedisSerializer.string();// 设置key的序列化器redisTemplate.setKeySerializer(stringSerializer);// 设置 Hash 结构中 key 的序列化器redisTemplate.setHashKeySerializer(stringSerializer);return new ShiroRedisCacheManager(redisTemplate);}
    
  6. sessionManager 会话管理器:因为已经禁用cookie了,前面已经改写好了,使用我们自己写的sessionManager. 同时要将session缓存到redis中,所以也要配置sessionDAO

        // 配置SessionDAO@Beanpublic SessionDAO shiroRedisSessionDAO(RedisTemplate redisTemplate, CacheManager cacheManager) {ShiroRedisSessionDAO sessionDAO = new ShiroRedisSessionDAO(redisTemplate, "shiro:session");// 活跃session缓存的名字sessionDAO.setActiveSessionsCacheName("shiro:active:session");sessionDAO.setCacheManager(cacheManager);return sessionDAO;}// sessionManager配置@Beanpublic SessionManager sessionManager(SessionFactory sessionFactory,SessionDAO sessionDAO) {//自定义的SessionManager,已经禁用了CookieAccessTokenWebSessionManager webSessionManager = new AccessTokenWebSessionManager();// 自动配置中已经配置了sessionFactory 直接注入进来webSessionManager.setSessionFactory(sessionFactory);// 使用自定义的ShiroRedisSessionDAOwebSessionManager.setSessionDAO(sessionDAO);return webSessionManager;}
    
  7. shiroFilterFactoryBean 拦截器相关配置。定义shiroFilterFactoryBean 的时候,需要为它配置:

    • securityManager

    • 自定义的Filter。它是一个Map结构,过滤器名称过滤器对象之间的映射

    • URL与过滤器名称和请求URL之间的关系。即什么样的URL对应哪一个或者哪几个过滤器。

      可以为一个URL配置多个过滤器名称,名称之间使用"," 分割,这样一个请求就可以通过一个过滤器链

      /*** 自定义filter.*  apiAuthc->ApiAuthenticationFilter*  sysAuthc ->AuthenticationFilter* @return*/private Map<String, Filter> getCustomerShiroFilter() {// API 认证过滤器ApiAuthenticationFilter apiAuthcFilter = new ApiAuthenticationFilter();// 用户名,密码认证过滤器AuthenticationFilter sysAuthcFilter = new AuthenticationFilter();//需要指定登录地址sysAuthcFilter.setLoginUrl("/sys/login");Map<String, Filter> filters = new HashMap<>();filters.put("apiAuthc", apiAuthcFilter);filters.put("sysAuthc", sysAuthcFilter);return filters;}/*** URL与filter之间的关系** @return*/private ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();chainDefinition.addPathDefinition("/api/**", "apiAuthc");chainDefinition.addPathDefinition("/sys/**", "sysAuthc");return chainDefinition;}/***  ShiroFilterFactoryBean 重要配置,为他配置过滤器URL映射关系和自定义的过滤器。**/
    @Beanprotected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();filterFactoryBean.setSecurityManager(securityManager);filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());filterFactoryBean.setFilters(getCustomerShiroFilter());return filterFactoryBean;}

4.4 改造Controller

controller使用 “/api” 和 “/sys” 进行分类:
在这里插入图片描述

4.5 测试

  • 用户名密码登录/sys/login

    请求报文:

    POST /sys/login HTTP/1.1
    Host: 127.0.0.1:8080
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Content-Type: application/x-www-form-urlencodedusername=administrator&password=admin
    

    响应报文:

    {"name": "SystemAccount(account=administrator, pwdEncrypt=0b188436fd5c434e3b8ed05cfe7c107250c1ff0ac034fad089db0f017ac3cacb, salt=55ae2b2c63ddd6d4763e0c57bda9078e)","accessToken": "70555c4b-7191-41fd-a596-71462564c8da","message": "登录成功"
    }
    

    缓存:

    在这里插入图片描述

  • 退出登录 /sys/logout

    请求报文:刚才返回的accessToken加入到X-Access-Token请求头中

    POST /sys/logout HTTP/1.1
    Host: 127.0.0.1:8080
    X-Access-Token: 70555c4b-7191-41fd-a596-71462564c8da
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    

    响应报文:

    {"name": "SystemAccount(account=administrator, pwdEncrypt=0b188436fd5c434e3b8ed05cfe7c107250c1ff0ac034fad089db0f017ac3cacb, salt=55ae2b2c63ddd6d4763e0c57bda9078e)","message": "退出登录成功"
    }
    

    退出登录后,session缓存被清理掉了:

    在这里插入图片描述

  • 访问主页 /sys

    请求报文: 刚才返回的accessToken加入到请求头中,这个X-Access-Token已经被清理掉了,所以应该是进制访问

    GET /sys HTTP/1.1
    Host: 127.0.0.1:8080
    X-Access-Token: 70555c4b-7191-41fd-a596-71462564c8da
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    

    响应报文:

    {"code": 401,"msg": "未登录或登录已过期"
    }
    
  • 登录后获取正确的 accessToken 再次请求,访问主页 /sys

    请求报文:

    GET /sys HTTP/1.1
    Host: 127.0.0.1:8080
    X-Access-Token: 806db936-61f1-4030-9f5c-fcf71bbc7c38
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    

    响应报文: 此时可以正常访问

    {"name": "SystemAccount(account=administrator, pwdEncrypt=0b188436fd5c434e3b8ed05cfe7c107250c1ff0ac034fad089db0f017ac3cacb, salt=55ae2b2c63ddd6d4763e0c57bda9078e)","sessionKeys": "[org.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEY, org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY]"
    }
    
  • API请求,访问:/api/employees 添加一个员工

    请求报文:

    POST /api/employees HTTP/1.1
    Host: 127.0.0.1:8080
    X-Access-Key: db0f017ac3cacb
    X-Access-Timestamp: 1711853625738
    X-Access-Sign: d9afbd8009dec57cbe01bde8fda51246e6163fc0800a987d2ea9ce729e556b16
    X-Access-AppId: 123456
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Content-Type: application/json
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive{"name": "张三","gender": "男"}
    

    响应报文:

    {"message": "创建员工成功","data": {"name": "张三","gender": "男"},"code": "0000"
    }
    

5. 总结

  1. Shiro支持使用多个Realm进行认证,是由 认证器来完成工作的。默认使用的认证器是 ModularRealmAuthenticator,每个认证器需要为它配置认证策略,默认使用的是AtLeastOneSuccessfulStrategy. 其工作流程为:
    1. 用户发出URL请求
    2. 被Shiro过滤器拦截
    3. 过滤器创建AuthenticationToken, 后交给 securityManager, securityManager 再将token 交给认证器,认证器根据策略遍历realm ,调用realm的认证方法,最后根据策略来判断是否认证成功。
  2. Shiro支持使用多个Realm进行授权,是有授权器来完成工作的。默认使用的授权器是 ModularRealmAuthorizer
  3. 每一个过滤器创建一个特定的token,交给对应的realm,过滤器与匹配的URL相关联,这样就可以实现多种认证方式。

代码仓库 https://github.com/kaiwill/shiro-jwt , 本节代码在 8_springboot_shiro_jwt_多端认证鉴权_多Reaml管理.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/795913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于python爬虫与数据分析系统设计

**单片机设计介绍&#xff0c;基于python爬虫与数据分析系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于Python爬虫与数据分析系统的设计是一个结合了网络数据抓取、清洗、存储和数据分析的综合项目。这样的系统通常…

jenkins+docker实现可持续自动化部署springboot项目

目录 一、前言 二、微服务带来的挑战 2.1 微服务有哪些问题 2.2 微服务给运维带来的挑战 三、可持续集成与交付概述 3.1 可持续集成与交付概念 3.1.1 持续集成 3.1.2 持续交付 3.1.3 可持续集成与交付核心理念 3.2 可持续集成优点 3.3 微服务为什么需要可持续集成 四…

Java栈和队列的实现

目录 一.栈(Stack) 1.1栈的概念 1.2栈的实现及模拟 二.队列(Queue) 2.1队列的概念 2.2队列的实现及模拟 2.3循环队列 2.4双端队列&#xff08;Deque&#xff09; 一.栈(Stack) 1.1栈的概念 栈:一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作…

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测 目录 回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基于CPO-GPR基于冠豪猪算法优化高斯…

顺序表相关习题

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

平衡二叉树,红黑树,B树和B+树的区别及其应用场景

平衡二叉树 基础数据结构左右平衡高度差大于1会自旋每个节点记录一个数据 平衡二叉树&#xff08;AVL&#xff09; AVL树全称G.M. Adelson-Velsky和E.M. Landis&#xff0c;这是两个人的人名。 平衡二叉树也叫平衡二叉搜索树&#xff08;Self-balancing binary search tree…

58 vue-cli 以及 webpack 提供的默认的插件, 配置

前言 vue-cli 这边作为驱动 webpack 的一个应用 它需要构造 webpack 所需要的上下文, 以及参数 这里 我们来关注一下 vue-cli 这边为 webpack 构造的参数 的相关处理 webpack 这边上下文的配置, 主要分为了几个部分, Entry, Output, Module, Resolve, Plugin, DevServer, O…

入门MyBatis

文章目录 入门MyBatisMyBatis快速入门创建user表添加数据创建模块导入坐标编写Mybatis核心配置文件编写SQL映射文件编码 使用idea编写sql代码链接数据库调出console控制台 Mapper代理开发定义与SQL映射文件同名的Mapper接口编码 MyBatis核心配置文件安装mybatisx插件配置文件完…

9(10)-1(2)-CSS 布局模型+CSS 浮动

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 一、CSS 布局模型1 流动模型&#xff08;标准流&#xff09; 二、CSS 浮动1 浮…

HTTP详解及代码实现

HTTP详解及代码实现 HTTP超文本传输协议 URL简述状态码常见的状态码 请求方法请求报文响应报文HTTP常见的HeaderHTTP服务器代码 HTTP HTTP的也称为超文本传输协议。解释HTTP我们可以将其分为三个部分来解释&#xff1a;超文本&#xff0c;传输&#xff0c;协议。 超文本 加粗样…

k8s存储卷 PV与PVC 理论学习

介绍 存储的管理是一个与计算实例的管理完全不同的问题。PersistentVolume 子系统为用户和管理员提供了一组 API&#xff0c;将存储如何制备的细节从其如何被使用中抽象出来。为了实现这点&#xff0c;我们引入了两个新的 API 资源&#xff1a;PersistentVolume 和 Persistent…

Java集合——Map、Set和List总结

文章目录 一、Collection二、Map、Set、List的不同三、List1、ArrayList2、LinkedList 四、Map1、HashMap2、LinkedHashMap3、TreeMap 五、Set 一、Collection Collection 的常用方法 public boolean add(E e)&#xff1a;把给定的对象添加到当前集合中 。public void clear(…

用C/C++加Easyx实现俄罗斯方块游戏(爆肝4万字,完全免费)

前言 相信大家一定玩过俄罗斯方块这款小游戏&#xff0c;简单容易上手是老少皆宜的小游戏&#xff0c;今天大家就跟着我来实现这个小游戏吧&#xff01;让自己学的C语言有用武之地。 为了让俄罗斯方块的开发更为简单些&#xff0c;图像更为丰富&#xff0c;在这里就利用了Easyx…

LOOP循环

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 循环语句和条件语句一样都可以控制程序的执行顺序&#xff0c;而循环语句是指一段程序可以重复执行&#xff0c;PL/SQL 语言主要支持 3 种类型的循环&#xff1a;LOOP 循环、…

最优乘车

题目描述 H 城是一个旅游胜地&#xff0c;每年都有成千上万的人前来观光。为方便游客&#xff0c;巴士公司在各个旅游景点及宾馆&#xff0c;饭店等地都设置了巴士站并开通了一些单程巴上线路。每条单程巴士线路从某个巴士站出发&#xff0c;依次途经若干个巴士站&#xff0c;…

42. 接雨水(Java)

目录 题目描述:输入&#xff1a;输出&#xff1a;代码实现&#xff1a; 题目描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a; height [0,1,0,2,1,0,1,3,2,1,2,1]输出&#xff1…

JavaEE 初阶篇-生产者与消费者模型(线程通信)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 生产者与消费者模型概述 2.0 在生产者与消费者模型中涉及的关键概念 2.1 缓冲区 2.2 生产者 2.3 消费者 2.4 同步机制 2.5 线程间通信 3.0 实现生产者与消费者模…

【ArcGIS微课1000例】0107:ArcGIS加载在线历史影像服务WMTS

文章目录 一、WMTS历史影像介绍二、ArcGIS加载WMTS服务三、Globalmapper加载WMTS服务一、WMTS历史影像介绍 通过访问历史影响WMTS服务,可以将全球范围内历史影像加载进来,如下所示: WMTS服务: https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/WM…

二叉树中所有距离为k的节点

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 从目标节点的左孩子&#xff0c;右孩子&#xff0c;父亲节点出发去找&#xff0c;左孩子 右孩子 做法简单 &#xff0c; 主要是父亲节点 &#xff0c;因此我们需要知道每个节点的父亲节点&am…

【学习笔记】Elsevier的Latex模板文件(附网址)

注&#xff1a;这是一篇没有技术含量的水文&#xff0c;主要是看有人下载下来&#xff0c;居然当成资源需要积分才能下载。我觉得不行&#xff0c;故提供原始下载地址供查阅使用。 链接: 上述图片所示网址&#xff1a;链接直达