spring boot + shiro 实现角色权限控制 您所在的位置:网站首页 redis缓存用户权限表 spring boot + shiro 实现角色权限控制

spring boot + shiro 实现角色权限控制

2023-07-19 04:20| 来源: 网络整理| 查看: 265

spring boot + shiro 实现角色权限控制 简介

Apache Shiro 是一个强大并且易于使用的java安全框架,可以用与身份验证、授权、加密和会话管理。同样的框架还有spring security,spring security有很好的平台支持,和活跃的社区氛围,并且对spring完美兼容,但是使用难度上,远远超过shiro。

身份认证:用户身份识别。授权:用户权限控制。知道来的人有没有资格进来。加密:加密敏感数据,防止偷窥。比如密码md5两次加密。会话管理:用户的时间敏感的状态信息。

shiro上手快 ,控制粒度可糙可细 ,自由度高,可以独立运行。

类说明 类名说明Subject可以理解为与shiro打交道的对象,该对象封装了一些对方的信息,shiro可以通过subject拿到这些信息SecurityManagerShiro的总经理,通过指使Authorizer和Authenticator等对subject进行授权和身份验证等工作Realm管理着一些如用户、角色、权限等重要信息,Shiro中所需的这些重要信息都是从Realm这里获取的,Realm本质上就是一个重要信息的数据源Authenticator认证器,负责Subject的认证操作,认证过程就是根据Subject提供的信息通过Realm查询到相关信息,然后做对比,支持扩展Authorizer授权器,控制着Subject对服务资源的访问权限SessionManager用于管理Session,这个Session可以是web的也可以不是web的SessionDao把Session的 CRUD和存储介质联系起来的工具,存储介质可以是数据库,也可以是缓存,比如把session放到redis里面CacheManager缓存控制器,Realm管理的数据(用户、角色、权限)可以放到缓存里由CacheManager管理,提高认证授权等的速度Cryptography加密组件,Shiro提供了很多加解密算法的组件

Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)代表Shiro应用安全的四大基石。

实践开发 开发环境 spring boot 2.3.0.RELEASEmaven 3.6.0jdk 1.8mysql 5.7intellij idea 设计技术

spring boot + shiro + swagger + mybatis

maven 4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.0.RELEASE com.felton.springboot shrio 0.0.1-SNAPSHOT shrio Demo project for Spring Boot 1.8 5.1.46 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.apache.shiro shiro-spring 1.4.2 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test mysql mysql-connector-java ${mysql.version} org.mybatis.generator mybatis-generator-core 1.3.5 io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.2 org.springframework.boot spring-boot-maven-plugin org.mybatis.generator mybatis-generator-maven-plugin 1.3.5 ${basedir}/src/main/resources/mybatis-generator.xml true true mysql mysql-connector-java ${mysql.version} application.yml配置

主要是数据库配置

spring: datasource: url: jdbc:mysql://localhost:3306/shrio?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver #mybatis mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true 数据库脚本

数据库表结构,标准的RBAC用户角色权限结构。

shiro_table

