1.Apache Shiro
Apache Shiro Reference Documentation | Apache Shiro
Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:
· 易于理解的 Java Security API;
· 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
· 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
· 支持一级缓存,以提升应用程序的性能;
· 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
· 异构客户端会话访问;
· 非常简单的加密 API;
· 不跟任何的框架或者容器捆绑,可以独立运行。
推荐shiro的参考手册介绍 - 《Apache Shiro 1.2.x 参考手册》 - 书栈网 · BookStack
2.结构
- Subject (org.apache.shiro.subject.Subject)
正在与软件交互的一个特定的实体“view”(用户、第三方服务、时钟守护任务等)。
- SecurityManager (org.apache.shiro.mgt.SecurityManager)
如同上面提到的,SecurityManager 是 Shiro 的核心,它基本上就是一把“保护伞”用来协调它管理的组件使之平稳地一起工作,它也管理着 Shiro 中每一个程序用户的视图,所以它知道每个用户如何执行安全操作。
- Authenticator(org.apache.shiro.authc.Authenticator)
Authenticator 是一个组件,负责执行和反馈用户的认证(登录),如果一个用户尝试登录,Authenticator 就开始执行。Authenticator 知道如何协调一个或多个保存有相关用户/帐号信息的 Realm,从这些 Realm中获取这些数据来验证用户的身份以确保用户确实是其表述的那个人。
- Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy)
如果配置了多个 Realm,AuthenticationStrategy 将会协调 Realm 确定在一个身份验证成功或失败的条件(例如,如果在一个方面验证成功了但其他失败了,这次尝试是成功的吗?是不是需要所有方面的验证都成功?还是只需要第一个?)
- Authorizer(org.apache.shiro.authz.Authorizer)
Authorizer 是负责程序中用户访问控制的组件,它是最终判断一个用户是否允许做某件事的途径,像 Authenticator 一样,Authorizer 也知道如何通过协调多种后台数据源来访问角色和权限信息,Authorizer 利用这些信息来准确判断一个用户是否可以执行给定的动作。
- SessionManager(org.apache.shiro.session.mgt.SessionManager)
SessionManager 知道如何创建并管理用户 Session 生命周期而在所有环境中为用户提供一个强有力的 Session 体验。这在安全框架领域是独一无二—Shiro 具备管理在任何环境下管理用户 Session 的能力,即使没有 Web/Servlet 或者 EJB 容器。默认情况下,Shiro 将使用现有的session(如Servlet Container),但如果环境中没有,比如在一个独立的程序或非 web 环境中,它将使用它自己建立的 session 提供相同的作用,sessionDAO 用来使用任何数据源使 session 持久化。
- SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)
SessionDAO 代表 SessionManager 执行 Session 持久(CRUD)动作,它允许任何存储的数据挂接到 session 管理基础上。
- CacheManager(org.apache.shiro.cache.CacheManager)
CacheManager 为 Shiro 的其他组件提供创建缓存实例和管理缓存生命周期的功能。因为 Shiro 的认证、授权、会话管理支持多种数据源,所以访问数据源时,使用缓存来提高访问效率是上乘的选择。当下主流开源或企业级缓存框架都可以继承到 Shiro 中,来获取更快更高效的用户体验。
- Cryptography (org.apache.shiro.crypto.*)
Cryptography 在安全框架中是一个自然的附加产物,Shiro 的 crypto 包包含了易用且易懂的加密方式,Hashes(即digests)和不同的编码实现。该包里所有的类都亦于理解和使用,曾经用过 Java 自身的加密支持的人都知道那是一个具有挑战性的工作,而 Shiro 的加密 API 简化了 java 复杂的工作方式,将加密变得易用。
- Realms (org.apache.shiro.realm.Realm)
如同上面提到的,Realm 是 shiro 和你的应用程序安全数据之间的“桥”或“连接”,当实际要与安全相关的数据进行交互如用户执行身份认证(登录)和授权验证(访问控制)时,shiro 从程序配置的一个或多个Realm 中查找这些数据,你需要配置多少个 Realm 便可配置多少个 Realm(通常一个数据源一个),shiro 将会在认证和授权中协调它们。
2.依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
3.认证案列
3-1利用配置进行登录
1.案列需要依赖.test文件夹全部删掉,我没有用spring的test依赖 ,启动代码会报错
<?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 https://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.5.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- compiler插件, 设定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>8</source>
<target>8</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.在 resources 新建一个shiro.ini文件
#声明用户账号
[users]
jay=123
3.编写测试代码
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/**
* @Description:shiro的第一个例子
*/
public class HelloShiro {
@Test
public void shiroLogin() {
//导入权限ini文件构建权限工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securityManager = factory.getInstance();
//使用SecurityUtils工具生效安全管理器
SecurityUtils.setSecurityManager(securityManager);
//使用SecurityUtils工具获得主体
Subject subject = SecurityUtils.getSubject();
//构建账号token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
//登录操作
subject.login(usernamePasswordToken);
System.out.println("是否登录成功:" + subject.isAuthenticated());
}
}
4.运行结果
3-2模拟数据库的登录基于realm
1.新建service包
这里我只是为了模拟,所以不需要新导入依赖 还是3-1中的依赖 ,我们这里的service包只是为了模拟操作,不需要加什么注解 来交由spring来管理我们就是简单 测试 创建一个根据账号获取密码的接口就行了
package com.example.shiro.service;
/**
* 模拟数据库操作服务接口
*/
public interface SecurityService {
/**
* @Description 查找密码按用户登录名
* @param loginName 登录名称
* @return 密码
*/
String findPasswordByLoginName(String loginName);
}
package com.example.shiro.service.impi;
import com.example.shiro.service.SecurityService;
public class SecurityServiceImpl implements SecurityService {
@Override
public String findPasswordByLoginName(String loginName) {
return "123";
}
}
2.新建realm包
这个包主要存放我们权限一些类 ,在这个包新建 DefinitionRealm
package com.example.shiro.realm;
import com.example.shiro.service.SecurityService;
import com.example.shiro.service.impi.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class DefinitionRealm extends AuthorizingRealm {
/**
* @param authenticationToken 认证方法
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取登录名
String loginName = (String) authenticationToken.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
final String password = securityService.findPasswordByLoginName(loginName); //通过模拟的数据获取密码
/**
* 如果查询出来的密码是空的或者是null 就代表这个 账号不存在
*/
if ("".equals(password) || password == null) {
throw new UnknownAccountException("账户不存在");
}
//传递账号和密码
return new SimpleAuthenticationInfo(loginName,password,getName());
}
/**
* @param principalCollection 鉴权方法
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
3.修改shiro.ini文件
注意自己的包名
#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.example.shiro.realm.DefinitionRealm
securityManager.realms=$definitionRealm
4.运行结果
3-3 Realm使用散列算法(密码加密登录)
Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作。
Shiro内部的一些数据的【存储/表示】,都使用了base64和16进制字符串
包结构如下 ,个别类不需要要 测试的密码还是写死的,只是模拟操作。
1.建立一个生成加密的工具类
在tool包建一个
package com.example.shiro.tools;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 生成摘要
*/
public class DigestsUtil {
public static final String SHA1 = "SHA-1"; //加密的算法
public static final Integer ITERATIONS = 512; //加密的次数
/**
* 散列盐加密
*
* @param input 输入参数
* @param salt 盐
* @return 加密的数据
*/
public static String sha1(String input, String salt) {
return new SimpleHash(SHA1, input, salt, ITERATIONS).toString();
}
/**
* 随机生成salt
*
* @return hex编码的salt
*/
public static String generateSalt() {
final SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
/**
* @param passwordPlain 密文密码
* @return map-> salt 和密文密码
* @Description
*/
public static Map<String, String> entryPassword(String passwordPlain) {
Map<String, String> map = new HashMap<>();
String salt = generateSalt();
String password = sha1(passwordPlain, salt);
map.put("salt", salt);
map.put("password", password);
return map;
}
}
编码解密的工具类(可以不用)
package com.example.shiro.tools;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;
/**
* @Description:封装base64和16进制编码解码工具类
*/
public class EncodesUtil {
/**
* @Description HEX-byte[]--String转换
* @param input 输入数组
* @return String
*/
public static String encodeHex(byte[] input){
return Hex.encodeToString(input);
}
/**
* @Description HEX-String--byte[]转换
* @param input 输入字符串
* @return byte数组
*/
public static byte[] decodeHex(String input){
return Hex.decode(input);
}
/**
* @Description Base64-byte[]--String转换
* @param input 输入数组
* @return String
*/
public static String encodeBase64(byte[] input){
return Base64.encodeToString(input);
}
/**
* @Description Base64-String--byte[]转换
* @param input 输入字符串
* @return byte数组
*/
public static byte[] decodeBase64(String input){
return Base64.decode(input);
}
}
2.编写业务层代码(这里只是模拟操作,不是真正的service层)
接口
package com.example.shiro.service;
import java.util.Map;
/**
* 模拟数据库操作服务接口
*/
public interface SecurityService {
/**
* @Description 查找密码按用户登录名
* @param loginName 登录名称
* @return 密码
*/
Map<String,String> findPasswordByLoginName(String loginName);
}
实现接口
package com.example.shiro.service.impl;
import com.example.shiro.service.SecurityService;
import com.example.shiro.tools.DigestsUtil;
import java.util.Map;
public class SecurityServiceImpl implements SecurityService {
@Override
public Map<String, String> findPasswordByLoginName(String loginName) {
return DigestsUtil.entryPassword("123");
}
}
3. 在realm包下设置认证规则
package com.example.shiro.realm;
import com.example.shiro.service.SecurityService;
import com.example.shiro.service.impl.SecurityServiceImpl;
import com.example.shiro.tools.DigestsUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.Map;
public class DefinitionRealm extends AuthorizingRealm {
public DefinitionRealm(){
//指定密码匹配方式为sha1
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
//指定密码迭代次数
matcher.setHashIterations(DigestsUtil.ITERATIONS);
//使用父亲方法使匹配方式生效
setCredentialsMatcher(matcher);
}
/**
* @param authenticationToken 认证方法
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取登录名
String loginName = (String) authenticationToken.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
final Map<String, String> map = securityService.findPasswordByLoginName(loginName);//通过模拟的数据获取密码
if(map.isEmpty()){
throw new UnknownAccountException("账号不存在");
}
final String salt = map.get("salt");
final String password = map.get("password");
return new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt), getName());
}
/**
* @param principalCollection 鉴权方法
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
4.测试
别把3-2中的shiro.ini文件删了 还要用
package com.example.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/**
* @Description:shiro的第一个例子
*/
public class HelloShiro {
@Test
public void shiroLogin() {
//导入权限ini文件构建权限工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securityManager = factory.getInstance();
//使用SecurityUtils工具生效安全管理器
SecurityUtils.setSecurityManager(securityManager);
//使用SecurityUtils工具获得主体
Subject subject = SecurityUtils.getSubject();
//构建账号token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
//登录操作
subject.login(usernamePasswordToken);
System.out.println("是否登录成功:" + subject.isAuthenticated());
}
}
5.运行结果
4.认证+鉴权案列
简单就是不同的角色用户只能访问指定的信息 ,我们需要知道用户有那些角色,用户有那些权限 ,包结构和之前的一致3-3
1.新增两个查询角色和权限的接口,在SecurityService接口
/**
* @Description 查找角色按用户登录名
* @param loginName 登录名称
* @return
*/
List<String> findRoleByLoginName(String loginName);
/**
* @Description 查找资源按用户登录名
* @param loginName 登录名称
* @return
*/
List<String> findPermissionByLoginName(String loginName);
2.实现
@Override
public List<String> findRoleByLoginName(String loginName) {
List<String> list = new ArrayList<>();
list.add("admin");
list.add("dev");
return list;
}
@Override
public List<String> findPermissionByLoginName(String loginName) {
List<String> list = new ArrayList<>();
list.add("order:add");
list.add("order:list");
list.add("order:del");
return list;
}
3.添加鉴权方法
/**
* @param principals 鉴权方法
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//拿到用户认证凭证信息
String loginName = (String) principals.getPrimaryPrincipal();
//从数据库中查询对应的角色和资源
SecurityService securityService = new SecurityServiceImpl();
List<String> roles = securityService.findRoleByLoginName(loginName);
List<String> permissions = securityService.findPermissionByLoginName(loginName);
//构建资源校验
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roles);
authorizationInfo.addStringPermissions(permissions);
return authorizationInfo;
}
4.测试
package com.example.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/**
* @Description:shiro的第一个例子
*/
public class HelloShiro {
@Test
public void testPermissionRealm() {
Subject subject = shiroLogin("jay", "123");
//判断用户是否已经登录
System.out.println("是否登录成功:" + subject.isAuthenticated());
//---------检查当前用户的角色信息------------
System.out.println("是否有管理员角色:"+subject.hasRole("admin"));
//---------如果当前用户有此角色,无返回值。若没有此权限,则抛 UnauthorizedException------------
try {
subject.checkRole("coder");
System.out.println("有coder角色");
}catch (Exception e){
System.out.println("没有coder角色");
}
//---------检查当前用户的权限信息------------
System.out.println("是否有查看订单列表资源:"+subject.isPermitted("order:list"));
//---------如果当前用户有此权限,无返回值。若没有此权限,则抛 UnauthorizedException------------
try {
subject.checkPermissions("order:add", "order:del");
System.out.println("有添加和删除订单资源");
}catch (Exception e){
System.out.println("没有有添加和删除订单资源");
}
}
/**
* @Description 登录方法
*/
private Subject shiroLogin(String loginName,String password) {
//导入权限ini文件构建权限工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securityManager = factory.getInstance();
//使用SecurityUtils工具生效安全管理器
SecurityUtils.setSecurityManager(securityManager);
//使用SecurityUtils工具获得主体
Subject subject = SecurityUtils.getSubject();
//构建账号token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password);
//登录操作
subject.login(usernamePasswordToken);
return subject;
}
}
5.运行结果