shiro
1、主流安全框架
shiro:Apache开源,简单易用,集成没有限制 https://shiro.apache.org/,
shiro学习资料:https://www.w3cschool.cn/shiro/andc1if0.html,https://www.bilibili.com/video/BV1uz4y197Zm?spm_id_from=333.999.0.0
Spring Security:Spring家族,只能集成在Spring家族相关框架中。
2、解决问题:权限管理
权限管理包括:认证和授权
认证:用户登录
授权:访问控制。进行身份认证后,不同身份的用户能访问的资源不同。
3、架构图
1)Subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证授权。
2)Security Manager:安全管理器,主体进行认证授权都是通过Security Manager进行的,Security Manager是一个包含了许多模块的容器。
3)Authentication:认证器,主体认证通过authentication进行。
4)Authorizer:授权器,主体授权通过authorizer进行。
5)Session Manager:web应用中一般是web容器(tomcat)对session进行管理,shiro中也提供session管理方案。
6)Session Dao:提供了session的crud操作。
7)Cache Manager:缓存控制器,来管理如用户、角色、权限的缓存,放到缓存中能提高访问性能。
8)Cryptography:提供了多种密码加密策略
9)Realms:域,相当于数据源,用于或获取安全实体的;可以是JDBC实现,也可以内存实现等。注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
4、shiro中的认证
shiro认证中的关键对象:
1)Subject:主体,访问系统的用户,主体可以是:用户、程序等,进行认证的都称为主体。
2)Principal:身份信息,是主体进行身份认证的标识,标识具有唯一性,一个主体可以有多个身份,但必须有一个主身份(Primary Principal)。
3)Credential:凭证信息,只有主体知道的安全信息,如密码、证书等。
4.1、身份认证流程
4.2、快速入门
1)导坐标
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
</dependency>
2)配置文件
预先将用户信息存入配置文件中(以后连接数据库),shiro配置文件比较特殊,后缀名为.ini
。
[users]
wang=123
zhangsan=1234
lisi=12345
3)编写代码
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象 Security Manager
DefaultSecurityManager securityManager =new DefaultSecurityManager();
//2.给安全管理器设置Realms, 读取准备好的用户信息
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 全局安全工具类 给全局的安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象,subject主体,
Subject subject = SecurityUtils.getSubject();
//5.创建令牌token,authentication
UsernamePasswordToken token = new UsernamePasswordToken("wang","123");
try {
System.out.println("认证之前状态:"+subject.isAuthenticated());
subject.login(token);//用户认证
System.out.println("认证之后状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e1){
e1.printStackTrace();
System.out.println("用户名不存在");
}
catch (IncorrectCredentialsException e2) {
e2.printStackTrace();
System.out.println("密码错误");
}
}
}
- 成功案例
- 用户名错误
- 密码错误
4)通过调试debug源码之后,发现在认证执行流程中:
-
最终执行用户名比较的是:SimpleAccountRealm类中的doGetAuthenticationInfo()方法
-
最终执行密码校验的是:AuthenticationRealm类中的assertCredentialsMatch()方法
5)总结
AuthenticatingRealm抽象类中认证realm的方法为doGetAuthenticationInfo()
AuthorizingRealm抽象类中授权realm的方法为doGetAuthorizationInfo()
SimpleAccountRealm实现类中有两个方法一个是认证一个是授权
4.3、自定义realm
自定义realm
/**
* 实现自定义Realm,将认证/授权来源转为数据库实现,实现AuthorizingRealm抽象类,并实现方法
*/
public class CustomerRealm extends AuthorizingRealm {
/**
* 授权方法
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证方法
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = token.getPrincipal().toString();
//根据用户名(principal)从数据库中查询
if ("wang".equals(principal)) {
//参数1:用户名 参数2:从数据库中查询密码 参数3:提供当前的realm的名字 this.getName()
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
使用自定义realm进行认证
/**
* 使用自定义Realm
*/
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
//创建security manager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//给安全工具类设置安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取主体 subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("wang", "1231");
try {
subject.login(token);
} catch (UnknownAccountException e0) {
e0.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}
}
}
4.4、自定义realm + md5 + salt + hash
public class TestMd5Salt {
public static void main(String[] args) {
Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toHex());
Md5Hash md5Hash1 = new Md5Hash("123", "abc");
System.out.println(md5Hash1.toHex());
Md5Hash md5Hash2 = new Md5Hash("123", "abc", 1024);
System.out.println(md5Hash2.toHex());
}
}
1)自定义realm + md5 + salt + hash
/**
* 使用自定义realm + md5 + salt + hash
*/
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = token.getPrincipal().toString();
//根据用户名查询数据库
//模拟
if ("wang".equals(principal)) {
//参数1:用户名 参数2:数据库md5+salt之后的密码 参数3:随机盐 参数4:realm的名
return new SimpleAuthenticationInfo(principal, "894b3913a4a13b25dc6186d11835c209",
ByteSource.Util.bytes("abc"), this.getName());
}
return null;
}
}
2)使用自定义realm + md5 + salt + hash进行认证
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入自定义realm
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);//注入
//将安全管理器注入安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取 主体subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e1) {
e1.printStackTrace();
System.out.println("密码错误");
}
}
}
5、shiro授权
5.1、授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。
5.2、关键对象
在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
-
Subject 主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
-
Resource 资源,在应用中用户可以访问的URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
-
Permission 权限/许可,规定了主体对资源的操作许可,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源。
5.3、授权方式
- 基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.hasRole("admin")){
//操作什么资源
}
- 基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问
if(subject.isPermission("user:update:01")){
//对01用户进行修改
}
5.4、权限字符串
权限字符串的规则:资源标识符:操作:资源实例表示符表示为对哪个资源的哪个实例具有什么操作,”:“是资源/操作/实例的分隔符,权限字符串也可以使用 * 通配符。
5.5、shiro中授权的实现方式
- 编程式
Subject subject=SecurityUtils.getSubject();
if(Subject.hasRole("admin")){
//有权限
}else{
//无权限
}
- 注解式
@RequiresRoles("admin")
public void hello(){
//有权限
}
- 标签式
JSP标签
<shiro:hasRole name="admin">
<!--有权限-->
<shiro:hasRole>
Thymeleaf中使用shiro需要额外集成
5.6、授权实现
自定义realm
/**
* 使用自定义realm + md5 + salt + hash
*/
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取主身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.print("身份信息:" + primaryPrincipal+" ");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询的角色信息赋值给权限对象, 模拟
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRoles(Arrays.asList("user", "test"));
//将数据库中查询的权限信息赋值给权限对象,模拟
simpleAuthorizationInfo.addStringPermission("user:*:*");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = token.getPrincipal().toString();
//根据用户名查询数据库
//模拟
if ("wang".equals(principal)) {
//参数1:用户名 参数2:数据库md5+salt之后的密码 参数3:随机盐 参数4:realm的名
return new SimpleAuthenticationInfo(principal, "894b3913a4a13b25dc6186d11835c209",
ByteSource.Util.bytes("abc"), this.getName());
}
return null;
}
}
测试认证授权
public class TestCustomerMd5RealmAuthenticator {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//注入自定义realm
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数
hashedCredentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);//注入
//将安全管理器注入安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取 主体subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e1) {
e1.printStackTrace();
System.out.println("密码错误");
}
//授权
if (subject.isAuthenticated()) {
//基于角色的权限控制
System.out.println(subject.hasRole("admin"));
System.out.println("-----------");
//是否具有其中一个角色
System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("admin", "student"))));
System.out.println("-----------");
//基于多角色的权限控制,同时需要具有多个角色
System.out.println(subject.hasAllRoles(Arrays.asList("user", "test")));
System.out.println("-----------");
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "student")));
System.out.println("-----------");
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println(subject.isPermitted("user:*:01"));
System.out.println(subject.isPermitted("user:update:02"));
System.out.println(subject.isPermitted("goods:update:01"));
System.out.println("-----------");
//分别具有哪些权限
System.out.println(Arrays.toString(subject.isPermitted("user:*;01", "user:insert:*", "goods:delete:*")));
System.out.println("-----------");
//同时具有哪些权限
System.out.println(subject.isPermittedAll("user:*:12", "goods:*:*"));
}
}
}