单词概念
Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。
原生 shiro 基本使用 - 认证
helloworld
导入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
编写shiro配置文件
shiro.ini
编写权限数据
[users]
zhangsan=123
zhaoxingyu=123456
认证测试
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.使用SecurityUtils 给全局工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhaoxingyu", "123456");
//6.用户认证
try {
//认证成功 subject.isAuthenticated()
System.out.println("认证状态:" + subject.isAuthenticated());
//验证令牌
subject.login(token);
System.out.println("认证状态:" + subject.isAuthenticated());
} catch (UnknownAccountException e) { //认证用户不存在,抛出UnknownAccountException异常
System.out.println("用户名不存在:");
} catch (IncorrectCredentialsException e) {//密码不正确,抛出IncorrectCredentialsException异常
System.out.println("密码错误。。。");
} catch (Exception e){
e.printStackTrace();
System.out.println("未知异常");
}
}
}
自定义 Realm
- 默认使用的 org.apache.shiro.realm.SimpleAccountRealm 继承的 AuthorizingRealm
- 自定义的要继承AuthorizingRealm
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取token用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
//根据信息 , 可以去真实数据库查询
if("zhaoxingyu".equals(principal)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123456",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
- 使用自定义的 认证授权
//设置自定义的Realm ,其他都一样
securityManager.setRealm(new CustomerRealm());
MD5 使用
使用这个 md5+ 盐值 + hash散列 规则解锁时,也需要使用这个规则
//md5 + 盐值 + hash散列次数
Md5Hash md5Hash = new Md5Hash("123456","Xz#p",1024);
System.out.println(md5Hash.toHex());
自定义 Realm + md5
使用md5 + 盐值 + 松散hash
//使用 md5 + 盐值 + hash散列 进行加密
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if("zhaoxingyu".equals(principal)){
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,
"64845a31af89f20715438ea1793a4246", //数据库密码
ByteSource.Util.bytes("Xz#p"), //盐值
this.getName());
return info;
}
return null;
}
public class TestCustomerMD5Realm {
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//注入的realm
CustomerMD5Realm realm = new CustomerMD5Realm();
//设置加密凭证
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//使用md5方式加密
credentialsMatcher.setHashAlgorithmName("md5");
//松散次数
credentialsMatcher.setHashIterations(1024);
//设置进 CustomerMD5Realm 需要的加密规则
realm.setCredentialsMatcher(credentialsMatcher);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhaoxingyu","123456");
try {
subject.login(token);
System.out.println("任证状态:"+subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.err.println("==用户名不存在==");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.err.println("==密码错误==");
} catch (Exception e) {
e.printStackTrace();
System.err.println("==未知异常==");
}
}
}
原生 shiro 基本使用 - 授权
满足认证 (subject.isAuthenticated()==true) 后,进行授权
System.out.println("========== 角色 ===========");
//具有那个角色
System.out.println("hasRole:" + subject.hasRole("user")); //true
//同时具有哪些角色,全部满足
System.out.println("hasAllRoles:" + subject.hasAllRoles(Arrays.asList("user", "admin")));//true
//返回boolean数组 有哪个角色 那个就是true
for (boolean b : subject.hasRoles(Arrays.asList("admin", "super"))) {
System.out.println("hasRoles:" + b); //true , false
}
System.out.println("========== 权限 ===========");
//基于权限字符串的访问控制 资源标识:操作:资源类型 (:分隔符)
//具有的权限
System.out.println("isPermitted:"+subject.isPermitted("user:update:01")); //true
//分别具有的权限
for (boolean b : subject.isPermitted("user:*", "order:*")) { //true false
System.out.println("isPermitted:"+b);
}
//同时具有哪些权限
System.out.println("isPermittedAll:"+subject.isPermittedAll("user:*", " product:*")); //true
使用自定义的继承
AuthorizingRealm
类,授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
//就是登陆时候的用户名
System.out.println("身份信息:"+primaryPrincipal);
//为我当前登录用户进行的授权
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中的查新角色复制给权限对象
simpleAuthorizationInfo.addRoles(Arrays.asList("admin","user"));
//将数据库中的查新权限复制给权限对象 //user,product 下所有资源都有权限
simpleAuthorizationInfo.addStringPermissions(Arrays.asList("user:*","product:*"));
return simpleAuthorizationInfo;
}
shiro整合springboot – jsp
- 使用jsp模板
###pom
<!-- 整合的shiro的 starter -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
配置类
1.创建 shiroFilter 负责拦截所有请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
System.out.println("拦截了请求");
//配置访问资源受限规则
Map<String, String> maps = new HashMap<>();
maps.put("/index.jsp", "authc"); //访问/index.jsp 要有authc权限
//将 /login.jsp 设置为拦截请求跳转页面 默认就是 /login.jsp
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
return shiroFilterFactoryBean;
}
2.创建安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("getRealm") Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
3.创建自定义realm 加入spring 类要继承AuthorizingRealm
@Bean
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法位md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
其他内置过滤器缩写
配置缩写 | 对应的过滤器 | 功能 |
---|---|---|
身份验证相关的 | ||
anon | AnonymousFilter | 指定url可以匿名访问 |
authc | FormAuthenticationFilter | 基于表单的拦:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure) |
authcBasic | BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application) |
logout | authc.LogoutFilter | 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/) |
user | UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可 |
授权相关的 | ||
roles | RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]” |
perms | PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms[“user:create”]” |
port | PortFilter | 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样 |
rest | HttpMethodPermissionFilter | rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll) |
ssl | SslFilter | SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样 |
noSessionCreation | NoSessionCreationAuthorizationFilter | 需要指定权限才能访问 |
CustomerRealm 授权 ,登录成功就有 user:* , order:save 权限
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//给予权限
simpleAuthorizationInfo.addStringPermissions((Arrays.asList("user:*","order:save")));
return simpleAuthorizationInfo;
}
###jsp
shrio整个jsp标签 shiro:hasPermission :是否哟这个权限字符串 shiro:hasRole这个用户 是否有这个角色
<shiro:hasPermission name="user:*">
<li><a href="">用户管理</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:*">
<li><a href="">订单管理</a></li>
</shiro:hasPermission>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
Controller
@Controller
@RequestMapping("order")
public class OrderController {
@GetMapping("/list") //有order:list 权限就可以访问 否则抛出异常 :UnauthorizedException
@RequiresPermissions(value = {"order:list"})
@ResponseBody
public String list(){
return "访问order list...";
}
@GetMapping("/save")
@RequiresPermissions(value = {"order:save"})
@ResponseBody
public String save() {
return "访问order save...";
}
}
更多注解
-
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。
-
@RequiresUser
验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的(subject.isRemembered()结果为true)。
-
@RequiresGuest
验证是否是一个guest的请求,与@RequiresUser完全相反。
换言之,RequiresUser == !RequiresGuest。
此时subject.getPrincipal() 结果为null.
-
@RequiresRoles
例如:@RequiresRoles(“aRoleName”);
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException
-
@RequiresPermissions
例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} )
void someMethod();要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException
权限基本sql
/*
Navicat Premium Data Transfer
Source Server : localhost_mysql_3306
Source Server Type : MySQL
Source Server Version : 50731
Source Host : localhost:3306
Source Schema : shiro
Target Server Type : MySQL
Target Server Version : 50731
File Encoding : 65001
Date: 23/08/2020 18:27:15
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_pers
-- ----------------------------
DROP TABLE IF EXISTS `tb_pers`;
CREATE TABLE `tb_pers` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tb_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tb_role_pers
-- ----------------------------
DROP TABLE IF EXISTS `tb_role_pers`;
CREATE TABLE `tb_role_pers` (
`id` int(6) NOT NULL,
`roleid` int(6) NULL DEFAULT NULL,
`persid` int(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`salt` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tb_user_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_user_role`;
CREATE TABLE `tb_user_role` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`userid` int(6) NULL DEFAULT NULL,
`roleid` int(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
加入ehCache
pom
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
配置中开启缓存管理
@Bean
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法位md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
//================================缓存设置begin======================================
//开启缓存管理
customerRealm.setCacheManager(new EhCacheManager());
//开启全局缓存
customerRealm.setCachingEnabled(true);
//认证缓存
customerRealm.setAuthenticationCachingEnabled(true);
//认证缓存名字
customerRealm.setAuthenticationCacheName("authenticationCache");
//开启授权缓存
customerRealm.setAuthorizationCachingEnabled(true);
//授权缓存名字
customerRealm.setAuthorizationCacheName("authorizationCache");
//================================缓存设置end======================================
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}