/* Navicat Premium Data Transfer Source Server : 本地 Source Server Type : MySQL Source Server Version : 50729 Source Host : localhost:3306 Source Schema : shrio Target Server Type : MySQL Target Server Version : 50729 File Encoding : 65001 Date: 16/06/2020 15:33:00 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for permission -- ---------------------------- DROP TABLE IF EXISTS `permission`; CREATE TABLE `permission` ( `permission_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `permission_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', `resource_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源类型,[menu|button]', `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源路径', `permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view', `parent_id` int(11) NULL DEFAULT NULL COMMENT '父编号', `available` int(255) NULL DEFAULT NULL COMMENT '是否可用,如果不可用将不会添加给用户', PRIMARY KEY (`permission_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of permission -- ---------------------------- INSERT INTO `permission` VALUES (1, '用户管理', 'menu', 'user/userList', 'user:view', 0, 1); INSERT INTO `permission` VALUES (2, '用户添加', 'button', 'user/userAdd', 'user:add', 1, 1); INSERT INTO `permission` VALUES (3, '用户删除', 'button', 'user/userDel', 'user:del', 1, 1); -- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `role` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色标识程序中判断使用,如\"admin\",这个是唯一的:', `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述,UI界面显示使用', `available` int(255) NULL DEFAULT NULL COMMENT '是否可用,如果不可用将不会添加给用户', PRIMARY KEY (`role_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of role -- ---------------------------- INSERT INTO `role` VALUES (1, 'admin', '管理员', 1); -- ---------------------------- -- Table structure for role_permission_relation -- ---------------------------- DROP TABLE IF EXISTS `role_permission_relation`; CREATE TABLE `role_permission_relation` ( `permission_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, PRIMARY KEY (`permission_id`, `role_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of role_permission_relation -- ---------------------------- INSERT INTO `role_permission_relation` VALUES (1, 1); INSERT INTO `role_permission_relation` VALUES (2, 1); -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录用户名', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称(昵称或者真实姓名,根据实际情况定义)', `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码', `state` int(255) NULL DEFAULT NULL COMMENT '用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', `expired_date` datetime(0) NULL DEFAULT NULL COMMENT '过期日期', PRIMARY KEY (`user_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'admin', '管理员', '597042ee35c3a0a937c9ef4afff842f2', 1, '2020-06-10 16:17:04', '[email protected]', '2020-06-17 16:17:16'); -- ---------------------------- -- Table structure for user_role_relation -- ---------------------------- DROP TABLE IF EXISTS `user_role_relation`; CREATE TABLE `user_role_relation` ( `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, PRIMARY KEY (`user_id`, `role_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user_role_relation -- ---------------------------- INSERT INTO `user_role_relation` VALUES (1, 1); SET FOREIGN_KEY_CHECKS = 1; 目录结构

image-20200616154225112

这里使用了mbg自动生成model、dao、mapper。

配置realm

添加一个MyShiroRealm类,并继承AuthorizingRealm,并且需要实现两个方法。

doGetAuthenticationInfo:实现用户认证,通过服务加载用户信息并构造认证对象返回。

doGetAuthorizationInfo:实现权限认证,通过服务加载用户角色和权限信息设置进去。

package com.felton.springboot.shrio.config; import com.felton.springboot.shrio.entity.Permission; import com.felton.springboot.shrio.entity.Role; import com.felton.springboot.shrio.entity.User; import com.felton.springboot.shrio.service.PermissionService; import com.felton.springboot.shrio.service.RoleService; import com.felton.springboot.shrio.service.UserService; import jdk.nashorn.internal.parser.Token; 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 org.springframework.context.annotation.Configuration; import java.util.List; /** * 类 名:com.felton.springboot.shrio.config.MyShrioRealm * 类描述:todo * 创建人:liurui * 创建时间:2020/6/10 11:23 * 修改人: * 修改时间: * 修改备注: * * @author liurui * @version 1.0 */ public class MyShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleservice; @Autowired private PermissionService permissionService; /** * 权限信息 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); List roleList = roleservice.getRoleListByUserName(user.getUserName()); List permissionList = null; if (roleList.size() > 0) { //添加角色 for (Role role : roleList) { authorizationInfo.addRole(role.getRole()); permissionList = permissionService.getPermissionListByRoleId(role.getRoleId()); for (Permission permission : permissionList) { //添加权限 authorizationInfo.addStringPermission(permission.getPermission()); } } } return authorizationInfo; } /** * 身份认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("【MyShiroRealm】身份认证"); String userName = (String) authenticationToken.getPrincipal(); User user = userService.findByUserName(userName); if (user == null) { return null; } if (user.getState() == 0) { // 用户被管理员锁定抛出异常 throw new AuthenticationException(); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user, user.getPassword(), getName() ); return authenticationInfo; } } 配置shiro

这里使用的是spring boot,所以抛弃传统的xml配置,改为编写配置类ShiroConfig。在项目启动的时候,实现方法会给注入到spring容器中。

package com.felton.springboot.shrio.config; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 类 名:com.felton.springboot.shrio.config.ShiroConfig * 类描述:todo * 创建人:liurui * 创建时间:2020/6/10 11:24 * 修改人: * 修改时间: * 修改备注: * * @author liurui * @version 1.0 */ @Configuration public class ShiroConfig { /** * 将自己的验证方式加入容器 * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } /** * 权限管理,配置主要是realm的管理认证 * @return */ @Bean DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(myShiroRealm()); return manager; } /** * 凭证匹配器(密码校验交给Shiro的SimpleAuthenticationInfo进行处理) * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 加密方式 hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 加密两次 hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager()); Map filterMap = new HashMap(); // 登出 filterMap.put("/logout", "logout"); // 对所有用户认证 filterMap.put("/**", "authc"); filterMap.put("/swagger**/**", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/v2/**", "anon"); // 登录 bean.setLoginUrl("/login"); // 首页 bean.setSuccessUrl("/index"); // 未授权页面,认证不通过跳转 bean.setUnauthorizedUrl("/403"); bean.setFilterChainDefinitionMap(filterMap); return bean; } /** * 开启shiro aop 注解支持 * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { System.out.println("错误"); SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); mappings.setProperty("UnauthorizedException", "/403"); resolver.setExceptionMappings(mappings); resolver.setDefaultErrorView("error"); resolver.setExceptionAttribute("exception"); return resolver; } } service层的简单使用 package com.felton.springboot.shrio.service.impl; import com.felton.springboot.shrio.model.LoginResult; import com.felton.springboot.shrio.service.LoginService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Service; /** * 类 名:com.felton.springboot.shrio.service.impl.LoginServiceImpl * 类描述:todo * 创建人:liurui * 创建时间:2020/6/10 11:08 * 修改人: * 修改时间: * 修改备注: * * @author liurui * @version 1.0 */ @Service public class LoginServiceImpl implements LoginService { @Override public LoginResult login(String userName, String password) { LoginResult result = new LoginResult(); if (userName == null || userName.isEmpty()) { result.setLogin(false); result.setResult("用户名为空"); return result; } String msg = ""; UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try { Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token); Session session = currentUser.getSession(); // 设置会话session session.setAttribute("userName", userName); result.setLogin(true); return result; } catch (Exception e) { e.printStackTrace(); } result.setLogin(false); result.setResult(""); return result; } @Override public void logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); } }

