一、Shiro简介
Shiro是Apache下的一个安全权限管理框架,对比与Spring Security,它更加的小巧轻便,对于简单的权限控制完全可以应付。
Shiro中各个模块的作用分别是:
- Authentication: 表示身份的认证,通常就是用户的登录过程。
- Authorization: 表示给相应的用户授权,判断当前用户是否具有做某件事的权限。
- Session Manager: 会话管理,当用户登录后再没有退出之前,所有的信息都会保存在这个session中,值得一提的是,这个session并非是JavaWeb中Servlet的session,而是Shiro自己提供的session,如此,该session在非Web环境也是可以使用的。
- Cryptography: 加密模块负责将数据加密存储到数据库,验证的时候用加密的密文对用户的身份进行认证。
- Web Support: Shiro可以非常容易地集成到Web项目中。
- Caching: 缓存使得用户登录后其基本信息不用再次通过查询得到,而是直接从自身的缓存获取,提高了验证的效率。
- Concurrency: 支持多线程的并发验证模式。
- Testing: 提供测试支持。
- Run As: 在得到用户的许可下,另一个用户能以这个用户的身份进行访问。
- Remember Me: 用户选择该项后,下次登录就不用验证直接得到身份的认证。
Shiro中一些实体术语的解释:
- Subject: 主体,代表当前需要得到身份认证的用户。
- SecurityManager: 安全管理器,负责所有的安全认证过程,是整个Shiro的核心。
- Realm: 数据域,表示Shiro从哪里获取数据来进行身份认证和授权。
- Authenticator: 认证器,主要负责对主体的认证工作。
- Authrizer: 授权器,判断用户是否具有某种权限。
二、一个最简单的身份认证过程
首先,我们需要自己创建一个maven的工程,引入必须的jar包:
junit
junit
4.9
org.apache.shiro
shiro-core
1.2.2
然后我们创建一个单元测试类来模拟登录过程中Shiro对用户的身份认证过程:
public class LoginTest {
private SimpleAccountRealm sar = new SimpleAccountRealm();
@Before
public void setUser(){
this.sar.addAccount("zhangsan", "123321");
}
@Test
public void testLogin(){
// 创建SecurityManager
DefaultSecurityManager dsm = new DefaultSecurityManager();
// 连接到realm
dsm.setRealm(sar);
SecurityUtils.setSecurityManager(dsm);
// 构造需要认证的主体及信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123321");
// 进行认证
subject.login(token);
System.out.println("是否认证成功?" + subject.isAuthenticated());
subject.logout();
System.out.println("是否认证成功?" + subject.isAuthenticated());
}
}
在如上的简单示例中,SimpleAccountRealm
就是存放着用户真正密码的数据源,将由DefaultSecurityManager
来对用户输入的密码进行验证,判断是否通过身份的认证。其执行的结果如下:
是否认证成功?true
是否认证成功?false
三、增加对用户角色的授权验证
在上面的例子中,我们只是对用户的身份进行了认证,然而一个用户可能拥有多种角色,不同用户也有着不同的角色,我们此时就需要对用户是否拥有某种角色来进行授权判断:
public class LoginAndCheckRoleTest {
private SimpleAccountRealm sar = new SimpleAccountRealm();
@Before
public void setUser(){
this.sar.addAccount("zhangsan", "123321", "admin", "manager");
}
@Test
public void testLogin(){
// 创建SecurityManager
DefaultSecurityManager dsm = new DefaultSecurityManager();
// 连接到realm
dsm.setRealm(sar);
SecurityUtils.setSecurityManager(dsm);
// 构造需要认证的主体及信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123321");
// 进行认证
subject.login(token);
System.out.println("是否认证成功?" + subject.isAuthenticated());
System.out.println("是否是Admin?" + subject.hasRole("admin"));
subject.checkRole("manager");
System.out.println("是否是Guest?" + subject.hasRole("guest"));
subject.logout();
}
}
在如上的例子中,我们的SimpleAccountRealm
中不但存储了用户的密码等身份信息,还存放了角色等授权信息,在用户登录成功后,用户自动就获取了这些角色的授权。其执行的结果如下:
是否认证成功?true
是否是Admin?true
是否是Guest?false
四、使用持久化的外部数据源进行认证和授权
上面都是直接在程序中指定的用户的身份信息和授权信息,在真实的开发中很少用到,更多的是从外部数据库或者缓存中读取。
4.1 使用ini配置文件的外部数据源
首先我们在ini配置文件中存放好用户的身份和授权信息:
[users]
zhangsan=zhangsan999,admin
wangwu=likesea999
[roles]
admin=user:delete
该配置文件有一定的书写格式,[users]代表的是用户的密码和角色,[roles]代表某种角色拥有的权限信息。
public class IniRealmTest {
@Test
public void testLogin() {
IniRealm ir = new IniRealm("classpath:shiro.ini");
// 创建SecurityManager
DefaultSecurityManager dsm = new DefaultSecurityManager();
// 连接到realm
dsm.setRealm(ir);
SecurityUtils.setSecurityManager(dsm);
// 构造需要认证的主体及信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"zhangsan999");
// 进行认证
subject.login(token);
System.out.println("是否认证成功?" + subject.isAuthenticated());
System.out.println("是否是Admin?" + subject.hasRole("admin"));
System.out.println("是否允许删除用户?" + subject.isPermitted("user:delete"));
System.out.println("是否允许增加用户?" + subject.isPermitted("user:add"));
subject.logout();
}
}
我们这次只是将原来的SimpleAccountRealm
改成了IniRealm
,并指定到具体的ini配置文件,其它的都没有修改。其执行结果如下:
是否认证成功?true
是否是Admin?true
是否允许删除用户?true
是否允许增加用户?false
4.2 使用Mysql的外部数据源
首先我们需要在mysql数据库中创建新的数据库shiro_test
,然后在其下创建三张表plu_user
、plu_role
、plu_permission
分别存放用户的密码、角色和权限,具体的SQL语句和数据插入语句这里就略过了:
public class JdbcRealmTest {
public static final String LOGIN_QUERY_SQL = "select password from plu_user where name = ?";
public static final String ROLE_QUERY_SQL = "select role from plu_role where name = ?";
public static final String PERMISSION_QUERY_SQL = "select permission from plu_permission where role = ?";
private static DruidDataSource ds;
@BeforeClass
public static void initDataSource(){
ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/shiro_test?serverTimezone=GMT");
ds.setUsername("root");
ds.setPassword("root");
}
@Test
public void testLogin() {
JdbcRealm jr = new JdbcRealm();
// 自定义指定查询用户登录、角色及权限的SQL语句
jr.setAuthenticationQuery(LOGIN_QUERY_SQL);
jr.setUserRolesQuery(ROLE_QUERY_SQL);
jr.setPermissionsQuery(PERMISSION_QUERY_SQL);
jr.setDataSource(ds);
// 默认不会开启权限查询,需要手动开启
jr.setPermissionsLookupEnabled(true);
// 创建SecurityManager
DefaultSecurityManager dsm = new DefaultSecurityManager();
// 连接到realm
dsm.setRealm(jr);
SecurityUtils.setSecurityManager(dsm);
// 构造需要认证的主体及信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"zhangsan999");
// 进行认证
subject.login(token);
System.out.println("是否认证成功?" + subject.isAuthenticated());
System.out.println("是否是Admin?" + subject.hasRole("admin"));
System.out.println("是否允许删除用户?" + subject.isPermitted("user:delete"));
System.out.println("是否允许增加用户?" + subject.isPermitted("user:add"));
subject.logout();
}
}
如上代码使用了JdbcRealm
,默认情况下,Shiro会自动去数据库的users
、user_roles
、roles_permissions
表中查找身份、角色和权限数据,如果你按照这些表名建立了数据库表并插入了相应的数据,那么就不用如上面代码中一样再指定SQL查询语句了,Shiro都帮你做好了。
在实际的开发过程中,我们的密码、角色和权限表都不太可能取这样的名字,所以就需要如示例中一样自定义查询的SQL语句,值得注意的是,Shiro默认是不开启权限的查询功能的,我们需要在代码中手动地打开。如上代码执行的结果为:
是否认证成功?true
是否是Admin?true
是否允许删除用户?true
是否允许增加用户?false
五、自定义Realm的使用
Shiro提供的几个realm虽然很好用,但是我们或许会有自己的认证和授权逻辑,那么就需要自定义一个realm:
public class MyRealm extends AuthorizingRealm {
// 用来模拟数据库中的用户和密码对
private Map userMap = new HashMap<>(16);
// 用来模拟缓存中的角色信息
private Set roleSet = new HashSet<>();
// 用来模拟缓存中的权限信息
private Set permissionSet = new HashSet<>();
// 普通类代码块,每次加载类的时候执行,早于类的构造函数执行
{
// 用户密码经过加盐加密后的值
userMap.put("jansen", "e8c5405c5a2d5ee3b7912e2e78c3e189");
roleSet.add("admin");
roleSet.add("manager");
permissionSet.add("user:delete");
permissionSet.add("user:update");
}
// 自定义授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
Set roles = this.getRolesByUserName(userName);
Set permissions = this.getPermissionsByUserName(userName);
SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
sai.setStringPermissions(permissions);
sai.setRoles(roles);
return sai;
}
// 自定义认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// 1:获取主体提交的认证信息中的用户名
String userName = (String) token.getPrincipal();
// 2:通过该用户名获取真实的密码
String password = this.getPasswordByUserName(userName);
if(password == null){
System.out.println("查无此人!");
return null;
}
SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(userName,password,"MyRealm");
// MD5加盐设置,此处盐为固定的字符串“shiro”
sai.setCredentialsSalt(ByteSource.Util.bytes("shiro"));
return sai;
}
// 模拟从数据库中获取用户的密码
private String getPasswordByUserName(String userName){
return userMap.get(userName);
}
// 模拟从缓存中获取角色信息
private Set getRolesByUserName(String userName){
return roleSet;
}
// 模拟从缓存或者数据库中获取权限信息
private Set getPermissionsByUserName(String userName){
return permissionSet;
}
}
然后我们就可以使用自定义的realm来进行测试了:
public class MyRealmTest {
@Test
public void testLogin() {
MyRealm mr = new MyRealm();
// 如果要将用户的密码加密的话
HashedCredentialsMatcher hcm = new HashedCredentialsMatcher();
hcm.setHashAlgorithmName("md5");
hcm.setHashIterations(1);
mr.setCredentialsMatcher(hcm);
// 创建SecurityManager
DefaultSecurityManager dsm = new DefaultSecurityManager();
// 连接到realm
dsm.setRealm(mr);
SecurityUtils.setSecurityManager(dsm);
// 构造需要认证的主体及信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("jansen","123321");
// 进行认证
subject.login(token);
System.out.println("是否认证成功?" + subject.isAuthenticated());
System.out.println("是否是Admin?" + subject.hasRole("admin"));
System.out.println("是否允许删除用户?" + subject.isPermitted("user:delete"));
System.out.println("是否允许增加用户?" + subject.isPermitted("user:add"));
subject.logout();
}
@Test
public void generateMd5Code(){
Md5Hash hashCode = new Md5Hash("123321","shiro");
System.out.println(hashCode);// e8c5405c5a2d5ee3b7912e2e78c3e189
}
}
我们此处在用户认证的时候加入了加盐MD5加密的逻辑,程序执行的结果为:
是否认证成功?true
是否是Admin?true
是否允许删除用户?true
是否允许增加用户?false
以上这些例子有助于你快速了解Shiro的简单使用,关于进一步和Spring的集成、Session使用等高级的主题将在以后逐步呈现。