2.Shiro权限框架
2.1 概念
2.2 Apache Shiro 与Spring Security区别
3.Shiro认证
3.1 基于ini认证
3.2 自定义Realm --认证
4.Shiro授权
4.1 基于ini授权
4.2 自定义realm – 授权
5.项目集成shiro 认证-授权注意点
5.1 认证
5.2 授权
5.3 注解@RequiresPermissions()
5.4 标签式权限验证
权限管理概述
RBAC(Role Based Access Control) :某个用户拥有什么角色,被允许做什么事情(权限)
用户登录—>分配角色---->(权限关联映射)---->鉴权(拥有什么什么权限)
Shiro权限框架
主要认识:
Subject currentUser = SecurityUtils.getSubject()
Apache Shiro 与Spring Security区别
Shiro::用于中小型项目比较常见,简单易上手,可以支持多种环境
Shiro 可以不跟任何的框架或者容器绑定,可独立运行
Spring Security:一般多用于spring环境,中大型项目,更强大
Spring Security 则必须要有Spring环境
Shiro认证
基于ini认证
1.新建项目 --导入依赖
--新建 配置文件ini
2. shiro帮我们创建用户
创建令牌 ,类比页面传入的账号密码
密码错误-- IncorrectCredentialsException
账号错误-- UnknownAccountException
源码分析--
导入依赖
commons-logging commons-logging 1.1.3 org.apache.shiro shiro-core 1.5.2 junit junit 4.13.2 org.projectlombok lombok 1.16.22 provided编写ini,shiro默认支持的是ini配置的方式(只是演示) shiro-au.ini,真实项目使用xml
#用户的身份、凭据
[users]
zhangsan=555
xiaoluo=666
使用 Shiro 相关的 API 完成身份认证
@Test
public void testLogin(){
//创建Shiro的安全管理器,是shiro的核心
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//加载shiro.ini配置,得到配置中的用户信息(账号+密码)
IniRealm iniRealm = new IniRealm(“classpath:shiro-au.ini”);
securityManager.setRealm(iniRealm);
//把安全管理器注入到当前的环境中
SecurityUtils.setSecurityManager(securityManager);
//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
Subject subject = SecurityUtils.getSubject();
System.out.println(“认证状态:”+subject.isAuthenticated());
//创建令牌(携带登录用户的账号和密码)
UsernamePasswordToken token = new UsernamePasswordToken(“xiaoluo”,“666”);
//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
subject.login(token);
System.out.println(“认证状态:”+subject.isAuthenticated());
//登出
//subject.logout();
//System.out.println(“认证状态:”+subject.isAuthenticated());
}
自定义Realm–认证
模拟
@Getter
@Setter
public class Employee {
private String username;
private String password;}
思路:
//继承 AuthorizingRealm
//参数的token ,subject进行登录匹配传入的token : UsernamePasswordToken
1.获取用户名
方式一:将token强转为UsernamePasswordToken-->getUsername()
方式二:通过token.getPrincipal()
2.以用户名为条件,查询mysql数据库,得到用户对象(用户名/密码)
模拟从数据库查询的对象 ------->自己new一个
3.将用户对象封装成认证info对象返回
//存在两种可能
1.用户对象为null-----> return null
2.不为空 ---->封装数据 return new SimpleAuthenticationInfo(
有3个参数
1.employee , //从数据库查询出来需要进行密码匹配的用户对象
2.employee.getPassword(), //匹配对象密码
3.super.getName() //指定realm的名称 ,可自定义
)
实现:
public class Realm extends AuthorizingRealm {
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取登录用户名
String username = (String) token.getPrincipal();
//2.以用户名为条件查询mysql数据库,得到用户对象
//模拟数据
Employee employee = new Employee();
employee.setUsername("xiaoluo");
employee.setPassword("123");
//封装成一个认证info对象
//判断用户是否为空
if (employee!=null){
return new SimpleAuthenticationInfo(
employee,
employee.getPassword(),
super.getName()
);
}
return null;
}
}
@Test
public void testLogin(){
//创建Shiro的安全管理器,是shiro的核心
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//自定义Realm,查出用户信息
Realm realm = new Realm();
securityManager.setRealm(realm);
//把安全管理器注入到当前的环境中
SecurityUtils.setSecurityManager(securityManager);
//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
Subject subject = SecurityUtils.getSubject();
System.out.println("认证状态:"+subject.isAuthenticated());
//创建令牌(携带登录用户的账号和密码)
UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","123");
//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
//登出
//subject.logout();
//System.out.println("认证状态:"+subject.isAuthenticated());
}
Shiro授权
基于ini授权
登录
分配
鉴权
权限表达式 资源:操作
admin=*:* //超级管理员
emp=employee:*
//判断是否有某个角色
subject.hasRole(“role”);
//用户拥有所有指定角色返回true
subject.hasAllRoles(Arrays.aList(“role1”,“role2”));
//判断用户是否有指定角色,将结果返回,封装到boolean数组中
boolean[] booleans=subject.hasRoles(Arrays.aList(“role1”,“role2”));
//check开头的是没有返回值,当没有权限时就会抛出异常
subject.checkRole(“role”);
//判断用户是否有某个权限
subject.isPermitted(“权限表达式”)
boolean[] booleans=subject.isPermitted(“权限表达式1”,“权限表达式2”)
自定义realm — 授权
思路:
1.获取当前登录用户的id/name
//获取当前登录用户对象 ,登录传过来的第一个参数
方式一 :principals.getPrimaryPrincipal();
方式二:SecurityUtils.getSubject().getPrincipal();
2.以用户id作为条件查询mysql数据,查询该用户拥有角色/权限
//假装查数据
//List roles= roleService.queryByEmployee(Employee.getId());
//List //List roles= permissionService.queryByEmployee(Employee.getId());
= permissionService.queryByEmployee(Employee.getId());
3.将角色与权限封装到授权info对象中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles) //添加角色
info.addStringPermission(permission);//添加权限
实现:
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前用户对象
Employee employee = (Employee)principalCollection.getPrimaryPrincipal();
//以用户id查询数据库,判断该角色/用户是否拥有权限 模拟
List roles= Arrays.asList("seller");
List permissions= Arrays.asList("customer:list","customer:save");
//封装到info对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
public class ShiroDemo {
@Test
public void testLogin(){
//创建Shiro的安全管理器,是shiro的核心
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//加载shiro.ini配置,得到配置中的用户信息(账号+密码)
IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");
//自定义Realm,查出用户信息
Realm realm = new Realm();
securityManager.setRealm(realm);
//把安全管理器注入到当前的环境中
SecurityUtils.setSecurityManager(securityManager);
//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
Subject subject = SecurityUtils.getSubject();
System.out.println("认证状态:"+subject.isAuthenticated());
//创建令牌(携带登录用户的账号和密码)
UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","12");
//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
//登出
//subject.logout();
//System.out.println("认证状态:"+subject.isAuthenticated());
//判断用户是否有某个角色
System.out.println("hr:"+subject.hasRole("hr"));
System.out.println("seller:"+subject.hasRole("seller"));
//是否同时拥有多个角色
System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("hr", "seller")));
boolean[] booleans = subject.hasRoles(Arrays.asList("hr", "seller"));
//System.out.println(booleans);
//check开头的是没有返回值的,当没有权限时就会抛出异常
subject.checkRole("seller");
//判断用户是否有某个权限
System.out.println("user:delete:"+subject.isPermitted("user:delete"));
subject.checkPermission("customer:list");
}
}
//如果用户是超级管理员
//设置超级管理员角色
info.addRole("admin")
//设置超级管理员权限
info.addStringPermission("*:*");
项目集成shiro 认证-授权注意点
认证
1.添加对应依赖
2.配置代理过滤器
为什么不用配置拦截器?
Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用
3.创建shiro.xml
4.配置shiro过滤器
5.配置安全管理器 DefaultWebSecurityManager
6.修改LoginController
7.配置自定义Realm
8.将自定义Realm交给容器管理
shiro中的过滤器**
过滤器的名称
Java 类
anon
org.apache.shiro.web. lter.authc.AnonymousFilter
authc
org.apache.shiro.web. lter.authc.FormAuthenticationFilter
authcBasic
org.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter
roles
org.apache.shiro.web. lter.authz.RolesAuthorizationFilter
perms
org.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter
user
org.apache.shiro.web. lter.authc.UserFilter
logout
org.apache.shiro.web. lter.authc.LogoutFilter
port
org.apache.shiro.web. lter.authz.PortFilter
rest
org.apache.shiro.web. lter.authz.HttpMethodPermissionFilter
ssl
org.apache.shiro.web. lter.authz.SslFilter
/static/**=anon
/**=authc
/logout=logout
/employee/input=perms["user:update"]
授权
没有权限的异常 :org.apache.shiro.authz.UnauthorizedException
shiro注解鉴权操作方式:
类比RBAC:
1.自定义权限注解
2.将注解贴在请求映射方法上面
3.将注解标注的权限表达式加载到数据库中
4.将这些表达式根据用户角色进行权限分配
5.当用户登录之后,访问某个请求映射方法时,先经过权限拦截器,进行鉴权操作
1.获取当前登录用户权限表达式集合
2.获取当前请求映射方法头顶上权限表达式
3.判断用户权限表达式集合中是否包含该表达式
Shiro 权限验证三种方式
编程式
注解式
页面标签式
3.页面标签式
<@shiro.hasPermission name=“employee:list”>
/@shiro.hasRole
授权步骤
1.贴注解
2.开启 Shiro 注解扫描器
3.查询数据库真实数据 自定义realm
注解@RequiresPermissions()
value属性: 这个属性是一个数组
Logical.AND: 必须同时拥有value配置所有权限才允许访问
Logical.OR:只需要拥有value配置所有权限中一个即可允许访问
约定:权限表达式,第一值权限表达式,第二值为权限名称
@RequiresPermissions(value={“employee:list”,“员工列表”},logical=Logical.OR)
逻辑为OR,value满足其中一个条件成立
标签式权限验证
**
拓展FreeMarker标签**
默认的freemarker是不支持shiro标签,所以需要做功能拓展
----------------
public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {
@Override
public void afterPropertiesSet() throws IOException, TemplateException {
//继承之前的属性配置,这不能省
super.afterPropertiesSet();
Configuration cfg = this.getConfiguration();
cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签
}
}
--------------------------让之前的配置文件
中的FreeMarkerConfigurer修改为自定义的ShiroFreeMarkerConfig类
true
authenticated 标签:已认证通过的用户。
<@shiro.authenticated> /@shiro.authenticated
notAuthenticated 标签:未认证通过的用户。
<@shiro.notAuthenticated>/@shiro.notAuthenticated
principal 标签 :输出当前用户信息,通常为登录帐号信息
<@shiro.principal property=“name” /> //对应name属性
hasRole 标签:验证当前用户是否拥有该角色
<@shiro.hasRole name=“admin”>我是管理员/@shiro.hasRole
hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔
<@shiro.hasAnyRoles name=“admin,user,hr”>Hello admin/@shiro.hasAnyRoles
hasPermission 标签:验证当前用户是否拥有该权限
<@shiro.hasPermission name=“department:delete”>删除/@shiro.hasPermission
Shiro加密
//参数1 :原文,参数2:盐 ,参数3:散列次数(加密次数)
Md5Hash hash=new Md5Hash("1","kent",3);
盐一般要求是固定长度的字符串,且每个用户的盐不同。
一般盐的选择的是用户的唯一数据(账号名等),盐是要求不能改变的,不然下次加密结果就对应不上了
Shiro缓存
当我们登录时,授权信息是要从数据库中查询的,如果每次刷新刷新都需要获取你到底有没有权限,对性能影响不好,用户登录后,授权信息一般很少改动,所以,我们可以将第一次授权后,将信息存在缓存中,下次直接再缓存中获取,就很好的避免了多次访问数据库
Shiro没有实现自己的缓存机制,只提供了支持缓存的API接口,这里使用的是EhCache
mybatis 一级 二级缓存------百度(ing)补充
集成EhCache
第三方EhCache
1.配置缓存管理器并引用缓存管理器
----------------------------------------
2.添加缓存配置文件 shiro-ehcache.xml
拓展 统一全局异常
@ControllerAdvice 控制器功能增强注解
1.在进入请求映射方法之前做功能增强,经典用法:date日期格式化
2.在进入请求映射方法之后做功能增强,经典用法:统一异常处理
3.处理异常的方法,方法需要贴ExceptionHandler注解
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
shiroFilter
/*
/userLogin=anon
/css/**=anon
/js/**=anon
/img/**=anon
/upload/**=anon
/userLogout=logout
/**=authc
@RequestMapping("/userLogin")
@ResponseBody
public JsonResult login(String username, String password){
try {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
SecurityUtils.getSubject().login(token);
return new JsonResult();
} catch (UnknownAccountException e) {
return new JsonResult(false, "账号不存在");
} catch (IncorrectCredentialsException e) {
return new JsonResult(false, "密码错误");
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false, "登录异常,请联系管理员");
}
}
@RequestMapping("/userLogin")
@ResponseBody
public JsonResult login(String username, String password){
try {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
SecurityUtils.getSubject().login(token);
return new JsonResult();
} catch (UnknownAccountException e) {
return new JsonResult(false, "账号不存在");
} catch (IncorrectCredentialsException e) {
return new JsonResult(false, "密码错误");
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false, "登录异常,请联系管理员");
}
}
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