shiro双realm验证

假设现在有这样一种需求:存在两张表user和admin,分别记录普通用户和管理员的信息。并且现在要实现普通用户和管理员的分开登录,即需要两个Realm——UserRealm和AdminRealm,分别处理普通用户和管理员的验证功能。 
  但是正常情况下,当定义了两个Realm,无论是普通用户登录,还是管理员登录,都会由这两个Realm共同处理。这是因为,当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用的Realm的是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时,就使用这个Realm,当配置了多个Realm时,会使用所有配置的Realm。 
  现在,为了实现需求,我会创建一个org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类,并重写doAuthenticate()方法,让特定的Realm完成特定的功能。如何区分呢?我会同时创建一个org.apache.shiro.authc.UsernamePasswordToken的子类,在其中添加一个字段loginType,用来标识登录的类型,即是普通用户登录,还是管理员登录。具体步骤如下: 
   
  第一步:创建枚举类LoginType用以记录登录的类型:

//登录类型
//普通用户登录,管理员登录
public enum LoginType {
    USER("User"),  ADMIN("Admin");

    private String type;

    private LoginType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return this.type.toString();
    }
}

第二步:新建org.apache.shiro.authc.UsernamePasswordToken的子类CustomizedToken:

import org.apache.shiro.authc.UsernamePasswordToken;

public class CustomizedToken extends UsernamePasswordToken {

    //登录类型,判断是普通用户登录,教师登录还是管理员登录
    private String loginType;

    public CustomizedToken(final String username, final String password,String loginType) {
        super(username,password);
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }
}

第三步:新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类CustomizedModularRealmAuthenticator:

import java.util.ArrayList;
import java.util.Collection;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;

/**
 * @author Alan_Xiang
 * 自定义Authenticator
 * 注意,当需要分别定义处理普通用户和管理员验证的Realm时,对应Realm的全类名应该包含字符串“User”,或者“Admin”。
 * 并且,他们不能相互包含,例如,处理普通用户验证的Realm的全类名中不应该包含字符串"Admin"。
 */
public class CustomizedModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();
        // 强制转换回自定义的CustomizedToken
        CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
        // 登录类型
        String loginType = customizedToken.getLoginType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        Collection<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if (realm.getName().contains(loginType))
                typeRealms.add(realm);
        }

        // 判断是单Realm还是多Realm
        if (typeRealms.size() == 1)
            return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
        else
            return doMultiRealmAuthentication(typeRealms, customizedToken);
    }

}

第四步:创建分别处理普通用户登录和管理员登录的Realm: 
   
  UserRealm:

import javax.annotation.Resource;

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

import com.ang.elearning.po.User;
import com.ang.elearning.service.IUserService;

public class UserRealm extends AuthorizingRealm {

    @Resource
    IUserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        User user = null;
        // 1. 把AuthenticationToken转换为CustomizedToken
        CustomizedToken customizedToken = (CustomizedToken) token;
        // 2. 从CustomizedToken中获取email
        String email = customizedToken.getUsername();
        // 3. 若用户不存在,抛出UnknownAccountException异常
        user = userService.getUserByEmail(email);
        if (user == null)
            throw new UnknownAccountException("用户不存在!");
        // 4.
        // 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
        // 以下信息从数据库中获取
        // (1)principal:认证的实体信息,可以是email,也可以是数据表对应的用户的实体类对象
        Object principal = email;
        // (2)credentials:密码
        Object credentials = user.getPassword();
        // (3)realmName:当前realm对象的name,调用父类的getName()方法即可
        String realmName = getName();
        // (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
        ByteSource credentialsSalt = ByteSource.Util.bytes(email);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
                realmName);
        return info;
    }

}

AdminRealm:

import javax.annotation.Resource;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import com.ang.elearning.po.Admin;
import com.ang.elearning.service.IAdminService;

public class AdminRealm extends AuthorizingRealm {

    @Resource
    private IAdminService adminService;

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        Admin admin = null;
        // 1. 把AuthenticationToken转换为CustomizedToken
        CustomizedToken customizedToken = (CustomizedToken) token;
        // 2. 从CustomizedToken中获取username
        String username = customizedToken.getUsername();
        // 3. 若用户不存在,抛出UnknownAccountException异常
        admin = adminService.getAdminByUsername(username);
        if (admin == null)
            throw new UnknownAccountException("用户不存在!");
        // 4.
        // 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
        // 以下信息从数据库中获取
        // (1)principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
        Object principal = username;
        // (2)credentials:密码
        Object credentials = admin.getPassword();
        // (3)realmName:当前realm对象的name,调用父类的getName()方法即可
        String realmName = getName();
        // (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
                realmName);
        return info;
    }

}

