浅谈Apache Shiro权限模块及数据库设计
时间:2022-07-26
本文章向大家介绍浅谈Apache Shiro权限模块及数据库设计,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
写在最最最~~~前面的:由于Shiro框架在学习过程中假如没有一个实例Demo的参考,理解起来可能较为生涩难懂,所以笔者建议大家参考这个开源的项目:点我下载项目,该项目是我在学习Apache Shiro过程中参考的项目,我在原项目的基础上增加了一些便于理解的注释等,项目采用前后分离的方式开发,原作者:点我查看
简介
Apache Shiro是Java项目中常用的两大安全框架之一,可以完成认证、授权、加密、会话管理等功能。Apache Shiro较Spring家族的Spring Security更为简洁、更易上手的特点。
Shiro三大组件
- Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
- SubjectManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器。
- Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。 扩展组件:
- SessionManager :使用Session可以完成用户登录及单点登录等功能。
- CacheManager: 对Shiro其他组件提供缓存支持(如使用Redis缓存)。
Apache Shiro使用
1. 实现URL级别的权限控制(某个页面、资源需要什么权限访问)
首先需要在pom文件中引入该框架,配置 web.xml ,使用 shiroFilter 拦截 / * 根据需求完成对页面权限控制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Shiro核心组件 -->
<!-- 1 配置 securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 配置缓存配置 -->
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="jdbcRealm"/>
</bean>
<!-- 2配置 缓存框架 ehcache-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 3 自定义Realm -->
<bean id="jdbcRealm" class="com.qfjy.web.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--hashAlgorithm 加密的名称 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- hashIterations 加密的次数-->
<property name="hashIterations" value="1902"></property>
</bean>
</property>
</bean>
<!--
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
</bean>
</property>
-->
<!-- =========================================================
Shiro Spring特有的整合
========================================================= -->
<!-- 后处理器自动为Spring配置的Shiro对象调用init()和destroy()方法,
因此您不必1)为每个bean定义指定init-method和destroy-method属性,
2)甚至知道哪些Shiro对象要求调用这些方法。
Spring 自动管理Shiro的对象(生命周期管理)
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--
为Spring配置的bean启用Shiro Annotations。仅在lifecycleBeanProcessor运行后运行
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 前提条件: bean id 必须要和 web.xml shiroFilter一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--未认证可以访问的页面 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 认证成功后可以访问的页面-->
<property name="successUrl" value="/main.jsp"/>
<!-- 无权限页面-->
<property name="unauthorizedUrl" value="/unauth.jsp"/>
<!-- <property name="filters">
<util:map>
<entry key="aName" value-ref="someFilterPojo"/>
</util:map>
</property> -->
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/users/login=anon
/css/**=anon
/images/**=anon
/js/**=anon
/login/exit = logout
/student.jsp=roles[stu]
/teacher.jsp=roles[tea]
/list.jsp=roles[stu,tea]
/** = authc
</value>
</property>
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
</bean>
<!-- 工厂方法注入 -->
<!-- 1 构建Bean管理交由Spring ioc容器-->
<bean id="filterChainDefinitionMapBuilder" class="com.qfjy.web.shiro.FilterChainDefinitionMapBuilder"></bean>
<!-- 2 工厂方法注入-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="builder" />
</beans>
关于配置:
页面权限的优先顺序: 先声明的优先:URL权限采取第一次匹配优先的方式(为准),即从头开始
xxx.html* = anon (未登录可以访问) xxx.html* =authc (必须登录才能访问 ) xxx.html* = perms[权限] (需要特定权限才能访问) xxx.html* = roles[角色] (需要特定角色才能访问 )
2.方法级别细粒度权限控制
方法级别粒度权限控制需要完成对用户的认证功能
认证:
//Realm:SecurityManager要验证用户身份,那么它需要从Realm中获取相应分身进行比较,以确定用户身份是否合法。
//合法:从Realm中获得用户相应的角色/权限。
//ShiroRealm.java
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UsersService usersService;
/**
* 作用:认证
* 1、该方法什么情况下会被调用
* currentUser.login(token)
* 2、该方法的入参是什么数据
* UsernamePasswordToken
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从token中获取用户从前端提交的数据
UsernamePasswordToken upToken= (UsernamePasswordToken) token;
System.out.println(token);
//1 得到用户名输入的用户名
String username=upToken.getUsername();
//2 判断当前用户在数据库中是否存在
Users users=usersService.selectByUsername(username);
//3如果用户名不存在 UnknownAccountException
if(users==null){
throw new UnknownAccountException("用户名不存在");
}
//4如果用户的状态 锁定 LockedAccountException status=0 1正常
if(users.getStatus()==0){
throw new LockedAccountException("该帐户已被锁定");
}
//5 密码的比较(前台的密码=数据库中查询的密码) Shiro内部来完成的。
// 方法中的参数:Object principal, Object credentials, String realmName
/*
身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证 明)给 shiro,从而应用能验证用户身份:
• principals:身份,即主体的标识属性,可以是任何属性,如用户名、 邮箱等,唯一即可。
一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/邮箱/手机号。
•credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证 书等。
• 最常见的 principals 和 credentials 组合就是用户名/密码了
*/
Object principal=username;
Object credentials=users.getPassword();//数据库查询出的密码,不能给从前端中获取的token中的密码,否则全是对的
ByteSource credentialsSalt=ByteSource.Util.bytes(username); //加盐主体
//AuthenticationInfo info=new SimpleAuthenticationInfo(principal,credentials,super.getName());
AuthenticationInfo info =new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,super.getName());
return info;
}
/**
* 作用:授权
* 1、该方法什么情况下会被调用?
* 1.1当访问需要角色权限时访问
* 1.2如果找到该角色,就不会再第二次调用。
* 2、该方法的入参是什么数据?
* 主体信息(用户名)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1 得到用户名信息
String username= (String) principals.getPrimaryPrincipal();
//2 查询数据库,根据用户名称,查询该用户拥有哪些角色
Set<String> roles= usersService.selectRnamesByUserName(username);
//3 管理员拥有所有的角色(特殊权限)
if(roles.contains("admin")){
roles=usersService.selectRolesAllRnames();
}
AuthorizationInfo info=new SimpleAuthorizationInfo(roles);
return info;
}
/**
* 注册功能时需要使用的代码
* @param args
*/
public static void main(String[] args) {
//注册时存入的数据
//对数据库的密码按照相应规则加密
/**
* hashAlgorithmName 加密码名称
* credentials 要加密的密码
* hashIterations 加密的次数
*/
Object credentials="123456";
String hashAlgorithmName ="MD5";
String username="maoshuai"; //用户名
Object salt=ByteSource.Util.bytes(username); ;
int hashIterations=1902; //加密次数
Object result=new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(result);
}
}
登录:
//UserController.java
@Controller
@RequestMapping("users")
public class UsersController {
/**
* 登录功能
* 如果登录失败,跳到登录页面login.jsp
* 如果登录成功,跳到主页面 main.jsp
*
*@RequestParam注解:
*将请求的参数赋值到控制器的方法参数上
*eg:@RequestParam(value="userId",defaultValue="1"
*/
@RequestMapping("login") // users/login
public ModelAndView login(@RequestParam("username")String username,
@RequestParam("password")String password){
ModelAndView model=new ModelAndView();
//获取用户信息
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) { //当前Subject是否进行认证(登录)
//前台用户传入的用户名和密码 (将用户名和密码封装到UsernamePasswordToken对象中)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);//记住我
try {
//进行认证(登录)功能
currentUser.login(token);
} catch (UnknownAccountException uae) {//未知帐户异常
System.out.println(uae.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","用户名不存在");
return model;
} catch (IncorrectCredentialsException ice) { //凭证匹配器异常 不正确的凭据异常
System.out.println(ice.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","密码输入错误");
return model;
} catch (LockedAccountException lae) { //帐户锁定异常 锁定帐户例外 (将来要在业务逻辑中进行判断)
System.out.println(lae.getMessage());
model.setViewName("/login.jsp");
model.addObject("msg","该用户因违规,被锁定");
return model;
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) { // 认证异常 身份验证异常
return null;
}
}
/*
* 在ShiroRealm中认证成功后
* */
model.setViewName("redirect:/main.jsp");
return model;
}
/**
* 登出功能 (退出登录)
*/
@RequestMapping("logout") // users/logout
public String logout(){
Subject currentUser = SecurityUtils.getSubject();
//all done - log out! 登出
currentUser.logout();
return "/login.jsp";
}
}
粒度权限控制常用注解:
@RequiresPermissions(权限) 需要特定权限才能访问
@RequiresRoles(角色) 需要特定角色才能访问
@RequiresAuthentication 需要认证才能访问
以下是一个粒度例子:
//UserController.java
//若需要实现这么一个需求:只有特定角色才能实现删除功能
/**
* 删除角色
* 传入一个roleId,根据roleId判断用户是否具有删除功能的权限
*/
@RequiresPermissions("role:delete")
@PostMapping("/deleteRole")
public JSONObject deleteRole(@RequestBody JSONObject requestJson) {
CommonUtil.hasAllRequired(requestJson, "roleId");
return userService.deleteRole(requestJson);
}
基于RBAC的数据库设计
sys_role:用户组/角色(定义系统需要的权限分级,如CEO、HR、员工,表中使用不同的id以区分的角色)
sys_user:用户表(表中放置系统中所有用户的信息,表中的id用于设置主键,由于角色与用户是一对多的关系,role_id用于与角色表中的id进行关联,以实现用户获取其对应的角色)
sys_permission:权限表,用户登录后,获取角色,角色表对应permission中id来获取角色可以使用的操作权限,
sys_role_permission:角色权限表(表中permission_id对应sys_permission表中id,表中有role_id和permission_id,来配置角色中的操作权限)
- HBase 的表结构
- 微信小程序示例 - 表单
- TensorFlow中的feed与fetch
- TensorFlow中常量与变量的基本操作演示
- 如何写出好的 JavaScript —— 浅谈 API 设计
- 分析DAO的漏洞
- Phoenix - Hbase与SQL
- 2018年,让你的数据库变更快的十个建议
- Hadoop如何使用Zookeeper来保障高可用?
- HTTP 压力测试工具 wrk
- 理解 Linux shell 中的一个方言:2>&1
- 大家知道什么是git中的 .gitignore吗?
- MySQL 8.0 新特性 :隐藏索引 Invisible Indexes
- 如何监控MySQL的复制延迟?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法