Shiro-多Realm验证

1.多Realm验证

  存在这样一种场景,同一个密码可能在MqSQL中存储,也可能在Oracle中存储,有可能MqSQL中使用的是MD5加密算法,而Oracle使用SHA1加密算法。这就需要有多个Realm以及认证策略的问题。

  

  通过查看源码可以看到 ModularRealmAuthenticator.class 中的 doAuthenticate

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

   即可以看到如果有一个Realm 使用的是 doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);

  如果有多个Realm 使用的是doMultiRealmAuthentication(realms, authenticationToken);

  所以我们可以配置多个Realm 给到 ModularRealmAuthenticator 这个bean,将ModularRealmAuthenticator 单独配置为一个bean,将这个bean 配置给SecurityManager

  1).添加第二个Realm SecondRealm.java

package com.java.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("[SecondRealm] doGetAuthenticationInfo " + token);

        // 1. 把AuthenticationToken 转换为UsernamePasswordToken
        UsernamePasswordToken up = (UsernamePasswordToken) token;
        // 2. 从UsernamePasswordToken 中来获取username
        String username = up.getUsername();
        // 3. 调用数据库的方法,从数据库中查询username对应的用户记录
        System.out.println("从数据库中获取userName :" + username + " 所对应的用户信息.");
        // 4. 若用户不存在,则可以抛出 UnknownAccoountException 异常
        if ("unknown".equals(username)) {
            throw new UnknownAccountException("用户不存在");
        }
        // 5. 根据用户信息的情况,决定是否需要抛出其他的AuthencationException 异常 假设用户被锁定
        if ("monster".equals(username)) {
            throw new LockedAccountException("用户被锁定");
        }
        // 6. 根据用户的情况,来构建AuthenticationInfo 对象并返回,通常使用的是
        // SimpleAuthenticationInfo
        // 以下信息是从数据库获取的.

        Object principal = username; // principal 认证的实体信息.
                                        // 可以是username,也可以是数据表对应的用户的实体类对象
