Apache Shiro是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码学和会话管理。
认证简单的说,就是登录的时候判断你的用户名和密码是否完全匹配,就是证明你是你。
授权,是在认证的基础之上,进行角色和权限的授予。权限决定了一个用户可以进行怎样的操作。
权限定义了一个用户是否可以执行某个操作。
角色就是一组权限的集合。
我们通常是把一组权限绑定到一种角色上,再把一个或者多个角色赋给一个用户,这样就实现了权限的控制。即权限通过角色定义到用户上。角色作为权限的集合,方便了我们对权限的管理。
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
[users]
heqianqian = 12345666
3.编写测试代码
public class SimpleLoginTest {
@Test
public void test() throws Exception {
ShiroUtils.login("classpath:shiro/shiro.ini", "heqianqian", "12345666");
}
}
public class ShiroUtils {
public static Subject getSubject(String path) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(path);
SecurityManager manager = factory.getInstance();
SecurityUtils.setSecurityManager(manager);
return SecurityUtils.getSubject();
}
public static Subject login(String path, String userName, String passWord) {
Subject subject = getSubject(path);
UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
System.out.println("无效的用户名");
e.printStackTrace();
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
} catch (AuthenticationException e) {
e.printStackTrace();
}
//subject.logout();
//System.out.println("退出成功");
return subject;
}
}
前面例子的数据写的配置文件里,我们也可以从数据库读取数据
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.39version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>${slf4j.version}version>
dependency>
[main]
# 表示实例化右边字符串表示的类,赋给左边字符串表示的变量。
##JdbcRealm默认使用users表
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
dataSource = com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass = com.mysql.jdbc.Driver
dataSource.user = root
dataSource.password = ****
# 表示对 dataSource 这个变量设置属性值 jdbcUrl ,这个属性值是一个字符串。
dataSource.jdbcUrl = jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8
# 表示对 jdbcRealm 这个变量设置属性值 dataSource , 这个 dataSource 属性是上面实例化的一个对象,所以表示这个对象要使用前缀 `$`。
jdbcRealm.dataSource = $dataSource
securityManager.realms = $jdbcRealm
DROP DATABASE db_shiro;
# 创建数据库 db_shiro
CREATE DATABASE db_shiro DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
# 使用数据库 db_shiro
USE db_shiro;
drop table if exists users;
create table users(
id int(11) not null auto_increment comment '主键',
username varchar(20) default null comment '用户名',
password varchar(20) default null comment '密码',
PRIMARY KEY (id)
)engine=innodb auto_increment=1 default charset=utf8 comment '用户表';
insert into users(username,password) values('hqq','123456');
注意:
这里建表的表名是users 因为JdbcRealm默认查找users表里的数据
JdbcRealm 源码
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 = ?";
...
}
4.编写测试方法
public class JdbcRealmTest {
private String userName = "heqianqian";
private String passWord = "123";
private Logger logger = Logger.getLogger(JdbcRealmTest.class);
@Test
public void test() throws Exception {
ShiroUtils.login("classpath:shiro/jdbcRealm.ini", userName, passWord);
}
}
这种方式实现的 Realm 仅只能实现认证操作,并不能实现授权操作。
public class MapRealm implements Realm {
private static Map users = new HashMap<>();
static {
users.put("heqianqian", "123");
users.put("lucy", "456");
}
/**
* 返回唯一的Realm名字
*/
@Override
public String getName() {
System.out.println("设置Realm的名字");
return "My MapRealm";
}
/**
* 判断是否支持Token
*/
@Override
public boolean supports(AuthenticationToken authenticationToken) {
System.out.println("Map Realm 中给出支持的 Token 的方法");
// 表示仅支持 UsernamePasswordToken 类型的 Token
return authenticationToken instanceof UsernamePasswordToken;
}
/**
* 获取认证信息
*/
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("Map Realm 中返回认证信息的方法");
String userName = (String) authenticationToken.getPrincipal();
String passWord = new String((char[])authenticationToken.getCredentials());
if (!users.containsKey(userName)) {
System.out.println("用户名错误!");
} else if (!users.get(userName).equals(passWord)) {
System.out.println("密码错误!");
}
return new SimpleAuthenticationInfo(userName, passWord, getName());
}
}
编写配置文件mapRealm.ini
[main]
# 声明了我们自己定义的一个 Realm
myMapRealm = heqianqian.shiro.realm.MapRealm
# 将我们自己定义的 Realm 注入到 securityManager 的 realms 属性中去
securityManager.realms = $myMapRealm
测试
public class MapRealmTest {
@Test
public void test() throws Exception {
ShiroUtils.login("classpath:shiro/mapRealm.ini","heqianqian","123");
}
}
public class MyStaticRealm extends AuthorizingRealm {
/**
* 用于授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 暂时忽略,以后介绍
return null;
}
/**
* 用于认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("Static Realm 中认证的方法");
String userName = authenticationToken.getPrincipal().toString();
String password = new String((char[]) authenticationToken.getCredentials());
if (!"heqianqian".equals(userName)) {
throw new UnknownAccountException("无效的用户名");
} else if (!"123".equals(password)) {
throw new IncorrectCredentialsException("密码无效");
}
return new SimpleAuthenticationInfo("heqianqian", "123", getName());
}
}
编写配置文件mystaticRealm.ini
[main]
myStaticRealm = heqianqian.shiro.realm.MyStaticRealm
securityManager.realms = $myStaticRealm
测试
@Test
public void testPermission() throws Exception {
Subject subject = ShiroUtils.login("classpath:shiro/mystaticRealm.ini", "heqianqian", "123");
}
这时候,我们会有一个疑问,securityManager 的属性既然是 realms,说明可以设置若干个 Realm,它们认证的顺序是如何的呢。
那么对于若干个 Realm,Shiro 提供了一种配置方式,让我们来决定在多个 Reaml 同时声明的情况下,采用哪些 Realm 返回的认证信息的方式,这就是我们的认证策略。
认证策略主要有以下三种:
1、FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
2、AtLeastOneSuccessfulStrategy: (这是默认使用的认证策略,即在不配置情况下 Shiro 所采用的认证策略)只要有一个 Realm 验证成功即可, 和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份验证成功的认证信息;
3、AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
配置示例
# 配置认证策略
allSuccessfulStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $allSuccessfulStrategy
编写配置文件
[users]
hqq = 123,role1,role2
lucy = 111,role1
[roles]
role1 = user:select
role2 = user:add,user:update,user:delete
测试角色和权限
public class RoleTest {
@Test
public void testHasRole() throws Exception {
Subject subject = ShiroUtils.login("classpath:shiro/shiro-role.ini", "hqq", "123");
assertEquals(true, subject.hasRole("role1"));
assertEquals(true, subject.hasRole("role2"));
assertEquals(true, subject.hasAllRoles(Arrays.asList("role1", "role2")));
subject.logout();
}
@Test
public void testCheckRole() throws Exception {
Subject subject = ShiroUtils.login("classpath:shiro/shiro-role.ini", "hqq", "123");
subject.checkRole("role1");
subject.checkRole("role2");
subject.checkRoles(Arrays.asList("role1", "role2"));
assertEquals(true, subject.hasRole("role2"));
}
}
public class PermissionTest {
@Test
public void testHasPermission() throws Exception {
Subject subject = ShiroUtils.login("classpath:shiro/shiro-permission.ini", "hqq", "123");
assertEquals(true, subject.isPermitted("user:select"));
subject.isPermitted("user:select", "user:add", "user:delete", "user:update");
assertEquals(true, subject.isPermittedAll("user:select", "user:select", "user:add", "user:delete", "user:update"));
subject.logout();
}
@Test
public void testCheckPermission() throws Exception {
Subject subject = ShiroUtils.login("classpath:shiro/shiro-permission.ini", "hqq", "123");
subject.checkPermission("user:select");
subject.checkPermissions("user:select", "user:add", "user:delete", "user:update");
subject.logout();
}
}
public class MyPermission implements Permission {
private String resourceId;
private String operator;
private String instanceId;
public MyPermission() {
}
public MyPermission(String permissionStr) {
String[] strs = permissionStr.split("\\+");
if (strs.length > 1) {
this.resourceId = strs[1];
}
if (this.resourceId == null || "".equals(this.resourceId)) {
this.resourceId = "*";
}
if (strs.length > 2) {
this.operator = strs[2];
}
if (strs.length > 3) {
this.instanceId = strs[3];
}
if (this.instanceId == null || "".equals(this.instanceId)) {
this.instanceId = "*";
}
System.out.println("实例化 MyPermission 时 => " + this.toString());
}
/**
* 【这是一个非常重要的方法】
* 由程序员自己编写授权是否匹配的逻辑,
* 我们这里的实现,是将 Realm 中给出的 Permission 和 ini 配置中指定的 PermissionResoler 中指定的 Permission 进行比对
* 比对的规则完全由我们自己定义
*/
@Override
public boolean implies(Permission permission) {
if (!(permission instanceof MyPermission)) {
return false;
}
MyPermission mp = (MyPermission) permission;
if (!"*".equals(mp.resourceId) && !this.resourceId.equals(mp.resourceId)) {
return false;
}
if (!"*".equals(mp.operator) && !this.operator.equals(mp.operator)) {
return false;
}
if (!"*".equals(mp.instanceId) && !this.instanceId.equals(mp.instanceId)) {
return false;
}
return true;
}
@Override
public String toString() {
return "MyPermission{" +
"resourceId='" + resourceId + '\'' +
", operator='" + operator + '\'' +
", instanceId='" + instanceId + '\'' +
'}';
}
}
实现 PermissionResolver 接口的意义在于告诉 Shiro 根据字符串的表现形式(表现特征),采用什么样的 Permission 进行匹配。
public class MyPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String s) {
/*如果我们的权限字符串是以 “+” 号开头的话,就使用我们自定义的 MyPermission ,否则就使用默认的 WildcardPermission 解析这个字符串。*/
if (s.startsWith("+")) {
return new MyPermission(s);
}
return new WildcardPermission(s);
}
}
说明:这段代码实现的一个效果是:如果我们的权限字符串是以 “+” 号开头的话,就使用我们自定义的 MyPermission ,否则就使用默认的 WildcardPermission 解析这个字符串。
public class MyStaticRealm extends AuthorizingRealm {
/**
* 用于授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("Static Realm 中授权的方法");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("r1");
info.addRole("r2");
info.addRole("role1");
info.addStringPermission("+user+");
info.addObjectPermission(new MyPermission("+user+add+1"));
return info;
}
/**
* 用于认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("Static Realm 中认证的方法");
String userName = authenticationToken.getPrincipal().toString();
String password = new String((char[]) authenticationToken.getCredentials());
if (!"heqianqian".equals(userName)) {
throw new UnknownAccountException("无效的用户名");
} else if (!"123".equals(password)) {
throw new IncorrectCredentialsException("密码无效");
}
return new SimpleAuthenticationInfo("heqianqian", "123", getName());
}
}
编写配置文件
#配置自定义权限解析器
permissionResolver = heqianqian.shiro.permission.MyPermissionResolver
authorizer.permissionResolver = permissionResolver
securityManager.authorizer = $authorizer
public class MyRolePermissionResolver implements RolePermissionResolver {
@Override
public Collection resolvePermissionsInRole(String s) {
System.out.println("MyRolePermissionResolver");
if (s.contains("role1")) {
return Arrays.asList((Permission) new MyPermission("+user+add+2"));
}
return null;
}
}
authorizer = org.apache.shiro.authz.ModularRealmAuthorizer
##配置RolePermissionResolver
rolePermissionResolver = heqianqian.shiro.role.MyRolePermissionResolver
authorizer.rolePermissionResolver = rolePermissionResolver
#配置自定义权限解析器
permissionResolver = heqianqian.shiro.permission.MyPermissionResolver
authorizer.permissionResolver = permissionResolver
securityManager.authorizer = $authorizer
public class MyStaticRealm extends AuthorizingRealm {
/**
* 用于授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("static Realm 中授权的方法");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("role1");
return info;
}
/**
* 用于认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 省略
}
}
currentSubject.checkPermission("+user+add+1");
PasswordService service = new DefaultPasswordService();
String str1 = service.encryptPassword("123456");
String str2 = service.encryptPassword("123456");
System.out.println(str1);
System.out.println(str2);
// 盐值是存放在加密以后的密码中的
boolean boolean1 = service.passwordsMatch("123456",str1);
System.out.println(boolean1);
boolean boolean2 = service.passwordsMatch("123456",str2);
System.out.println(boolean2)
自定义 Realm 的认证实现部分:
public class MyPasswordRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// default
String userName = (String) authenticationToken.getPrincipal();
//String passWordFromDB = "$shiro1$SHA-256$500000$onR5YN4/BqH6toWLn9atsg==$g31FX9UERzy36yBlruzIZqGwCLf8V9P5p3hBrZQOAY0=";
//md5+salt
String passWordFromDB = "c39d11270c99cb00a13a6ac1e54effa2";
String salt = "hqq";
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userName, passWordFromDB, getName());
info.setCredentialsSalt(ByteSource.Util.bytes(salt.getBytes()));
return info;
}
}
配置文件
# 声明一个 Shiro 已经有的密码匹配的类
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
# 声明自定义的 Realm 类
;myPasswordRealm= heqianqian.shiro.realm.MyPasswordRealm
# 将 passwordMatcher 注入到自定义的 Realm 类中
myPasswordRealm.credentialsMatcher=$passwordMatcher
# 将自定义的 Realm 注入到 securityManager 中
securityManager.realms=$myPasswordRealm
String originPassword = "123456";
String salt = "hello";
String md5 = new Md5Hash(originPassword,salt).toString();
System.out.println(md5);
public class MyPasswordRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = authenticationToken.getPrincipal().toString();
// String password = new String((char[]) authenticationToken.getCredentials());
// 此时我们应该从数据库中根据 username 查询出对应的密码
String passwordFromDB = "eeb9bad681184779aa6570e402d6ef6c";
String salt = "hello";
SimpleAuthenticationInfo info= new SimpleAuthenticationInfo(username,passwordFromDB,getName());
// 设置加密算法的盐值,使用 ByteSource 这个工具类
info.setCredentialsSalt(ByteSource.Util.bytes(salt.getBytes()));
return info;
}
}
配置文件
# 声明一个 Shiro 已经有的密码匹配的类
hashMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
hashMatcher.hashAlgorithmName=md5
hashMatcher.hashSalted=hello
# 声明自定义的 Realm 类
myPasswordRealm=com.liwei.realm.MyPasswordRealm
# 将 passwordMatcher 注入到自定义的 Realm 类中
myPasswordRealm.credentialsMatcher=$hashMatcher
# 将自定义的 Realm 注入到 securityManager 中
securityManager.realms=$myPasswordRealm
参考文章
http://blog.csdn.net/lw_power?viewmode=contents