Shiro 安全框架详解二(概念+权限案例实现)
Posted Yan Yang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro 安全框架详解二(概念+权限案例实现)相关的知识,希望对你有一定的参考价值。
Shiro 安全框架详解二
总结内容
一、登录认证
Shiro 入门以及登录认证案例及实现请看我上篇博客:
Shiro 安全框架详解一(概念+登录案例实现)
二、Shiro 授权
1. 概念
系统中的授权功能就是为用户分配相关的权限,只有当用户拥有相应的权限后,才能访问对应的资源。
2. 授权流程图
三、基于 ini 的授权认证案例实现
1. 实现原理图
2. 实现代码
2.1 添加 maven jar包依赖
<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.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
<scope>provided</scope>
</dependency>
2.2 编写 ini 配置文件:shiro-authc.ini
shiro默认支持的是ini配置的方式,这里只是为了方便,项目中还是会选择xml
#用户的身份、凭据、角色
[users]
zhangsan=555,hr,seller
admin=123,seller
#角色与权限信息
[roles]
hr=employee:list,employee:delete
seller=customer:list,customer:save
- 权限表达式
权限表达式的作用主要是用来在权限校验的时候使用,表达式中包含有当前访问资源的相关信息,应该具有唯一性,跟以前的权限表达式一致,shiro中也可以使用*通配符。
2.3 Shiro 常用 API
- 常用API
【1】判断用户拥有单个角色:用户对象.hasRole;
【2】判断同时拥有多个角色:hasRoles;
【3】判断拥有指定的角色:hasAllRoles ,全部拥有返回 true ,否则返回 false;
【4】判断用户是否拥有某个权限:isPermitted(),有返回 true,否则返回 false;
【5】ckeck 开头的是没有返回值的,当没有权限时就会抛出异常;
@Test
public void testAuthor(){
//创建Shiro的安全管理器,是shiro的核心
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//加载shiro.ini配置,得到配置中的用户信息(账号+密码)
IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");
securityManager.setRealm(iniRealm);
//把安全管理器注入到当前的环境中
SecurityUtils.setSecurityManager(securityManager);
//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
Subject subject = SecurityUtils.getSubject();
System.out.println("认证状态:"+subject.isAuthenticated());
//创建令牌(携带登录用户的账号和密码)
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
//登出
//subject.logout();
//System.out.println("认证状态:"+subject.isAuthenticated());
//判断用户是否有某个角色
System.out.println("role1:"+subject.hasRole("role1"));
System.out.println("role2:"+subject.hasRole("role2"));
//是否同时拥有多个角色
System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("role1", "role2")));
//check开头的是没有返回值的,当没有权限时就会抛出异常
subject.checkRole("hr");
//判断用户是否有某个权限
System.out.println("user:delete:"+subject.isPermitted("user:delete"));
subject.checkPermission("user:delete");
}
四、CRM 中集成 Shiro 授权
1. Shiro 权限验证三种方式
- 编程式 通过写 if/else 授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("hr")) {
//有权限
} else {
//无权限
}
- 注解式 通过在controller的方法上放置相应的注解完成
@RequiresRoles("admin")
@RequiresPermissions("user:create")
public void hello() {
//有权限
}
- JSP 标签(shiro 自带) 或 freemarker 的标签(第三方) 在页面通过相应的标签完成
<@shiro.hasPermission name="employee:list">
<!-- 有权限 -->
</@shiro.hasRole>
五、项目中集成 Shiro 认证授权案例实现
1. 项目准备
项目准备请看我上一篇博客:
Shiro 安全框架详解一(概念+登录案例实现)
2. 代码实现
2.1 登录的 EmployeeController 控制器
@RequiresPermissions(value = "employee:list")
@RequestMapping("/list")
public String list(Model model){
model.addAttribute("depts", departmentService.list());
return "employee/list";
}
- @RequiresPermissions(“employee:list”)
权限限定注解,表示当前用户拥有employee:list 权限才可以访问当前请求映射方法 - @RequiresRoles(“hr”)
角色限定注解,表示当前用户拥有 hr 角色才可以访问当前请求映射方法
2.2 配置自定义Realm:CarBusinessRealm
package cn.wolfcode.shiro;
import cn.wolfcode.domain.Employee;
import cn.wolfcode.domain.Role;
import cn.wolfcode.service.IEmployeeService;
import cn.wolfcode.service.IPermissionService;
import cn.wolfcode.service.IRoleService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class CarBusinessRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
@Autowired
private IRoleService roleService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、获取页面传入的账户:username
String username = (String) token.getPrincipal();
// 2、以账户为条件,查询用户对象
Employee employee = employeeService.selectByUsername(username);
if (employee == null) {
return null;
}
// 封装成 info 数据
return new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 1、获取当前登录用户对象
Employee employee = (Employee) principals.getPrimaryPrincipal();
// 2、查询该用户对象的权限集合
List<String> permissions = permissionService.selectByEmpId(employee.getId());
// 3、查询该用户对象的角色集合
List<String> roles = roleService.queryByEmpId(employee.getId());
// 4、封装授权 info 对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 是超管获取所有权限
if (employee.isAdmin()) {
List<Role> rolesT = roleService.listAll();
for (Role role : rolesT) {
info.addRole(role.getSn());
}
// 拥有所有权限
info.addStringPermission("*:*");
return info;
}
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
}
2.3 创建 shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- <aop:config/> 会扫描配置文件中的所有 advisor,并为其创建代理 -->
<aop:config/>
<!-- Pointcut advisor 通知器,会匹配所有加了 shiro 权限注解的方法 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 自定义 realm -->
<bean id="carBusinessRealm" class="cn.wolfcode.shiro.CarBusinessRealm"/>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 设置 realm 值-->
<property name="realm" ref="carBusinessRealm"/>
<!-- 注册缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 设置配置文件 -->
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 引用指定的安全管理器 -->
<property name="securityManager" ref="securityManager"/>
<!-- shiro 默认的登录地址是 /login.jsp 现在要指定我们自己的登录页面地址 -->
<property name="loginUrl" value="/login.html"/>
<!-- 路径对应的规则:登录校验规则 -->
<property name="filterChainDefinitions">
<value>
/userLogin=anon
/appointment/save=anon
/index=anon
/css/**=anon
/js/**=anon
/img/**=anon
/upload/**=anon
/static/**=anon
/userLogout=logout
/**=authc
</value>
</property>
</bean>
</beans>
2.4 在SpringMVC 中引入 shiro.xml
<import resource="classpath:shiro.xml"/>
2.5 安全管理器
在 JavaEE 环境中,我们需要使用的安全管理器是:DefaultWebSecurityManager
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 这里面有很多配置文件 -->
</bean>
有了上面的配置,当我们的访问到达具体资源之前,会先进过指定的过滤器做预处理,在允许通过之后才能继续访问。
2.6 没有权限的异常处理
如果用户不是超级管理员,只能访问分配给他的相关资源,如果访问了没有权限的资源,会抛出下面的异常:
**org.apache.shiro.authz.UnauthorizedException**: Subject does not have permission [department:list]
我们只需要想办法捕获到该异常,然后进行处理即可。
@ExceptionHandler(AuthorizationException.class)
public String exceptionHandler(AuthorizationException e, HandlerMethod method, HttpServletResponse response){
e.printStackTrace(); //方便开发的时候找bug
//如果原本控制器的方法是返回jsonresult数据,现在出异常也应该返回jsonresult
//获取当前出现异常的方法,判断是否有ResponseBody注解,有就代表需要返回jsonresult
if(method.hasMethodAnnotation(ResponseBody.class)){
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(JSON.toJSONString(new JsonResult("没有权限操作!")));
} catch (IOException e1) {
e1.printStackTrace();
}
return null;
}
//如果原本控制器的方法是返回视图页面,现在也应该返回视图页面
return "common/nopermission";
}
2.7 标签式权限验证
在前端页面上,我们通常可以根据用户拥有的权限来显示具体的页面,如:用户拥有删除员工的权限,页面上就把删除按钮显示出来,否则就不显示删除按钮,通过这种方式来细化权限控制。
要能够实现上面的控制,需要使用 Shiro 中提供的相关标签,标签的使用步骤如下:
- 拓展freemarker标签
前端页面我们选择的是freemarker,而默认 freemarker 是不支持 shiro 标签的,所以需要对其功能做拓展,可以理解为注册 shiro 的标签,达到在freemarker 页面中使用的目的
public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {
@Override
public void afterPropertiesSet() throws IOException, TemplateException {
//继承之前的属性配置,这不能省
super.afterPropertiesSet();
Configuration cfg = this.getConfiguration();
cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签
}
}
- 在mvc.xml 中把以前的FreeMarkerConfigurer修改成我们自定义的MyFreeMarkerConfig类
<bean class="cn.wolfcode.crm.util.ShiroFreeMarkerConfig">
<!-- 配置 freemarker 的文件编码 -->
<!-- ..略... -->
</bean>
有了上面的准备工作后,我们就可以在freemarker 页面中使用 shiro 相关的标签来对页面显示做控制了。
- 使用shiro标签
常用标签:
authenticated 标签:已认证通过的用户。
<@shiro.authenticated> </@shiro.authenticated>
notAuthenticated 标签:未认证通过的用户。与 authenticated 标签相对。
<@shiro.notAuthenticated></@shiro.notAuthenticated>
principal 标签:输出当前用户信息,通常为登录帐号信息
后台是直接将整个员工对象作为身份信息的,所以这里可以直接访问他的 name 属性得到员工的姓名
<@shiro.principal property="name" />
对应realm中返回的SimpleAuthenticationInfo对象的第一个参数
new SimpleAuthenticationInfo(employee,employee.getPassword(),this.getName());
hasRole 标签:验证当前用户是否拥有该角色
<@shiro.hasRole name="admin">Hello admin!</@shiro.hasRole>
hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔
<@shiro.hasAnyRoles name="admin,user,operator">Hello admin</@shiro.hasAnyRoles>
hasPermission 标签:验证当前用户是否拥有该权限
<@shiro.hasPermission name="department:delete">删除</@shiro.hasPermission>
2.8 编程式权限验证
随便找个能执行到的地方,测试shiro提供的权限api是否能结合realm完成权限判断功能
@RequestMapping("/list")
public String list(Model model, QueryObject qo){
System.out.println("当前用户是否有admi以上是关于Shiro 安全框架详解二(概念+权限案例实现)的主要内容,如果未能解决你的问题,请参考以下文章
JAVAWEB开发之权限管理——shiro入门详解以及使用方法shiro认证与shiro授权