Shiro的Jdbc配置和验证策略(二)

戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。

一. 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

谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!

原文地址:https://www.cnblogs.com/yjltx/p/14343146.html