Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制

SpringBoot整合Shiro流程

导入Shiro,Mybatisplus依赖


        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>1.5.0version>
        dependency>

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.2.0version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.2.0version>
        dependency>

数据库三张表

用户表

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第1张图片

角色表

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第2张图片

权限表

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第3张图片

Shiro权限认证

Shiro 有三大核心组件,即 Subject、SecurityManager 和 Realm

Subject 为认证主体
包含 PrincipalsCredentials两个信息。我们看下两者的具体含义。

Principals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。

Credentials:代表凭证。常见的有密码,数字证书等等。

说白了,两者代表了需要认证的内容,最常见的便是用户名、密码了。比如用户登录时,通过 Shiro 进行身份认证,其中就包括主体认证。

SecurityManager 为安全管理员
这是 Shiro 架构的核心,是 Shiro 内部所有原件的保护伞。项目中一般都会配置 SecurityManager,开发人员将大部分精力放在了 Subject 认证主体上,与 Subject 交互背后的安全操作,则由 SecurityManager 来完成。

Realm 是一个域
它是连接 Shiro 和具体应用的桥梁。当需要与安全数据交互时,比如用户账户、访问控制等,Shiro 将会在一个或多个 Realm 中查找。我们可以把 Realm 看作 DataSource,即安全数据源。一般,我们会自己定制 Realm,下文会详细说明。

Shiro 权限认证
权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源。在权限认证中,最核心的三个要素是:权限、角色和用户。

权限(Permission):即操作资源的权利,比如访问某个页面,以及对某个模块的数据进行添加、修改、删除、查看操作的权利。
角色(Role):指的是用户担任的角色,一个角色可以有多个权限。
用户(User):在 Shiro 中,代表访问系统的用户,即上面提到的 Subject 认证主体

整合步骤一. 自定义Realm

package top.flya.shiro;

import org.apache.shiro.SecurityUtils;
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 top.flya.shiro.entity.User;
import top.flya.shiro.service.UserService;

import javax.annotation.Resource;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: 风离
 * @Date: 2021/08/09/6:15
 * @Description:
 *  doGetAuthorizationInfo() 方法:为当前登录成功的用户----"授权"---权限和分配角色。
 *  doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取--"认证"---信息。
 */
public class MyRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;


    @Override  //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取用户名
        String username = (String) principalCollection.getPrimaryPrincipal();
        System.err.println("进入自定义的Realm层的授权层了哦,并且用户名为:"+username);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 给该用户设置角色,角色信息存在 t_role 表中取
        authorizationInfo.setRoles(userService.getRoles(username));
        // 给该用户设置权限,权限信息存在 t_permission 表中取
        authorizationInfo.setStringPermissions(userService.getPermissions(username));
        System.out.println("授权信息为:"+authorizationInfo);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 根据 Token 获取用户名,如果您不知道该 Token 怎么来的,先可以不管,下文会解释
        String username = (String) authenticationToken.getPrincipal();
        System.err.println("进入自定义的Realm层的认证层了哦,并且用户名为:"+username);
        // 根据用户名从数据库中查询该用户
        User user = userService.getByUsername(username);
        System.err.println("该用户为"+user);
        if(user != null) {
            // 把当前用户存到 Session 中
            SecurityUtils.getSubject().getSession().setAttribute("user", user);
            // 传入用户名和密码进行身份认证,并返回认证信息
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
            System.err.println("认证信息为 :"+authcInfo);
            return authcInfo;
        } else {
            return null;
        }
    }

}

验证身份时需先根据用户输入的用户名从数据库中查出对应的用户,这时还未涉及到密码,也就是说即使用户输入的密码不正确,照样可以查询出该用户

然后,将该用户的相关信息封装到 authcInfo 中并返回给 Shiro。接下来就该 Shiro 上场了,将封装的用户信息用户的输入信息(用户名、密码)进行对比、校验(注意,这里对密码也要进行校验)。校验通过则允许用户登录,否则跳转到指定页面。

同理,权限验证时,也需先根据用户名从数据库中获取其对应的角色和权限,将其封装到 authorizationInfo 并返回给 Shiro

整合步骤二. 配置ShiroConfig

Shiro 配置一环套一环,遵循从 Realm 到 SecurityManager 再到 Filter 的过程

在Filter中要配置: 先认证身份 然后再认证权限

默认登录的 URL:身份认证失败会访问该 URL。
认证成功之后要跳转的 URL。
权限认证失败后要跳转的 URL。
需要拦截或者放行的 URL:这些都放在一个 Map 中。(静态资源等

package top.flya.shiro.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.flya.shiro.MyRealm;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: 风离
 * @Date: 2021/08/09/6:28
 * @Description:
 */
@Configuration
public class ShiroConfig {

    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * 注入自定义的 Realm
     * @return MyRealm
     */
    @Bean
    public MyRealm myAuthRealm() {
        MyRealm myRealm = new MyRealm();
        logger.info("====myRealm注册完成=====");
        return myRealm;
    }

    /**
     * 注入安全管理器
     * @return SecurityManager
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        // 将自定义 Realm 加进来
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
        logger.info("====securityManager注册完成====");
        return securityManager;
//        return null;
    }

    /**
     * 注入 Shiro 过滤器
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean // SecurityManager securityManager
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        // 设置自定义的 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 设置默认登录的 URL,身份认证失败会访问该 URL
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置成功之后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // 设置未授权界面,权限认证失败会访问该 URL
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        // LinkedHashMap 是有序的,进行顺序拦截器配置
        Map<String,String> filterChainMap = new LinkedHashMap<>();

        // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
        filterChainMap.put("/css/**", "anon");
        filterChainMap.put("/imgs/**", "anon");
        filterChainMap.put("/js/**", "anon");
        filterChainMap.put("/swagger-*/**", "anon");
        filterChainMap.put("/swagger-ui.html/**", "anon");
        // 登录 URL 放行
        filterChainMap.put("/login", "anon");

        // 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
        filterChainMap.put("/user/admin*", "authc");
        // “/user/student” 开头的用户需要角色认证,是“admin”才允许
        filterChainMap.put("/user/student*/**", "roles[admin]");
        // “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
        filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");

        // 配置 logout 过滤器
        filterChainMap.put("/logout", "logout");

        // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        logger.info("====shiroFilterFactoryBean注册完成====");
        return shiroFilterFactoryBean;
    }

}

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第4张图片