//        String credentials = "fc1709d0a95a6be30bc5926fdb7f22f4"; // credentials:密码
        String credentials = null; // credentials:密码
        String realmName = getName();
        AuthenticationInfo info = null;/*new SimpleAuthenticationInfo(principal, credentials, realmName);*/

        if("admin".equals(username)){
            credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
        }else if("user".equals(username)){
            credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
        }

        ByteSource credentialsSalt = ByteSource.Util.bytes(username);//这里的参数要给个唯一的;

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

        return info;
    }

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

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

    <!-- 数据源配置,暂时不考虑数据源,做一些静态的数据 -->
    <!-- Sample RDBMS data source that would exist in any application - not Shiro related. -->
    <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:shiro-spring"/>
        <property name="username" value="sa"/>
    </bean> -->
    <!-- Populates the sample database with sample users and roles. -->
    <!-- <bean id="bootstrapDataPopulator" class="org.apache.shiro.samples.spring.BootstrapDataPopulator">
        <property name="dataSource" ref="dataSource"/>
    </bean> -->

    <!-- Simulated business-tier "Manager", not Shiro related, just an example -->
    <!-- <bean id="sampleManager" class="org.apache.shiro.samples.spring.DefaultSampleManager"/> -->

    <!-- =========================================================
         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"/>
        <!-- Single realm app.  If you have multiple realms, use the ‘realms‘ property instead. -->
        <!-- 配置session的管理方式 -->
        <!-- <property name="sessionMode" value="native"/> -->
        <!-- <property name="realm" ref="jdbcRealm"/> -->
        <!-- 配置多个Realm -->
        <property name="authenticator" ref="authenticator"></property>
    </bean>

    <!-- 配置多个Realm -->
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </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>

    <!-- 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 自己写一个Realm,需要实现Realm接口
     -->
    <bean id="jdbcRealm" class="com.java.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.java.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>

    <!-- Secure Spring remoting:  Ensure any Spring Remoting method invocations can be associated
         with a Subject for security checks. -->
    <!-- 远程调用,暂时不需要 -->
    <!-- <bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
        <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"/><!-- 没有权限的页面 -->
        <!-- The ‘filters‘ property is not necessary since any declared javax.servlet.Filter bean
             defined will be automatically acquired and available via its beanName in chain
             definitions, but you can perform overrides or parent/child consolidated configuration
             here if you like: -->
        <!-- <property name="filters">
            <util:map>
                <entry key="aName" value-ref="someFilterPojo"/>
            </util:map>
        </property> -->
        <!--
            配置哪些页面需要受保护
            以及访问这些页面需要的权限
            1). anon(anonymous) 可以被匿名访问,即不需要登录就可以访问
            2). authc(authentication) 必须认证之后,即登录后才可以访问
            3). URL 权限采取第一次匹配优先的方式,即从开头使用第一个匹配的url模式对应的拦截器链。
             4). logout 登出
         -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp= anon
                /shiro/login= anon
                /shiro/logout = logout
                # everything else requires authentication:

                /** = authc
            </value>
        </property>
    </bean>

</beans>

   3).在UsernamePasswordToken.class 的

public char[] getPassword() {
        return password;
    }

  处打断点,会看到断点停两次。

  

  由于两种都使用的HashedCredentialsMatcher 时的两种算法:

测试成功:

[FirstRealm] doGetAuthenticationInfo org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true
从数据库中获取userName :admin 所对应的用户信息.
[SecondRealm] doGetAuthenticationInfo org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true
从数据库中获取userName :admin 所对应的用户信息.

  这两个使用顺序的,因为我们在配置文件中的配置,

<!-- 配置多个Realm -->
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>

2. Shiro 认证策略

  1).如果有多个Realm,怎样才算是认证成功,这就需要认证策略。

  认证策略主要使用的是 AuthenticationStrategy 接口

   这个接口由三个实现类:

    •   AuthenticationStrategy接口的默认实现:
    •   FirstSuccessfulStrategy:只要有一个Realm 验证成功即可,只返回第一个Realm 身份验证成功的认证信息,其他的忽略;
    •   AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息;
    •   AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
    •   ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略

  通过debug 可以看出,默认使用的是AtLeastOneSuccessfulStrategy

  

  为了便于观看,修改SecondRealm中的

    info = new SimpleAuthenticationInfo("seconde", credentials, credentialsSalt, realmName);

  2). 如何切换认证策略

    切换成 AllSuccessfulStrategy 即所有认证策略都通过了,才算认证成功。

    可以看出 认证策略是ModularRealmAuthenticator 类的一个属性 authenticationStrategy

    即在applicationContext.xml中添加配置:

<!-- 配置多个Realm -->
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
        </property>
    </bean>

    修改其中一个Realm的密码 为一个错误的密码:

    if("admin".equals(username)){
            credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06---";
        }else if("user".equals(username)){
            credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718---";
        }

    则可以看到修改后的验证策略为:

    [email protected]

[FirstRealm] doGetAuthenticationInfo org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true
从数据库中获取userName :admin 所对应的用户信息.
[SecondRealm] doGetAuthenticationInfo org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true
从数据库中获取userName :admin 所对应的用户信息.
登录失败:Unable to acquire account data from realm [[email protected]]. The [org.apache.shiro.authc.pam.AllSuccessfulStrategy implementation requires all configured realm(s) to operate successfully for a successful authentication.

时间: 2024-10-13 16:10:30

Shiro-多Realm验证的相关文章

shiro双realm验证

假设现在有这样一种需求:存在两张表user和admin,分别记录普通用户和管理员的信息.并且现在要实现普通用户和管理员的分开登录,即需要两个Realm--UserRealm和AdminRealm,分别处理普通用户和管理员的验证功能.  但是正常情况下,当定义了两个Realm,无论是普通用户登录,还是管理员登录,都会由这两个Realm共同处理.这是因为,当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAu

shiro多realm验证之——shiro实现不同身份使用不同Realm进行验证(转)

转自: http://blog.csdn.net/xiangwanpeng/article/details/54802509 (使用特定的realm实现特定的验证) 假设现在有这样一种需求:存在两张表user和admin,分别记录普通用户和管理员的信息.并且现在要实现普通用户和管理员的分开登录,即需要两个Realm--UserRealm和AdminRealm,分别处理普通用户和管理员的验证功能.  但是正常情况下,当定义了两个Realm,无论是普通用户登录,还是管理员登录,都会由这两个Realm

Shiro自定义realm实现密码验证及登录、密码加密注册、修改密码的验证

一:先从登录开始,直接看代码 @RequestMapping(value="dologin",method = {RequestMethod.GET, RequestMethod.POST},produces="text/html;charset=UTF-8") @ResponseBody public ResultJson systemUserdologin(XXX xxx,HttpServletRequest request,HttpServletRespons

springMvc和shiro整合,shiro的realm不能自动注入的问题

最近研究shiro,一开头就遇到了大困难,调试了3小时.问题描述如下:shiro和spring mvc整合,shiro自定义了realm.其中自定义的realm里面居然不能使用@Autowired注解标签注入相关的用户service.百思不得其解,一项项跟踪,发现原来shiro 自定义realm的认证阶段属于filter,当时的spring bean还没有读取进来. 最后通过配置web.xml文件,把spring mvc的xml提高一点优先级,才最终解决了这个问题. 1 <!-- 配置sprin

Shiro自定义Realm时用注解的方式注入父类的credentialsMatcher

用Shiro做登录权限控制时,密码加密是自定义的. 数据库的密码通过散列获取,如下,算法为:md5,盐为一个随机数字,散列迭代次数为3次,最终将salt与散列后的密码保存到数据库内,第二次登录时将登录的令牌再进行同样的运算后再与数据库的做对比. String algorithmName = "md5";String userName = "rose";String password = "rose123";int hashIterations =

Shiro - 关于Realm

之前在Authentication和Authorization中也提到Realm. 无论是身份验证还是权限验证,无论数据以什么方式存在,我们都需要访问一些数据并将其转换为Shiro可以识别的格式. 通常一个数据源对应一个Realm.因此,实现一个Realm时会用到该数据源相关的API. 通常一个数据源中会同时保存身份相关数据与权限相关数据.因此,一个Realm实现类可以进行认证和授权两种操作. 可以将Realm简单地理解为DAO. (虽然IDEA生成的type hirarchy diagram很

Spring MVC + Shiro 实现权限验证

MAVEN的pom.xml 引入shiro(Spring MVC+mybatis 请参见上一章). <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency> <!-- http://mvnrepository.co

shiro 前后台realm 写法,用于分辨realm

首先改写一下  UsernamePasswordToken 这个类 新建一个类,叫UsernamePasswordUsertypeToken,继承UsernamePasswordToken package hstc.edu.cn.realm; import org.apache.shiro.authc.UsernamePasswordToken; /** * Created by win8 on 2017/5/29. */ public class UsernamePasswordUsertyp

shiro的Realm

public class UserRealm extends AuthorizingRealm { private UserService userService = new UserServiceImpl(); protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.getPrimaryPrincipal()

shiro自定义realm支持MD5算法(六)

1.1     散列算法 通常需要对密码 进行散列,常用的有md5.sha, 对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文. 建议对md5进行散列时加salt(盐),进行加密相当 于对原始密码+盐进行散列.(盐就相当于加入一个随机数) 正常使用时散列方法: 在程序中对原始密码+盐进行散列,将散列值存储到数据库中,并且还要将盐也要存储在数据库中. 如果进行密码对比时,使用相同 方法,将原始密码+盐进行散列,进行比对. 1.2 MD5测试 package cn.qlq.