戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。
一. Shiro 的认证错误
在上一章节的例子中,我们可以看到, Shiro 是通用 Subject 对象的 login() 方法,进行认证判断的。 我们自己在开发中,常常有这么一个需求,在登录时,如果登录失败,会给出一些相应的提示,如用户名不存在,密码错误,账号不可用等信息。 Shiro 也提供了相应的功能。
public abstract void login(AuthenticationToken paramAuthenticationToken)
throws AuthenticationException;
Shiro 会抛出一个异常, AuthenticationException。
这个异常有许多子类,
通常会捕获相应的子类异常信息,进行相应的错误提示。
异常类名 | 异常解释 |
---|---|
DisabledAccountException | 禁用账户 |
LockedAccountException | 账户被锁定 |
UnknownAccountException | 错误的账户,即账户名不存在 |
ExcessiveAttemptsException | 登录失败次数过多,如超过3次 |
IncorrectCredentialsException | 错误凭证,即密码不正确 |
ExpiredCredentialsException | 过期凭证 |
常见的是 IncorrectCredentialsException 密码不正确和 UnknownAccountException 和未知账户。
友情提示: 在错误信息提示时,不要提示,用户名不存在,密码错误 这样的信息,而应该是 用户名/密码 错误,避免有心人尝试密码破解或者获取网站的用户名。
登录成功之后,也可以进行相应的退出, 调用 subject对象的 logout() 方法进行退出。
写一个 用户名密码和 退出的小例子。(配置文件,仍然使用 shiro.ini)
package com.yjl.demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
*
* @author 两个蝴蝶飞
* Shiro 的第二个演示文件, 凭证错误和退出
*/
public class ShiroDemo2 {
public static void main(String[] args) {
//1. 创建工厂
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
//2. 从工厂里面获取 SecurityManager
SecurityManager securityManager=factory.getInstance();
//3. 通过工具类设置 securityManager
SecurityUtils.setSecurityManager(securityManager);
//4. 获取当前登录的用户
Subject subject=SecurityUtils.getSubject();
//5. 拼装用户的身份和密码Token
UsernamePasswordToken token=new UsernamePasswordToken("yuezl","1234"); //1.正确密码
//UsernamePasswordToken token=new UsernamePasswordToken("yuezl","123456"); //2.错误密码
//UsernamePasswordToken token=new UsernamePasswordToken("yuezlAbc","1234"); //3.未知账户
System.out.println("用户是否登录成功A:"+subject.isAuthenticated());
//6. 调用 subject 里面的login 方法,进行登录
try{
subject.login(token);
//7.判断用户是否登录成功
if(subject.isAuthenticated()){
System.out.println("用户:"+token.getUsername()+", 登录成功");
}
}catch (UnknownAccountException var6) {
var6.printStackTrace();
System.out.println("没有此账号");
} catch (IncorrectCredentialsException var7) {
var7.printStackTrace();
System.out.println("密码不正确");
} catch (DisabledAccountException var8) {
var8.printStackTrace();
System.out.println("账户不可用");
}
System.out.println("用户是否登录成功B:"+subject.isAuthenticated());
//退出程序
subject.logout();
System.out.println("用户是否登录成功C:"+subject.isAuthenticated());
}
}
当运行1时, 用户名和密码均正确时:
当运行2时,用户名正确,但密码不正确
当运行3时,用户名不存在
用户名和密码是我们自己在配置文件 shiro.ini 里面配置的,能不能在数据库里面查询呢?
是可以的,我们可以实现 JdbcRealm
二. 配置JdbcRealm
二.一 JdbcRealm 内置 sql语句
public class JdbcRealm
extends AuthorizingRealm
{
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password
from users where username = ?";
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt
from users where username = ?";
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name
from user_roles where username = ?";
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission
from roles_permissions where role_name = ?";
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
...
};
JdbcRealm 继承了 AuthorizingRealm 抽象类。
由于 JdbcRealm 提供了内置的 sql 语句,所以如果我们想使用 JdbcRealm 那么就必须保证 用户表的表名必须为 users, 用户名必须为 username, 密码必须为 password 字段。 users 表不一定只有这两个字段,但必须要保证有这两个字段,如果想加盐加密,还需要有password_salt 字段。
二.二 创建数据库 shiro,用户表为 users
users 表里面必须有 username 和password 两个字段。
二.三 添加数据库和c3p0的相应依赖
pom.xml 中追加依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
二.四 创建jdbcRealm.ini, 里面配置数据库的相应信息
不用自定义用户信息了,直接通过 JdbcRealm 从数据库里面查询。 添加一个c3p0的数据库连接池。
[main]
#配置数据源
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
#配置数据库的信息
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8
dataSource.user=root
dataSource.password=abc123
#配置 realm,用JdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
jdbcRealm.dataSource=$dataSource
#注入到securityManager里面
securityManager.realm=$jdbcRealm
#不用自定义配置users 的信息
需要在 [main] 里面进行配置, 类似于 Spring 的 注入。
二.五 测试验证
package com.yjl.demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
*
* @author 两个蝴蝶飞
* Shiro 的第三个演示文件, JdbcRealm的使用
*/
public class ShiroDemo3 {
public static void main(String[] args) {
//1. 创建工厂
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:jdbcRealm.ini");
//2. 从工厂里面获取 SecurityManager
SecurityManager securityManager=factory.getInstance();
//3. 通过工具类设置 securityManager
SecurityUtils.setSecurityManager(securityManager);
//4. 获取当前登录的用户
Subject subject=SecurityUtils.getSubject();
//5. 拼装用户的身份和密码Token
UsernamePasswordToken token=new UsernamePasswordToken("yuezl","1234"); //1.正确密码
//UsernamePasswordToken token=new UsernamePasswordToken("yuezl","123456"); //2.错误密码
//UsernamePasswordToken token=new UsernamePasswordToken("yuezlAbc","1234"); //3.未知账户
System.out.println("用户是否登录成功A:"+subject.isAuthenticated());
//6. 调用 subject 里面的login 方法,进行登录
try{
subject.login(token);
//7.判断用户是否登录成功
if(subject.isAuthenticated()){
System.out.println("用户:"+token.getUsername()+", 登录成功");
}
}catch (UnknownAccountException var6) {
var6.printStackTrace();
System.out.println("没有此账号");
} catch (IncorrectCredentialsException var7) {
var7.printStackTrace();
System.out.println("密码不正确");
} catch (DisabledAccountException var8) {
var8.printStackTrace();
System.out.println("账户不可用");
}
System.out.println("用户是否登录成功B:"+subject.isAuthenticated());
//退出程序
subject.logout();
System.out.println("用户是否登录成功C:"+subject.isAuthenticated());
}
}
当运行1时, 用户名和密码均正确时:
当运行2时,用户名正确,但密码不正确
当运行3时,用户名不存在
注意,数据是从数据库里面查询出来的,并不是从 jdbcRealm.ini 里面配置出来的。
一定要保证字段是一致的,不然无法通过认证。
三. 认证策略
securityManager 可以配置多个 Realm, 当配置不同Realm, 甚至Realm 是不同的数据库时,那么在验证的时候,以谁为准呢?
这就是验证的策略。
验证策略 AuthenticationStrategy 有三种:
策略名 | 作用 |
---|---|
AllSuccessfulStrategy | 所有的Realm验证成功才算成功,且返回所有的Realm 认证信息,如果有一个没有通过,就是失败 |
AtLeastOneSuccessfulStrategy | 至少有一个成功,且返回所有通过认证的信息 |
FirstSuccessfulStrategy | 至少有一个成功,且只返回第一个通过认证的信息 (默认) |
AtLeastOneSuccessfulStrategy 与 FirstSuccessfulStrategy 的区别是, FirstSuccessfulStrategy 只返回第一条认证通过的信息,
AtLeastOneSuccessfulStrategy则会返回所有认证通过的信息。
下面写一个小例子,进行验证一下。
三.一 创建数据库 shiro1, 里面有一个 users 数据表
注意,用户的密码。
三.二 创建配置文件 jdbcStrategy.ini
[main]
#配置数据源
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
#配置数据库的信息
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8
dataSource.user=root
dataSource.password=abc123
#配置 realm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
jdbcRealm.dataSource=$dataSource
#配置数据源1
dataSource1=com.mchange.v2.c3p0.ComboPooledDataSource
#配置数据库的信息
dataSource1.driverClass=com.mysql.jdbc.Driver
dataSource1.jdbcUrl=jdbc:mysql://localhost:3306/shiro1?characterEncoding=utf8
dataSource1.user=root
dataSource1.password=abc123
#配置 realm
jdbcRealm1=org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
jdbcRealm1.dataSource=$dataSource1
#注入多个realm 到securityManager里面
securityManager.realms=$jdbcRealm,$jdbcRealm1
#配置验证器
authenticationStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
#authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
#authenticationStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$authenticationStrategy
三.三 测试文件,进行测试
注意,用户名和密码
Shiro 数据库里面, yuejl 的密码是1234, yuezl 的密码是1234
Shiro1 数据库里面, yuejl的密码是12345, yuezl的密码是 1234.
ShiroDemo5.java
package com.yjl.demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
*
* @author 两个蝴蝶飞
* Shiro 的第五个演示文件, 策略
*/
public class ShiroDemo5 {
public static void main(String[] args) {
//1. 创建工厂
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:jdbcStrategy.ini");
//2. 从工厂里面获取 SecurityManager
SecurityManager securityManager=factory.getInstance();
//3. 通过工具类设置 securityManager
SecurityUtils.setSecurityManager(securityManager);
//4. 获取当前登录的用户
Subject subject=SecurityUtils.getSubject();
//5. 拼装用户的身份和密码Token
//怕乱,不注释写了,后面的程序,只改变这一个 token 代码
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","1234");
//6. 调用 subject 里面的login 方法,进行登录
try{
subject.login(token);
//7.判断用户是否登录成功
if(subject.isAuthenticated()){
System.out.println("用户:"+token.getUsername()+", 登录成功");
}
}catch (UnknownAccountException var6) {
var6.printStackTrace();
System.out.println("没有此账号");
} catch (IncorrectCredentialsException var7) {
var7.printStackTrace();
System.out.println("密码不正确");
} catch (DisabledAccountException var8) {
var8.printStackTrace();
System.out.println("账户不可用");
}
}
}
三.三.一 FirstSuccessfulStrategy 默认策略
1. 如果用户是 yuejl, 1234 的话, shiro 符合,shiro1 不符合, 符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","1234");
运行程序:
2. 如果用户是 yuejl, 12345 的话, shiro 不符合,shiro1 符合,符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","12345");
运行程序:
3. 如果用户是 yuezl, 1234 的话, shiro 符合,shiro1 符合,符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuezl","1234");
运行程序:
三.三.二 AtLeastOneSuccessfulStrategy
在 jdbcStrategy.ini 里面,将策略改成 AtLeastOneSuccessfulStrategy
1. 如果用户是 yuejl, 1234 的话, shiro 符合,shiro1 不符合,符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","1234");
运行程序:
2. 如果用户是 yuejl, 12345 的话, shiro 不符合,shiro1 符合,符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","12345");
运行程序:
3. 如果用户是 yuezl, 1234 的话, shiro 符合,shiro1 符合,符合至少一个成功
UsernamePasswordToken token=new UsernamePasswordToken("yuezl","1234");
运行程序:
三.三.三 AllSuccessfulStrategy
在 jdbcStrategy.ini 里面,将策略改成 AllSuccessfulStrategy
1. 如果用户是 yuejl, 1234 的话, shiro 符合,shiro1 不符合,不符合全部成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","1234");
运行程序:
2. 如果用户是 yuejl, 12345 的话, shiro 不符合,shiro1 符合,不符合全部成功
UsernamePasswordToken token=new UsernamePasswordToken("yuejl","12345");
运行程序:
3. 如果用户是 yuezl, 1234 的话, shiro 符合,shiro1 符合,符合全部成功
UsernamePasswordToken token=new UsernamePasswordToken("yuezl","1234");
运行程序:
策略演示成功 。
本章节代码链接为:
链接:https://pan.baidu.com/s/1u8i2KHx0FuWbAbf4Fuejzg
提取码:okrl
谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!