Controller层与自定义Realm和ShiroConfig产生关联

package top.flya.shiro.controller;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;
import top.flya.shiro.entity.User;

import javax.servlet.http.HttpServletRequest;

/**
 * 

* 前端控制器 *

* * @author 风离 * @since 2021-08-09 */
@RestController @RequestMapping("/user") public class UserController { /** * 身份认证测试接口 * @param request * @return */ @RequestMapping("/admin") public String admin(HttpServletRequest request) { Object user = request.getSession().getAttribute("user"); return "success"; } /** * 角色认证测试接口 * @param request * @return */ @RequestMapping("/student") public String student(HttpServletRequest request) { return "success"; } /** * 权限认证测试接口 * @param request * @return */ @RequestMapping("/teacher") public String teacher(HttpServletRequest request) { return "success"; } /** * 用户登录接口 * @param user user * @param request request * @return string */ @PostMapping("/login") public String login(User user, HttpServletRequest request) { System.err.println(user.getUsername()+"-----"+user.getPassword()); // 根据用户名和密码创建 Token UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); // 获取 subject 认证主体 Subject subject = SecurityUtils.getSubject(); try{ // 开始认证,这一步会跳到我们自定义的 Realm 中 subject.login(token); System.err.println("认证成功之后就会回到Controller层啦"); request.getSession().setAttribute("user", user); return "认证成功啦"; }catch(Exception e){ e.printStackTrace(); System.err.println("认证失败之后也会回到Controller层啦"); request.getSession().setAttribute("user", user); request.setAttribute("error", "用户名或密码错误!"); return "用户名或者密码错误,认证失败"; } } }

接下来开始使用 Shiro 进行认证。

首先,设计如下几个接口。

接口一:使用 http://localhost:8080/user/admin 进行身份认证。
接口二:使用 http://localhost:8080/user/student 进行角色认证。
接口三:使用 http://localhost:8080/user/teacher 进行权限认证。
接口四:使用 http://localhost:8080/user/login 实现用户登录。

我们先了解下认证的流程。

流程一:直接访问接口一(此时还未登录),认证失败,跳转到 login.html 页面让用户登录。登录时请求接口四,实现用户登录,此时 Shiro 已经保存了用户信息。 (// 把当前用户存到 Session 中 SecurityUtils.getSubject().getSession().setAttribute("user", user);
流程二:再次访问接口一(此时用户已经登录),认证成功,跳转到 success.html 页面,展示用户信息。
流程三:访问接口二,测试角色认证是否成功。
流程四:访问接口三,测试权限认证是否成功。

流程分析

整个处理过程是这样的。

首先,根据前端传来的用户名和密码,创建一个 Token。

然后,使用 SecurityUtils 创建认证主体。

紧接着,调用 subject.login(token) 进行身份认证——注意,这里传入了刚刚创建的 Token,如注释所述,这一步会跳转入自定义的 Realm,访问 doGetAuthenticationInfo 方法,开始身份认证。

最后,启动项目,测试一下。在浏览器中请求:http://localhost:8080/user/admin, 首先进行身份认证,此时未登录,会跳转至 IndexController 中 /login 接口处,呈现出 login.html 页面供我们登录。

接着,使用用户名(fl1906)、密码(rewq4321)登录,在浏览器中请求:http://localhost:8080/user/student 接口,开始角色认证,因为数据库中 fl1906的用户角色是 admin,和配置中的吻合,认证通过。

我们再请求:http://localhost:8080/user/teacher 接口,进行权限认证,因为数据库中fl1906的用户权限为 user:*,满足配置中的 user:create,认证通过。

接下来,我们点击“退出”,系统会将该用户注销,让我们重新登录。我们尝试使用 csdn2 用户登录,重复上述操作,进行角色认证和权限认证时,因为数据库中 csdn2 用户的角色和权限与配置中的不同,所以认证失败。

SpringBoot整合Mybatisplus

配置Mybatisplus的映射文件位置及数据库账号密码数据源等

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  global-config:
    banner: false

spring:
  datasource:
    username: root
    password: 33333333
    url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver

配置代码生成器规则

参考之前我写的文章

注意一下生成的Mapper类如果上面没有加 @Mapper注解 则需要配置一个Mapper文件扫描器 可写在MybatisPlusConfig中或者写在启动类上 @MapperScan("top.flya.shiro.mapper")

运行测试结果截图

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第5张图片

Day13-SpringBoot整合Shiro及Mybatisplus实现权限控制_第6张图片

你可能感兴趣的:(SpringBoot,数据库,shiro,spring,java,spring,boot)