第五步:在spring配置文件中指定使用自定义的认证器:(其他配置略)

  <!-- 配置SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager" />
        <property name="authenticator" ref="authenticator"></property>
        <!-- 可以配置多个Realm,其实会把realms属性赋值给ModularRealmAuthenticator的realms属性 -->
        <property name="realms">
            <list>
                <ref bean="userRealm" />
                <ref bean="adminRealm"/>
            </list>
        </property>
    </bean>

  <!-- 配置使用自定义认证器,可以实现多Realm认证,并且可以指定特定Realm处理特定类型的验证 -->
    <bean id="authenticator" class="com.ang.elearning.shiro.CustomizedModularRealmAuthenticator">
        <!-- 配置认证策略,只要有一个Realm认证成功即可,并且返回所有认证成功信息 -->
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
        </property>
    </bean>

    <!-- 配置Realm -->
    <bean id="userRealm" class="com.ang.elearning.shiro.UserRealm">
        <!-- 配置密码匹配器 -->
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!-- 加密算法为MD5 -->
                <property name="hashAlgorithmName" value="MD5"></property>
                <!-- 加密次数 -->
                <property name="hashIterations" value="1024"></property>
            </bean>
        </property>
    </bean>

    <bean id="adminRealm" class="com.ang.elearning.shiro.AdminRealm">
        <!-- 配置密码匹配器 -->
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!-- 加密算法为MD5 -->
                <property name="hashAlgorithmName" value="MD5"></property>
                <!-- 加密次数 -->
                <property name="hashIterations" value="1024"></property>
            </bean>
        </property>
    </bean>

第六步:配置控制器: 
   
  UserController:

@Controller
@RequestMapping("/user")
public class UserController {

    private static final String USER_LOGIN_TYPE = LoginType.USER.toString();

    @Resource
    private IUserService userService;

    @RequestMapping(value = "login", method = RequestMethod.POST)
    public String login(@RequestParam("email") String email, @RequestParam("password") String password) {
        Subject currentUser = SecurityUtils.getSubject();
        if (!currentUser.isAuthenticated()) {
            CustomizedToken customizedToken = new CustomizedToken(email, password, USER_LOGIN_TYPE);
            customizedToken.setRememberMe(false);
            try {
                currentUser.login(customizedToken);
                return "user/index";
            } catch (IncorrectCredentialsException ice) {
                System.out.println("邮箱/密码不匹配!");
            } catch (LockedAccountException lae) {
                System.out.println("账户已被冻结!");
            } catch (AuthenticationException ae) {
                System.out.println(ae.getMessage());
            }
        }
        return "redirect:/login.jsp";
    }
}

AdminController:

@Controller
@RequestMapping("/admin")
public class AdminController {

    private static final String ADMIN_LOGIN_TYPE = LoginType.ADMIN.toString();

    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(@RequestParam("username") String username,@RequestParam("password") String password){
        Subject currentUser = SecurityUtils.getSubject();
        if(!currentUser.isAuthenticated()){
            CustomizedToken customizedToken = new CustomizedToken(username, password, ADMIN_LOGIN_TYPE);
            customizedToken.setRememberMe(false);
            try {
                currentUser.login(customizedToken);
                return "admin/index";
            } catch (IncorrectCredentialsException ice) {
                System.out.println("用户名/密码不匹配!");
            } catch (LockedAccountException lae) {
                System.out.println("账户已被冻结!");
            } catch (AuthenticationException ae) {
                System.out.println(ae.getMessage());
            }
        }
        return "redirect:/login.jsp";
    }

}

测试页面:login.jsp

<body>
    <form action="${pageContext.request.contextPath }/user/login"
        method="POST">
        邮箱:<input type="text" name="email">
        <br><br>
        密码:<input type="password" name="password">
        <br><br>
        <input type="submit" value="用户登录">
    </form>
    <br>
    <br>
    <form action="${pageContext.request.contextPath }/admin/login"
        method="POST">
        用户名:<input type="text" name="username">
        <br><br>
        密 码:<input type="password" name="password">
        <br><br>
        <input type="submit" value="管理员登录">
    </form>
</body>

这就实现了UserRealm用以处理普通用户的登录验证,AdminRealm用以处理管理员的登录验证。 
  如果还需要添加其他类型,例如,需要添加一个教师登录模块,只需要再新建一个TeacherRealm,并且在枚举类loginType中添加教师的信息,再完成其他类似的配置即可。

本文内容转自:http://blog.csdn.net/xiangwanpeng/article/details/54802509

时间: 2024-10-08 19:47:24

shiro双realm验证的相关文章

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

odoo12之应用:一、双因子验证(Two-factor authentication, 2FA)(HOTP,TOTP)附源码

前言 双因子认证:双因子认证(2FA)是指结合密码以及实物(信用卡.SMS手机.令牌或指纹等生物标志)两种条件对用户进行认证的方法.--百度百科 跟我一样"老"的网瘾少年想必一定见过买点卡后上面送的密保(类似但不完全一样),还有"将军令",以及网银的网盾,是一种二次验证的机制:它通常是6位的数字,每次使用后(HOTP)或者一定时间后(TOTP)都将会刷新,大大加大了用户的安全性,OTP(One-Time Password)分为HOTP(HMAC-based One-

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