Shiro 入门与身份认证
本节介绍了 Shiro 的入门示例程序。
“身份认证”是 Shiro 里很重要的概念。“认证”就是通过输入用户名和密码,到内存中或者到数据库中去查询是否有这个用户名的信息(且密码也匹配)。
用户信息存放在“内存”中
用户信息存放在“内存”中,意即将用户的用户名和密码信息存放在一个名为 shiro.ini 的文件中。更多的情况是,我们把用户的用户名和密码信息存放在数据库中,这也是我们以前做的项目的绝大多数情况下的做法。
项目结构:
创建 maven 项目。
1、引入 gav 依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.13</version>
</dependency>
如果使用 gradle 构建,则坐标为
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'org.apache.shiro:shiro-core:1.2.4'
compile 'org.slf4j:slf4j-log4j12:1.7.13'
}
2、编写 shiro 核心配置文件 shiro.ini
[users]
liwei=123456
wudi=wudi
huzhenyu=huzhenyu
3、编写 HelloWorld 代码
这里要特别注意:SecurityManager
所在的包名应该是org.apache.shiro.mgt
。
package com.liwei.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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;
import sun.org.mozilla.javascript.internal.SecurityUtilities;
/**
* Created by Liwei on 2015/12/15.
*/
public class HelloWorld {
public static void main(String[] args) {
// 读取配置文件,初始化 SecurityManager 工厂
// 注意:这里 SecurityManager 所在的包名
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 获取 SecurityManager 的实例
SecurityManager securityManager = factory.getInstance();
// 把 securityManager 的实例绑定到 SecurityUtils 上
SecurityUtils.setSecurityManager(securityManager);
// 得到当前执行的用户
Subject currentUser = SecurityUtils.getSubject();
// 自己创建一个令牌,输入用户名和密码
// UsernamePasswordToken token = new UsernamePasswordToken("liwei","123456");
UsernamePasswordToken token = new UsernamePasswordToken("huzhenyu","huzhenyu");
try {
// 身份认证(该方法会抛出异常)
currentUser.login(token);
System.out.println("身份认证成功!");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("身份认证失败!");
}
// 退出
currentUser.logout();
}
}
4、引入 slf4j 的 log4j 实现配置文件:log4j.properties
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
Subject 认证主体包含两个信息:
Principals:身份,可以是用户名,邮件,手机号码等等,用来标识一个登录主体身份;(我们可以理解成用户名)
Credentials:凭证,常见有密码,数字证书等等;(我们可以理解成密码)
用户信息存放在数据库中
下面我们介绍使用 JdbcRealm。那么什么是 Realm 呢?Realm 可以理解为主体的数据源(数据来源,里面存放的都是正确的用户名和密码)。
Realm 的意思是域,Shiro 从 Realm 中获取验证数据。
只须要对上面的代码稍稍作修改就可以实现将用户信息存放在数据库中,通过数据库中的用户信息进行登录匹配。既然使用的是 JdbcRealm,就须要使用到 jdbc 的相关技术。
首先我们引入依赖:
compile 'mysql:mysql-connector-java:5.1.38'
compile 'c3p0:c3p0:0.9.1.2'
compile 'commons-logging:commons-logging:1.2'
再向数据库里写入数据:
这里的表名和字段名都是固定的,这一点我们通过看 JdbcRealm 的源代码就可以知道。不过显然这样的方式显然是不灵活的。
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
userName varchar(20) DEFAULT NULL,
password varchar(20) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into users(userName,password) values ('liwei','123456');
insert into users(userName,password) values ('zhouguang','123456');
配置文件 jdbc_realm.ini:
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro
dataSource.password=123456
dataSource.user=root
dataSource.driverClass=com.mysql.jdbc.Driver
jdbcRealm.dataSource=$dataSource
securityManager.realm=$jdbcRealm
配置文件解释:
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
表示实例化右边字符串表示的类,赋给左边字符串表示的变量。
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro
表示对 dataSource 这个变量设置属性值 jdbcUrl ,这个属性值是一个字符串。
jdbcRealm.dataSource=$dataSource
表示对 jdbcRealm 这个变量设置属性值 dataSource , 这个 dataSource 属性是上面实例化的一个对象,所以表示这个对象要使用前缀 $
。
测试代码(和上一节没有什么不同,只是配置文件不一样而已):
public class JdbeReamlTest {
/**
* 这一节的内容是 身份认证
* @param args
*/
public static void main(String[] args) {
// 注意:这里 SecurityManager 所在的包名
// 读取配置文件,初始化SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbc_realm.ini");
SecurityManager securityManager = factory.getInstance();
// 把 securityManager 的实例绑定到 SecurityUtils 上
SecurityUtils.setSecurityManager(securityManager);
// 得到当前执行的用户
Subject currentUser = SecurityUtils.getSubject();
// 自己创建一个令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhouguang","123456");
try {
// 身份认证
currentUser.login(token);
System.out.println("身份认证成功");
} catch (AuthenticationException e) {
e.printStackTrace();
}
System.out.println("退出登录");
// 最后别忘了退出登录(shiro还会做很多操作,从控制台就可以看出)
currentUser.logout();
System.out.println("退出登录成功");
}
}
补充:在 currentUser.login(token);
这一行捕获的异常,这里只是简单地捕获了 AuthenticationException
这个异常,我们还可以具体地捕获用户名不存在异常和密码不存在异常。
总结:
使用 Shiro 进行身份认证的流程是:将用户的用户名和密码信息存放在一个 UsernamePasswordToken
对象中,通过 Subject
的 login()
方法把 UsernamePasswordToken
对象传进去。
此时 Shiro 框架会去 shiro.ini 文件中找指定的安全数据源 Realm
。默认是 IniRealm ,如果我们配置了 JdbcRealm ,就使用 JdbcRealm。
用户名是否存在,用户名存在的前提下密码是否也匹配,这个操作是由 Shiro 帮助我们完成的。