直接输入http://localhost:8080/system/module/list.do就能看到所有模块
比如使用普通员工登录, 直接url就能访问到它本应访问不到的页面
给一个员工分配了部门管理\查看权限, 但没分配新增权限, 但页面上依旧显示新增按钮
问题:
1. 用户不登录就可以访问系统资源
2. 用户登录之后可以访问到不属于自己的资源
3. 页面标签没有做到权限限制
上述功能的描述,可以使用过滤器或拦截器完成,当然在实际开发者已经有组织把这些功能封装为框架,比如Shiro或者SpringSecurity,来简化权限的控制,我们今天使用shiro框架。
Apache Shiro是Java的一个安全框架。不仅功能强大,而且使用简单,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。
官网地址:http://shiro.apache.org/
* Authentication(认证)
用户登录,身份识别 --- 结果:不是系统用户不能访问系统
* Authorization(授权)
限定用户可进行的操作 ---- 结果:没权限不能访问资源
* Cryptography(加密)
安全数据加密
* Session Manager(会话)
用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中(类似于web的session)
* Web Integration
web容器集成
* Integrations
集成其他应用,比如spring、缓存
* Application Code(应用)
我们自己编写的项目
* Subject(主体)
它是一个工具类,负责用户和shiro框架交互
* SecurityManager(安全管理器)
它是Shiro架构中最核心的组件,通过它可以协调其他组件完成用户认证和授权
* Realm(域)
它定义了访问数据的方式,用来连接不同的数据源,如:数据库,配置文件等
明确:我们使用shiro主要要做两件事
认证:认证指的是匹配用户名(邮箱)和密码,让平台认识你。
授权:授权指的是当前认证的用户进入平台,能操作哪些页面。
过滤器简称 | 对应的java类 | 描述 |
---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 未认证访问 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 认证后访问 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | httpBasic认证后访问 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 指定权限访问 |
port | org.apache.shiro.web.filter.authz.PortFilter | 指定端口访问 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | 指定rest访问 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 指定角色访问 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | ssl认证后访问 |
user | org.apache.shiro.web.filter.authc.UserFilter | 指定用户访问 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 用户退出 |
标签名称 | 标签条件(均是显示标签内容) |
---|---|
登录之后 | |
不在登录状态时 | |
用户在没有RememberMe时 | |
用户在RememberMe时 | |
在有abc或者123角色时 | |
拥有角色abc | |
没有角色abc | |
拥有权限资源abc | |
没有abc权限资源 | |
默认显示用户名称 |
org.apache.shiro
shiro-spring
1.3.2
org.apache.shiro
shiro-core
1.3.2
在web.xml中添加如下过滤器,注意filter-name的值是shiroFilter
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
添加spring整合shiro的配置文件
applicationContext-shiro.xml
/login.jsp = anon
/css/** = anon
/img/** = anon
/plugins/** = anon
/make/** = anon
/login.do = anon
/** = authc
在export_manager_web的com.itheima.web.realm
包创建类SaasRealm
package com.itheima.web.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class SaasRealm extends AuthorizingRealm {
/**
* @description 用户授权
* @author mryhl
* @date 2020/10/11 16:15 No such property: code for class: Script1
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=====================授权=====================");
return null;
}
/**
* @description 用户认证
* @author mryhl
* @date 2020/10/11 16:14 No such property: code for class: Script1
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=====================认证=====================");
return null;
}
}
2.1 修改LoginController
/**
* @description 用户登陆认证
* @author mryhl
* @date 2020/10/11 16:25 No such property: code for class: Script1
* @return
*/
@RequestMapping("/login")
public String login(String email,String password) {
// 1. 封装email和password为Token
AuthenticationToken authenticationToken = new UsernamePasswordToken(email, new Md5Hash(password, email, 2).toString());
// 2. 获取subject,并且调用login方法
// 通过SecurityUtils获取subject
Subject subject = SecurityUtils.getSubject();
/**
* @author mryhl
* 调用login方法, 传入token.
* 并对此进行捕获异常
*/
try {
subject.login(authenticationToken);
// 登陆成功
User user = (User) subject.getPrincipal();
// 通过则保存用户数据
session.setAttribute("loginUser",user);
//根据用户查询对应的权限
List moduleList=userService.findModuleByUser(user);
session.setAttribute("modules", moduleList);
return "redirect:/home/main.do";
} catch (Exception e) {
request.setAttribute("error", "用户名或者密码错误");
return "forward:/login.jsp";
}
}
2.2 修改SaasReam
/**
* @description 用户认证
* @author mryhl
* @date 2020/10/11 16:56
* @return AuthenticationInfo 的实现类 SimpleAuthenticationInfo 简单认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 进入方法标识
System.out.println("=====================认证=====================");
// 强制类型转换
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
// 获取传入的email
String email = usernamePasswordToken.getUsername();
// 根据用户进行查询
User user = userService.findByEmail(email);
// 判断返回信息
if (user==null) {
// 返回空的简单身份认证信息,代表没有找到内容
return new SimpleAuthenticationInfo();
}else {
/**
* @author mryhl
* SimpleAuthenticationInfo三个参数
* Object principal 主角---->user
* Object credentials 密码--->user.getPassword()
* String realmName 当前realm的名称--->this.getName()
*/
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
}
}
//退出
@RequestMapping(value = "/logout", name = "用户登出")
public String logout() {
SecurityUtils.getSubject().logout(); //登出
return "redirect:/login.jsp";
}
shiro授权的意思就是: 当一个用户试图访问一个资源的时候, shiro会判断此用户是否有访问此资源的权限
shiro授权的步骤:
2.1 xml中定义权限
applicationContext-shiro.xml
/company/list.do = perms["企业管理"]
2.2 SaasRealm中实现授权逻辑
/**
* @description 用户授权
* @author mryhl
* @date 2020/10/11 16:15
* @return simpleAuthorizationInfo 简单授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=====================授权=====================");
// 查询当前用户的权限信息
User user = (User) principalCollection.getPrimaryPrincipal();
List moduleList = userService.findModuleByUser(user);
// 将信息传递到Shiro
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Module module : moduleList) {
simpleAuthorizationInfo.addStringPermission(module.getName());
}
System.out.println(simpleAuthorizationInfo);
return simpleAuthorizationInfo;
}
3.1 去掉xml中的注解配置
spring/applicationContext-shiro.xml
/login.jsp = anon
/css/** = anon
/img/** = anon
/plugins/** = anon
/make/** = anon
/login.do = anon
/** = authc
3.2 使用注解声明资源访问权限
CompanyController.java
/**
* 查看列表
* name字段主要用于打印日志
*/
//@RequiresPermissions("企业管理") 代表只有用户有企业管理的权限,才能访问当前方法
//相当于XML中的 /company/list.do = perms["企业管理"]
@RequiresPermissions("企业管理")
@RequestMapping(value = "/list",name = "企业列表查询")
public String list(
@RequestParam(defaultValue = "1", name = "page") Integer pageNum,
@RequestParam(defaultValue = "1") Integer pageSize
){
PageInfo pageInfo = companyService.findByPage(pageNum, pageSize);
request.setAttribute("page", pageInfo);
return "company/company-list";
}
3.3 全局异常处理修改
基于注解的授权控制, 当授权失败的时候, 不会跳转授权失败页面, 而是抛出一个异常, 需要我们在全局异常处理器中自己处理
package com.itheima.web.handlers;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class CommonExceptionHandler {
//处理所有的Exception及其子类
@ExceptionHandler(Exception.class)
public String exceptionHandler(Exception e, HttpServletRequest request) {
//1. 打印异常,给程序员
e.printStackTrace();
request.setAttribute("errorMsg", e.getMessage());
//2. 返回页面, 给用户
return "error";
}
//处理未授权的异常
@ExceptionHandler(UnauthorizedException.class)
public String UnauthorizedException(Exception e, HttpServletRequest request) {
//2. 返回未授权页面, 给用户
return "redirect:/unauthorized.jsp";
}
}
4.1 说明
当用户没有相关资源的具体操作权限的时候,我们应该是不让其看到响应按钮的
这就要使用到了shiro标签, 这里主要介绍一个
, 用法如下:
4.2 代码
以部门管理为例子, 演示效果:
1 引入shiro标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
2 使用shiro标签控制权限
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="../../base.jsp" %>
数据 - AdminLTE2定制版
系统管理
部门管理
部门列表
我们现在访问被拦截的页面时,每次shiro的安全过滤器都需要从realm中获取认证方法,也意味这每次查询数据库,浪费服务性能,造成访问压力,这时候我们必须要进行优化:经常访问,但又不经常修改的这部分数据可以使用缓存
spring/applicationContext-shiro.xml
shiro 支持如下的权限写法, 代表同时拥有多个权限, 才能访问指定资源
/system/dept/edit.do = perms["新增部门","删除部门"]
但是我们现在有这样一个需求: 只需要满足其中一个个权限, 就要能对资源访问, 这时怎么办呢?
操作步骤:
AuthorizationFilter
, 并实现里面的方法自定义过滤器
package com.itheima.web.filters;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class MyPermissionsAuthorizationFilter extends AuthorizationFilter {
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response); //subject里面就有当前用户的权限 ["查看部门","新增部门","修改部门"]
String[] perms = (String[]) mappedValue; //访问资源需要的权限 ["新增部门","删除部门"]
//如果没有配置,放行
if (perms == null || perms.length == 0) {
return true;
}
for (String perm : perms) {
if (subject.isPermitted(perm)) {
return true;
}
}
return false;
}
}
修改配置文件
/login.jsp = anon
/css/** = anon
/img/** = anon
/plugins/** = anon
/make/** = anon
/login.do = anon
/company/list.do = myPerms["新增部门","删除部门"]
/** = authc