参考:
https://jinnianshilongnian.iteye.com/blog/2018936
http://how2j.cn/k/shiro/shiro-tutorial/1720.html#nowhere
RBAC 是当下权限系统的设计基础,同时有两种解释:
1.: Role-Based Access Control,基于角色的访问控制
即,你要能够删除产品,那么当前用户就必须拥有产品经理这个角色
2.:Resource-Based Access Control,基于资源的访问控制
即,你要能够删除产品,那么当前用户就必须拥有删除产品这样的权限
开源权限管理项目:
Spring Security
Apache Shiro
Apache Shiro是Java的一个安全框架,基本功能点如图:
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
以用户登录为例子,
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
一.接下来入门一个通过ini的简单登录的例子:
1.使用idea创建一个普通的java项目
新手可以参照:https://blog.youkuaiyun.com/oschina_41790905/article/details/79475187
2.引入两个jar包
新手关于jar包引入可以参考https://blog.youkuaiyun.com/qq_28289405/article/details/82216847
3.在src目录下新建 shiro.ini
用户数据 此处充当realm域
#定义用户
[users]
#用户名 zhang3 密码是 12345
zhang3 = 12345
#用户名 li4 密码是 abcde
li4 = abcde
3. 在src目录下创建 包结构,创建User.class
public class User {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4. 创建 TestShiro
准备3个用户,前两个能在 shiro.ini 中找到,第3个不存在
然后测试登录
public class TestShiro {
public static void main(String[] args) {
User zhang3 = new User();
zhang3.setName("zhang3");
zhang3.setPassword("12345");
User li4 = new User();
li4.setName("li4");
li4.setPassword("abcde");
User wang5 = new User();
wang5.setName("wang5");
wang5.setPassword("wrongpassword");
List<User> users = new ArrayList<>();
users.add(zhang3);
users.add(li4);
users.add(wang5);
//登陆每个用户
for (User user : users) {
if(login(user))
System.out.printf("%s \t成功登陆,用的密码是 %s\t %n",user.getName(),user.getPassword());
else
System.out.printf("%s \t成功失败,用的密码是 %s\t %n",user.getName(),user.getPassword());
}
}
private static boolean login(User user) {
Subject subject = getSubject(user);
//如果已经登录过了,退出
if (subject.isAuthenticated())
subject.logout();
//封装用户的数据
//token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
try {
//将用户的数据token 最终传递到Realm中进行对比
subject.login(token);
} catch (AuthenticationException e) {
//验证错误
return false;
}
return subject.isAuthenticated();
}
private static Subject getSubject(User user) {
//加载配置文件,并获取工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取安全管理者实例
SecurityManager sm = factory.getInstance();
//将安全管理者放入全局对象,注入SecurityManager
SecurityUtils.setSecurityManager(sm);
//全局对象通过安全管理者生成Subject对象
Subject subject = SecurityUtils.getSubject();
return subject;
}
}
5. 运行查看结果
二、通过数据库充当realm域
1.创建表
前面提到过,RBAC 的概念, RBAC会存在3 张基础表: 用户,角色,权限, 以及 2 张中间表来建立 用户与角色的多对多关系,角色与权限的多对多关系。 用户与权限之间也是多对多关系,但是是通过 角色间接建立的。
2. 添加jar包
简单介绍一下几个jar包:
commons-beanutils
是Apache开源组织提供的用于操作JAVA BEAN的工具包。使用commons-beanutils
,我们可以很方便的对bean对象的属性进行操作。参考:https://blog.youkuaiyun.com/jianggujin/article/details/51104949
slf4j:Simple Logging Facade for Java,为java提供的简单日志Facade。Facade门面,更底层一点说就是接口。它允许用户以自己的喜好,在工程中通过slf4j接入不同的日志系统。 参考:https://www.cnblogs.com/lujiango/p/8573411.html
因此slf4j入口就是众多接口的集合,它不负责具体的日志实现,只在编译时负责寻找合适的日志系统进行绑定。具体有哪些接口,全部都定义在slf4j-api中。它只提供一个核心slf4j api(就是slf4j-api.jar包),这个包只有日志的接口,并没有实现,所以如果要使用就得再给它提供一个实现了些接口的日志包,比 如:log4j,common logging,jdk log日志实现包等。
Jakarta Commons-logging(JCL)是apache最早提供的日志的门面接口。提供简单的日志实现以及日志解耦功能。 ---------------------------------------------------------------------------https://www.cnblogs.com/xiadongqing/p/5208824.htm
mysql-connector-java-5.0.8-bin JDBC驱动
3.
public class DatabaseRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//能进入到这里,表示账号已经通过验证了
String userName =(String) principalCollection.getPrimaryPrincipal();
//通过DAO获取角色和权限
Set<String> permissions = new DAO().listPermissions(userName);
Set<String> roles = new DAO().listRoles(userName);
//授权对象
SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
//把通过DAO获取到的角色和权限放进去
s.setStringPermissions(permissions);
s.setRoles(roles);
return s;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取账号密码
UsernamePasswordToken t = (UsernamePasswordToken) token;
String userName= token.getPrincipal().toString();
String password= new String( t.getPassword());
//获取数据库中的密码
String passwordInDB = new DAO().getPassword(userName);
//如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
if(null==passwordInDB || !passwordInDB.equals(password))
throw new AuthenticationException();
//认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());
return a;
}
}
4.修改ini文件
[main]
databaseRealm=com.jxw.DatabaseRealm
securityManager.realms=$databaseRealm
5. 运行结果与之前相同
Maven项目入门
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。
另外两个相关的概念是之前提到的Subject及Realm,分别是主体及验证主体的数据源。
基本的身份认证
1. 通过idea创建一个maven项目
项目结构如图
2. 在pom.xml中添加如下的代码,引入相关需要的jar包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
3.创建shiro.ini 用户身份/凭据
通过[users]指定了两个主体:zhang/123、wang/123。
即zhang为用户名 123位密码
[users]
zhang=123
wang=123
4.编写测试用例 LoginTest.java
public class LoginTest {
@Test
public void test1(){
// 1、首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2、接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 3、通过SecurityUtils得到Subject,其会自动绑定到当前线程;如果在web环境在请求结束时需要解除绑定;然后获取身份验证的Token,如用户名/密码;
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "1");
// 4、调用subject.login方法进行登录,其会自动委托给SecurityManager.login方法进行登录;
// 5、如果身份验证失败请捕获AuthenticationException或其子类,常见的如: DisabledAccountException(禁用的帐号)、
// LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、
// ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、
// ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;
// 对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,
// 防止一些恶意用户非法扫描帐号库;
try{subject.login(token);}catch (AuthenticationException e){
System.out.print("登录失败");
}
Assert.assertEquals(true,subject.isAuthenticated());
System.out.print("登录成功");
// 6、最后可以调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出
subject.logout();
}
}