1.简介
1.1什么是Shiro
Apache Shiro(日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,
提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。
Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。
Apache Shiro 的首要目标是易于使用和理解。安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。以下是你可以用 Apache Shiro 所做的事情:
验证用户来核实他们的身份
对用户执行访问控制,如:
判断用户是否被分配了一个确定的安全角色
判断用户是否被允许做某事
在任何环境下使用 Session API,即使没有 Web 或 EJB 容器。
在身份验证,访问控制期间或在会话的生命周期,对事件作出反应。
聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”。
启用单点登录(SSO)功能。
为没有关联到登录的用户启用"Remember Me"服务
…
以及更多——全部集成到紧密结合的易于使用的 API 中。
Shiro 视图在所有应用程序环境下实现这些目标——从最简单的命令行应用程序到最大的企业应用,不强制依赖其他第三方框架,容器,或应用服务器。当然,该项目的目标是尽可能地融入到这些环境,但它能够在任何环境下立即可用。
1.2Shiro整体架构图
- Subject:主体,可以看到主体可以是任何与应用交互的“用户”。
2. SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的FilterDispatcher。它是 Shiro 的核心,所有具体的交互都通过 SecurityManager 进行控制。它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
3. Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,我们可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
4. Authrizer:授权器,或者访问控制器。它用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能。
5. Realm:可以有1个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的。它可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等。
6. SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 需要有人去管理它的生命周期,这个组件就是 SessionManager。而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境。
7. SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD。我们可以自定义 SessionDAO 的实现,控制 session 存储的位置。如通过 JDBC 写到数据库或通过 jedis 写入 redis 中。另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能。
8. CacheManager:缓存管理器。它来管理如用户、角色、权限等的缓存的。因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。
9. Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的
2.测试代码
2.1.目录结构
2.2pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wo</groupId>
<artifactId>shiro_test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- shiro 安全框架的jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
2.3application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test4?useUnicode=true&characterEncoding=utf8&useSSL=false
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
3.简单的用户认证
3.1 shiro_first.ini
[users]
zhangsan=123
lisi=123
3.2TestShiro
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestShiro {
@Test
public void testFirstShiro(){
//使用IniSecurityManagerFactory加载shiro_first.ini文件
IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro_first.ini");
//使用工厂类创建securityManager
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
//使用shiro提供的工具类来创建manager的运行环境
SecurityUtils.setSecurityManager(securityManager);
//使用工具类获取我们的主体部分
Subject subject = SecurityUtils.getSubject();
//设置用户名及密码部分
//前端用户传输的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("lisi", "123");
//从主题中开始登录
try {
subject.login(token);
System.out.println("用户登录成功!!!!!!!!");
}catch (IncorrectCredentialsException inc){
System.out.println("用户登录失败!!!!!!!!!失败原因 密码错误!!!");
}catch (UnknownAccountException uae){
System.out.println("用户登录失败!!!!!!!!失败原因,没有该用户名");
}
if(subject.isAuthenticated()){
System.out.println("验证通过");
}else{
System.out.println("验证失败");
}
}
}
4.拥有权限的简单测试
4.1shiro_permission.ini
[users]
normal=123,role1
admin=admin,role2
root=root,role3
[roles]
role1=user:select
role2=user:select,user:update
role3=user:creat,user:delete,user:select,user:update
4.2TestShiroPermissions
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestShiroPermissions {
@Test
public void testUserPermissions(){
//使用IniSecurityManagerFactory加载shiro_first.ini文件
IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro_permission.ini");
//使用工厂类创建securityManager
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
//使用shiro提供的工具类来创建manager的运行环境
SecurityUtils.setSecurityManager(securityManager);
//使用工具类获取我们的主体部分
Subject subject = SecurityUtils.getSubject();
//设置用户名及密码部分
//前端用户传输的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("root", "root");
//从主题中开始登录
try {
subject.login(token);
System.out.println("用户登录成功!!!!!!!!");
}catch (IncorrectCredentialsException inc){
System.out.println("用户登录失败!!!!!!!!!失败原因 密码错误!!!");
}catch (UnknownAccountException uae){
System.out.println("用户登录失败!!!!!!!!失败原因,没有该用户名");
}
//判断用户的角色
String rol="role2";
boolean hasRole = subject.hasRole(rol);
if(hasRole){
System.out.println("用户拥有"+rol+"角色");
}else {
System.out.println("用户没有"+rol+"角色");
}
//判断用户权限
String per="user:delete";
boolean b = subject.isPermitted(per);
if (b){
System.out.println("用户拥有"+per+"权限");
}else {
System.out.println("用户没有"+per+"权限");
}
//批量验证该用户的角色
boolean[] hasRoles = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
System.out.println(Arrays.toString(hasRoles));
//批量验证该用户的权限
boolean[] permitted = subject.isPermitted("user:creat", "user:delete");
System.out.println(Arrays.toString(permitted));
//验证用户是否拥有输入的全部权限
boolean permittedAll = subject.isPermittedAll("user:creat", "user:delete");
System.out.println(permittedAll);
}
}
5.自定义安全策略的shiro应用案例:
5.1shiro_myrealm.ini
[main]
#自定义realm
myrealm=com.wo.shiro.MyRealm
#在shiro核心中,设置自定义myrealm
securityManager.realms=$myrealm
5.2MyRealm
public class MyRealm extends AuthorizingRealm {
//授权的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取到前端传输的用户名
String username = (String) principals.getPrimaryPrincipal();
//使用前端传输用户名5表联查,获取到用户的所拥有的权限信息
HashSet<String> objects=new HashSet<>();
objects.add("user:create");
objects.add("user:update");
//声明simpleAuthorizationInfo 对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将权限放置到声明simpleAuthorizationInfo 对象中
simpleAuthorizationInfo.setStringPermissions(objects);
//放置角色
simpleAuthorizationInfo.addRole("role1");
return simpleAuthorizationInfo;
}
//认证的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到前端传输的用户名
String username = (String) token.getPrincipal();
//使用前端用户名去数据库查询正确的密码
String password="123";
//将前端传输过来的用户名,以及使用该用户名 查询到的正确密码放置到查询出的正确密码放置到simpleAuthticationinfo中进行返回
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return simpleAuthenticationInfo;
}
}
5.3 TestShiroMyRealm
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestShiroMyRealm {
@Test
public void testMyRealm(){
//使用IniSecurityManagerFactory加载shiro_first.ini文件
IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro_myrealm.ini");
//使用工厂类创建securityManager
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
//使用shiro提供的工具类来创建manager的运行环境
SecurityUtils.setSecurityManager(securityManager);
//使用工具类获取我们的主体部分
Subject subject = SecurityUtils.getSubject();
//设置用户名及密码部分
//前端用户传输的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("root", "123");
//从主题中开始登录
try {
subject.login(token);
System.out.println("用户登录成功");
}catch (IncorrectCredentialsException ex){
System.out.println("用户登录失败,失败原因:密码错误");
}
boolean b = subject.hasRole("role2");
if (b){
System.out.println("用户拥有该角色");
}else {
System.out.println("用户没有该角色");
}
boolean permitted = subject.isPermitted("user:create");
if (permitted){
System.out.println("用户拥有该权限");
}else {
System.out.println("用户没有该权限");
}
}
}
6.自定义安全策略并凭证MD5加密的shiro应用案例
6.1shiro_myrealmMd5.ini
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#设置散列算法
credentialsMatcher.hashAlgorithmName=md5
#设置散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
myRealm=com.wo.shiro.MyRealmMD5
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm
6.2MyRealmMD5
public class MyRealmMD5 extends AuthorizingRealm{
//权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
//用username 去数据库获取加密后的密码 202cb962ac59075b964b07152d234b70
String password="202cb962ac59075b964b07152d234b70";
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return simpleAuthenticationInfo;
}
}
6.3TestShiroMd5MyRealm
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestShiroMd5MyRealm {
@Test
public void testMd5(){
//使用IniSecurityManagerFactory加载shiro_first.ini文件
IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro_myrealmMd5.ini");
//使用工厂类创建securityManager
SecurityManager securityManager = iniSecurityManagerFactory.createInstance();
//使用shiro提供的工具类来创建manager的运行环境
SecurityUtils.setSecurityManager(securityManager);
//使用工具类获取我们的主体部分
Subject subject = SecurityUtils.getSubject();
String username = "zhangsan";
String password = "123";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
System.out.println("用户登录成功");
}catch (IncorrectCredentialsException inc){
System.out.println("用户登录失败");
}
}
}