七 SpringBoot集成Shiro鉴权
1 Shiro 鉴权三种方式
编程式 通过写 if/else 授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("hr")) {
//有权限
} else {
//无权限
}
注解式 通过在controller的方法上放置相应的注解完成(shiro已经做好了,直接贴就行)
@RequiresRoles("hr")
@RequiresPermissions("user:create")
// 设置多个权限是否同时需要,或者只有其中一种即可
// @RequiresPermissions(value={"user:create","user:delete"},logical=logical.OR)
public void addUser(User user) {
//有权限
}
JSP标签(shiro自带) 、Freemarker的标签(第三方) 、ThymeLeaf的标签(第三方)在页面通过相应的标签完成,ThymeLeaf标签文档,通过标签完成有权限显示,无权限不显示。
<a shiro:hasRole="administrator" href="admin.html">Administer the system</a>
<a shiro:hasPermission="user:create" href="createUser.html">Create a new User</a>
2 Shiro 拦截方法流程
3 支持注解鉴权配置
需要ShiroConfig中配置Shiro注解通知器Bean(当看到注解后通知SecurityManager进行权限认证),与支持CGLIB的Bean
// 开启Shiro注解通知器
// Qualifier(起别名)此处 SecurityManager 可能报错,因为lang包是自动导入的
// 所以此处的 SecurityManager 是lang包下的,我们需要的是Shiro包下的 SecurityManager 可使用acap.xx.xx.xx.SecurityManager 这种方式,
// 或者使用之前定义的安全管理器类型 DefaultWebSecurityManager 防止自动导包
// public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
// DefaultWebSecurityManager securityManager){}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// 设置支持CGlib代理
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
4 完善Realm的授权方法
在Apache Shiro中,SimpleAuthorizationInfo 并不直接与 Subject 关联。相反,SimpleAuthorizationInfo 是用于封装授权信息(如角色和权限)的,这些信息随后会被存储在 SecurityManager 的上下文中,以便在需要时与 Subject 进行关联和检查
//授权相关
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户
Employee employee = (Employee)principals.getPrimaryPrincipal();
// 封装授权信息类
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据用户的id查询该用户拥有的角色编码
List<Role> roles = roleService.queryByEmployeeId(employee.getId());
for(Role role:roles){
info.addRole(role.getSn());
}
//根据用户的id查询该用户拥有的权限表达式
List<String> permissions = permissionService.queryByEmployeeId(employee.getId());
info.addStringPermissions(permissions);
return info;
}
5 完善自定义异常 – 无权限异常
@ControllerAdvice
public class ExceptionControllerAdvice {
// 自定义异常(向让用户看到的)
@ExceptionHandler(BusinessException.class)
public String handlerException(BusinessException e, Model model, HandlerMethod method, HttpServletResponse response){
// 异常也分为页面异常和 ajax 请求的异常。
if(method.hasMethodAnnotation(ResponseBody.class)){
// ajax 请求
response.setContentType("application/json;charset=utf-8");
try {
response.getWriter().write(new ObjectMapper().writeValueAsString(new JsonResult(false,e.getMessage())));
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} else{
// 页面请求
model.addAttribute("errorMsg",e.getMessage());
return "common/error";
}
}
// 系统异常(不想让用户看到的)
@ExceptionHandler(Exception.class)
public String handlerException(Exception e, Model model, HandlerMethod method, HttpServletResponse response){
// 异常也分为页面异常和 ajax 请求的异常。
if(method.hasMethodAnnotation(ResponseBody.class)){
// ajax 请求
response.setContentType("application/json;charset=utf-8");
try {
response.getWriter().write(new ObjectMapper().writeValueAsString(new JsonResult(false,"系统繁忙,请联系管理员")));
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} else{
// 页面请求
model.addAttribute("errorMsg","系统繁忙,请联系管理员");
return "common/error";
}
}
// 没有权限异常
@ExceptionHandler(UnauthorizedException.class)
public String handlerException(UnauthorizedException e, Model model, HandlerMethod method, HttpServletResponse response){
// 异常也分为页面异常和 ajax 请求的异常。
if(method.hasMethodAnnotation(ResponseBody.class)){
// ajax 请求
response.setContentType("application/json;charset=utf-8");
try {
response.getWriter().write(new ObjectMapper().writeValueAsString(new JsonResult(false,"您还没有权限访问,请联系管理员")));
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
} else{
// 页面请求
model.addAttribute("errorMsg","您还没有权限访问,请联系管理员");
return "common/nopermission";
}
}
}
6 超级管理员权限
超级管理员并没有配置任务的角色和权限会被拦截。所以需要在授权代码中做特殊的处理
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Employee employee = (Employee)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if(employee.isAdmin()){
//如果是超级管理员,给管理员赋所有的权限
List<Role> roles = roleService.listAll();
for(Role role:roles){
info.addRole(role.getSn());
}
//使用通配符表示拥有所有的权限
info.addStringPermission("*:*");
}else{
//根据用户的id查询该用户拥有的角色编码
List<Role> roles = roleService.queryByEmployeeId(employee.getId());
for(Role role:roles){
info.addRole(role.getSn());
}
//根据用户的id查询该用户拥有的权限表达式
List<String> permissions = permissionService.queryByEmployeeId(employee.getId());
info.addStringPermissions(permissions);
}
return info;
}
7 ShringBoot集成Shiro实现注解式鉴权功能步骤
- 在需要权限控制的控制器方法上面贴上注解。
- @RequiresPermissions() ==> 需要权限控制
- @RequiresRoles() ==> 需要角色控制
- 由于 Shiro 中使用的是 AOP 的方式对方法进行拦截控制,所以我们需要在配置文件中配置注解通知器,配置支持CGLIB。
- 完善 Realm 中授权逻辑
- 根据用户名查询当前用户信息。
- 根据用户信息查询用户的权限集合和角色集合。
- 对于超级管理员应该拥有一切的角色和权限。(角色必须通过遍历才能添加,权限可以使用通配符)
8 编程式鉴权
这种方式允许开发者根据业务需求自定义鉴权逻辑,具有较高的灵活性和可定制性。(注解式必须抛异常,编程式可不抛)
@RequestMapping("/list")
public String list(Model model, QueryObject qo) {
Subject subject = SecurityUtils.getSubject();
PageInfo<Department> pageInfo = null;
if(subject.isPermitted("department:list")){
pageInfo = departmentService.query(qo);
}else{
pageInfo = new PageInfo<>(Collections.EMPTY_LIST);
}
model.addAttribute("pageInfo", pageInfo);
return "department/list";
}
9 标签式鉴权
① 需要依赖(可以不是thymeleaf)
<!-- thymeleaf模板引擎和shiro框架的整合 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>${thymeleaf.extras.shiro.version}</version>
</dependency>
② 配置Shiro集成ThymeLeaf标签支持(ShiroConfig中添加Bean)
// thymeleaf模板引擎和shiro框架的整合
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
③ 页面中添加约束头
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
④ 使用标签
<a href="#" class="btn btn-success btn-input" style="margin: 10px" shiro:hasPermission="department:saveOrUpdate">
<span class="glyphicon glyphicon-plus"></span> 添加
</a>