springboot整合shiro+mybatis和shiro认证和授权过程的源码流程详解以及全面认识shiro

本文介绍如何使用Apache Shiro框架搭建权限管理系统,包括数据库表结构设计、依赖引入、配置类编写、自定义Realm实现及异常处理等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shiro架构图:

在这里插入图片描述

1.建表,五张表,如下:

1.1.用户表

CREATE TABLE `t_sys_user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `user_name` varchar(30) NOT NULL COMMENT '用户名',
  `user_password` varchar(128) NOT NULL COMMENT '用户密码',
  `salt` varchar(64) DEFAULT NULL COMMENT '加密盐',
  `user_phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `user_emai` varchar(20) DEFAULT NULL COMMENT '邮箱',
  `user_title` varchar(20) DEFAULT NULL COMMENT '职称',
  `creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',
  `creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',
  `creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',
  `updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',
  `updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `role_ids` varchar(200) DEFAULT NULL,
  `role_names` varchar(300) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;

1.2.用户角色表

CREATE TABLE `t_sys_user_role` (
  `user_role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户角色ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;

1.3.角色表

CREATE TABLE `t_sys_role` (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(100) NOT NULL COMMENT '角色名称',
  `role_code` varchar(100) NOT NULL COMMENT '角色编码',
  `creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',
  `creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',
  `creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',
  `updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',
  `updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `permission_ids` varchar(200) DEFAULT NULL,
  `permission_names` varchar(300) DEFAULT NULL,
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;


1.4.角色权限表

CREATE TABLE `t_sys_role_permission` (
  `role_permission_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色权限ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `permission_id` bigint(20) NOT NULL COMMENT '权限ID',
  PRIMARY KEY (`role_permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=78 DEFAULT CHARSET=utf8;


1.5.权限表

CREATE TABLE `t_sys_permission` (
  `permission_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
  `permission_name` varchar(100) NOT NULL COMMENT '权限名称',
  `permission_code` varchar(100) NOT NULL COMMENT '权限编码',
  `creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',
  `creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',
  `creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',
  `updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',
  `updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;


2.引入依赖:

 <!--引入shiro依赖包-->
 <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
     <version>1.4.0</version>
 </dependency>

3.编写shiro配置类ShiroConfig.java,如下:

说明:
shiro的配置类必须注入(自定义的Realm,DefaultWebSecurityManager,ShiroFilterFactoryBean,AuthorizationAttributeSourceAdvisor)四个Bean,否则实现不了shiro的认证和授权。

package com.lz.hehuorenservice.common.config;

import com.lz.hehuorenservice.common.bean.CustomRealm;
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;

/** Create by hyhweb on 2021/6/18 23:35 */
@Configuration
public class ShiroConfig {

  // 自定义的realm类为DefaultWebSecurityManager注入realm,实现登录认证和授权的业务代码
  @Bean
  CustomRealm customRealm() {
    return new CustomRealm();
  }
  // 安全管理器必须设置自定义的realm,才能把安全管理与业务代码的认证和权限关联起来,并且为ShiroFilterFactoryBean提供安全管理器的注入类。
  @Bean
  DefaultWebSecurityManager defaultWebSecurityManager() {
    DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
    defaultSecurityManager.setRealm(customRealm());
    return defaultSecurityManager;
  }
  // 此类为shiro注入带有自定义realm的安全管理器
  @Bean
  ShiroFilterFactoryBean shiroFilterFactoryBean() {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
    return shiroFilterFactoryBean;
  }

  // 此类是让权限相关操作生效的配置类,否则权限的注释和自定义的realm的doGetAuthorizationInfo()方法都失效。
  @Bean
  AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
        new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
    return authorizationAttributeSourceAdvisor;
  }
}

4.编写自定义的Realm,CustomRealm.java文件。如下:

说明:
自定义的realm,主要重写AuthorizingRealm的doGetAuthenticationInfo()和doGetAuthorizationInfo()方法,分别实现获取数据库的用户信息进行登录认证,以及获取数据库的权限实现授权。

package com.lz.hehuorenservice.common.bean;

import com.lz.hehuorenservice.system.dao.UserDao;
import com.lz.hehuorenservice.system.entity.User;
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.Set;

/** Create by hyhweb on 2021/6/18 23:56 */
public class CustomRealm extends AuthorizingRealm {
  @Autowired private UserDao userDao;

  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    // 从principalCollection中获取用户名,再根据用户名从数据库获取用户的权限,最好返回Set[]数据接口,这样就不需要再转换。
    // 需要自己编写代码实现获取用户权限的方法
    Set<String> permissionSet =
        userDao.getPermissionByUserName(principalCollection.getPrimaryPrincipal().toString());
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    simpleAuthorizationInfo.setStringPermissions(permissionSet);
    return simpleAuthorizationInfo;
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
      throws AuthenticationException {
    // 从authenticationToken中获取用户名,再根据用户名从数据库获取用户的数据。
    // 需要自己编写代码实现获取用户数据的方法
    User user = userDao.getUserByName(authenticationToken.getPrincipal().toString());
    if (user != null) {
      // 通过用户名和用户密码来实例化SimpleAuthenticationInfo对象,并返回simpleAuthenticationInfo。
      SimpleAuthenticationInfo simpleAuthenticationInfo =
          new SimpleAuthenticationInfo(user.getUserName(), user.getUserPassword(), getName());
      return simpleAuthenticationInfo;
    }
    return null;
  }
}

5.controller层的登录和权限配置,如下:

说明:
登录实现主要通过SecurityUtils.getSubject() 获取Subject,再执行 subject.login(usernamePasswordToken)方法, UsernamePasswordToken的实例化的实例作为参数。

package com.lz.hehuorenservice.common.controller;

import com.lz.hehuorenservice.common.response.ApiResult;
import com.lz.hehuorenservice.system.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

/** Create by hyhweb on 2021/6/11 22:13 */
@RestController
@RequestMapping("/test")
public class TestController {
  @PostMapping("/doLogin")
  public ApiResult doLogin() throws Exception {
    User user = new User();
    user.setUserName("test");
    user.setUserPassword("55");
    UsernamePasswordToken usernamePasswordToken =
        new UsernamePasswordToken(user.getUserName(), user.getUserPassword());
    Subject subject = SecurityUtils.getSubject();
    subject.login(usernamePasswordToken);
    return ApiResult.success(usernamePasswordToken.getPrincipal());
  }

  @GetMapping("/test1")
  @RequiresPermissions("add")
  public ApiResult test() throws Exception {

    return ApiResult.success("test");
  }
}

6.登录认证和授权异常处理,编写GlobalExceptionHandler.java,如下:

package com.lz.hehuorenservice.common.exception;

import com.lz.hehuorenservice.common.response.ApiResult;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;

import javax.validation.ValidationException;

/** Create by hyhweb on 2021/6/1 19:03 */
@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(AuthenticationException.class)
  public ApiResult authenticationExceptionHandler(AuthenticationException e) {
    return ApiResult.fail("登录失败,请重新登录");
  }

  @ExceptionHandler(IncorrectCredentialsException.class)
  public ApiResult authenticationExceptionHandler(IncorrectCredentialsException e) {
    return ApiResult.fail("登录账号或者密码错误,请重新登录");
  }

  @ExceptionHandler(AuthorizationException.class)
  public ApiResult authorizationExceptionHandler(AuthorizationException e) {
    return ApiResult.fail("权限不足,请联系管理员");
  }

  @ExceptionHandler(Exception.class)
  public ApiResult ExceptionError(Exception e) {
    return ApiResult.fail(e.getMessage());
  }
}

7.登录认证过程的主要工作流程:

7.1.从登录subject.login(usernamePasswordToken)到自定义的realm的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法,只是把用户的登录的用户名和密码传入到自定义的realm的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法中,让开发者通过用户名去获取数据库的用户数据,再通过SimpleAuthenticationInfo的实例化new SimpleAuthenticationInfo(user.getUserName(), user.getUserPassword(), getName())实例,返回SimpleAuthenticationInfo的实例给shiro继续使用。
在这里插入图片描述

7.2.自定义的realm的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法执行完之后,先把认证的数据缓存起来,然后在org.apache.shiro.authc.credential.SimpleCredentialsMatcher的doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)方法中,比较登录时生成的AuthenticationToken和oGetAuthenticationInfo()返回的AuthenticationInfo 比较密码是否相等。
在这里插入图片描述
7.3.org.apache.shiro.authc.credential.SimpleCredentialsMatcher的equals(Object tokenCredentials, Object accountCredentials)方法中,把密码转换成字节数组进行对比是否相等
在这里插入图片描述

8.授权过程的主要工作流程:

8.1.controller层的@RequiresPermissions(“add”)权限注释。
在这里插入图片描述

8.2.通过AOP流程把权限注释的权限解析出来:
说明:
经过一系列的注释解析和AOP流程,在org.apache.shiro.authz.aop.PermissionAnnotationHandler中的 assertAuthorized(Annotation a)方法把注释的权限解析出来。如下:
在这里插入图片描述

8.3.在org.apache.shiro.realm.AuthorizingRealm的isPermitted(PrincipalCollection principals, Permission permission) 方法中,再从AuthorizationInfo对象中获取权限列表(其实就是从自定义的realm的doGetAuthorizationInfo()方法返回的AuthorizationInfo中获取权限)
在这里插入图片描述
8.4.这时,注释的配置的权限和用户的权限列表都已经拿到,剩下的就是权限的比较,在用户的权限列表中是否包含注释@RequiresPermissions(“add”)的权限。

最终,在org.apache.shiro.authz.permission.WildcardPermission的implies(Permission p)方法中比较是不是用户的权限列表中是否包含注释的权限

 public boolean implies(Permission p) {
        if (!(p instanceof WildcardPermission)) {
            return false;
        } else {
            WildcardPermission wp = (WildcardPermission)p;
            List<Set<String>> otherParts = wp.getParts();
            int i = 0;

            for(Iterator var5 = otherParts.iterator(); var5.hasNext(); ++i) {
                Set<String> otherPart = (Set)var5.next();
                if (this.getParts().size() - 1 < i) {
                    return true;
                }

                Set<String> part = (Set)this.getParts().get(i);
                if (!part.contains("*") && !part.containsAll(otherPart)) {
                    return false;
                }
            }

            while(i < this.getParts().size()) {
                Set<String> part = (Set)this.getParts().get(i);
                if (!part.contains("*")) {
                    return false;
                }

                ++i;
            }

            return true;
        }
    }

在这里插入图片描述

9.shiro的权限管理的注解:

@RequiresAuthentication
@RequiresGuest
@RequiresPermissions(String[] value)
@RequiresRoles(String[] value)
@RequiresUser

10.shiro经常用到的安全管理器:

在这里插入图片描述

11.AuthenticationToken与登录接口的UsernamePasswordToken的关系,如下:

说明:
UsernamePasswordToken是AuthenticationToken的实现类,AuthenticationToken是可以拿到登录时缓存的用户名和密码。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值