大致的代码都在这里,如果需要完整的项目地址可以到这里

代码量也不算很多,但是要真正的理解shiro的执行过程还需要对其源码进行分析。简单的走了一下代码的执行过程。

用户的每一次请求都会执行自定义的AuthorizingRealm类,如果没有session记录的会执行身份认证doGetAuthenticationInfo,如果已经登录并且有session会话记录,则直接执行权限认证doGetAuthorizationInfo

登录过程

image-20200616164215983

执行过程

shiro

权限判断

如果在数据库中匹配到指定的权限,返回true值,由于每次访问接口都会触发doGetAuthorizationInfo方法,从而会经常性的查找数据库,如果用户数量大,查找权限多,会导致接口响应慢,这里可以改造使用redis缓存数据库来缓存权限信息。

image-20200616164822581

无权限

如果用户无权限,isPermitted会返回false,ModularRealmAuthorizer中会抛出无权限异常UnauthorizedException,而在ShiroConfig.java中已配置UnauthorizedException的异常捕获,并重定向到/403接口中。

image-20200616165418692

FAQ shiro的注解使用

推荐一篇文章,里边说了很明白,地址

主要用到的是@RequiresPermissions,注解中的值分为三种形式

普通形式 value只是一个普通的字符串,例如 @RequiresPermissions(“action”)多层形式 value中的字符串,使用冒号:来分割字符串的内容,例如:@RequiresPermissions(“user:add”) 一般第一个字符串是操作对象的权限领域,第二的是操作的权限类型多权限多层心事 value由多个多层形式的字符串组成,例如:@RequiresPermissions(“user:view”,"user:add)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有