转载:https://blog.csdn.net/xiangwanpeng/article/details/54802509

假设现在有这样一种需求:存在两张表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中添加教师的信息,再完成其他类似的配置即可。

shiro实现不同身份使用不同Realm进行验证的更多相关文章

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

    转自: http://blog.csdn.net/xiangwanpeng/article/details/54802509 (使用特定的realm实现特定的验证) 假设现在有这样一种需求:存在两张表 ...

  2. 30、shiro框架入门2,关于Realm

    1.Jdbc的Realm链接,并且获取权限 首先创建shiro-jdbc.ini的配置文件,主要配置链接数据库的信息 配置文件中的内容如下所示 1.变量名=全限定类名会自动创建一个类实例 2.变量名. ...

  3. Shiro学习之身份验证

    身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和cre ...

  4. 【Shiro】Apache Shiro架构之身份认证(Authentication)

    Shiro系列文章: [Shiro]Apache Shiro架构之权限认证(Authorization) [Shiro]Apache Shiro架构之集成web [Shiro]Apache Shiro ...

  5. shrio 身份认证流程-Realm

    身份认证流程 流程如下: 1.首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecuri ...

  6. Apache Shiro 使用手册(四)Realm 实现

    在认证.授权内部实现机制中都有提到,最终处理都将交给Real进行处理.因为在Shiro中,最终是通过Realm来获取应用程序中的用户.角色及权限信息的.通常情况下,在Realm中会直接从我们的数据源中 ...

  7. Apache Shiro 使用手册(四)Realm 实现(转发:http://kdboy.iteye.com/blog/1169631)

    在认证.授权内部实现机制中都有提到,最终处理都将交给Real进行处理.因为在Shiro中,最终是通过Realm来获取应用程序中的用户.角色及权限信息的.通常情况下,在Realm中会直接从我们的数据源中 ...

  8. 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)

    前言: shiro因为其简单.可靠.实现方便而成为现在最常用的安全框架,那么这篇文章除了会用简洁明了的方式讲一下基于spring的shiro详细配置和登录注销功能使用之外,也会根据惯例在文章最后总结一 ...

  9. Shiro学习总结(4)——Shrio登陆验证实例详细解读

    最终效果如下: 工程整体的目录如下: Java代码如下: 配置文件如下: 页面资源如下: 好了,下面来简单说下过程吧! 准备工作: 先建表: [sql] view plain copy drop ta ...

随机推荐

  1. 分享一个php的防火墙,拦截SQL注入和xss

    一个基于php的防火墙程序,拦截sql注入和xss攻击等 安装 composer require xielei/waf 使用说明 $waf = new \Xielei\Waf\Waf(); $waf- ...

  2. 每天一个dos命令-del.

    比较常用的选项: /F 强制删除只读文件. /Q 安静模式.删除全局通配符时,不要求确认 文件名或者路径中有空格,需要使用引号包围 常用的实例:del  /q/f   c:\Securitylog\S ...

  3. JVM学习目录

    JVM学习目录 JVM的整体结构 1.类加载子系统 类加载子系统 2.运行时数据区 运行时数据区总览 堆.栈.方法区的详细图解 2.1.程序计数器 程序计数器 2.2.本地方法栈 本地方法栈 2.3. ...

  4. sql 注入初探

    Sql注入:就是将恶意的sql语句插入到用户输入的参数当中并带入数据库中查询并在浏览器返回不该显示的信息 寻找sql注入点: 1.要有参数值的传递(url当中GET型的.注册用户名的页面.登录框.留言 ...

  5. Ansys Student 2020R2中Fluent编译UDF简介

    使用内建编译器 在Ansys Fluent中编译UDF一般都需要额外安装相应版本的Visual Studio编译器,VS的缺点是体量大,占空间,安装后还需要额外进行相关设置才能正常使用.而新版本的An ...

  6. mysql-12-DDL

    #DDL /* 数据定义语言 库和表的管理 一.库的管理 创建.修改.删除 二.表的管理 创建.修改.删除 创建 create 修改 alter 删除 drop */ #一.库的管理 #1.库的创建 ...

  7. Java知识系统回顾整理01基础02面向对象01类和对象

    一.面向对象实例--设计英雄这个类 LOL有很多英雄,比如盲僧,团战可以输,提莫必须死,盖伦,琴女 所有这些英雄,都有一些共同的状态 比如,他们都有名字,hp,护甲,移动速度等等 这样我们就可以设计一 ...

  8. ::在C++中是什么意思

    转自:https://blog.csdn.net/u012547790/article/details/22727277 ::在C++中是什么意思 今天又想了一下::在C++中是什么意思: 表示作用域 ...

  9. matlab中fspecial Create predefined 2-D filter以及中值滤波均值滤波以及高斯滤波

    来源: 1.https://ww2.mathworks.cn/help/images/ref/fspecial.html?searchHighlight=fspecial&s_tid=doc_ ...

  10. 使用MVC 5、Web API 2、KnockoutJS、Ninject和NUnit开发、架构和测试Web应用程序

    做一名微软软件开发人员就像在国际煎饼屋订早餐一样.每道菜都有一堆煎饼,你必须从各种各样的煎饼和糖浆口味中选择.对于web应用程序,解决方案堆栈是一组软件子系统或组件,用于交付功能完整的解决方案(无论是 ...