• SpringSecurity之授权


    SpringSecurity之授权

    1. 写在前面的话

    此文并非教程, 而是个人学习SpringSecurity时的记录以及一些踩坑解决

    如果能帮到大家, 那就让人非常开心了

    另外, SpringSecurity的授权分为web授权和方法授权, 本文只说明了web授权, 方法授权实际上就是SpringSecurity控制对方法和接口的访问, 需要学习的小伙伴可以自行学习, 笔者在今后的工作中如果用到的话也会回来添加相关内容

    好了, 让我们看看笔者的一些学习心得吧

    2. web授权

    个人认为授权较之认证简单了许多, 大概是本人在认证把该踩的坑都踩了一遍吧...

    首先, 我们需要建立数据库

    1. 建库

    我们需要以下的几张表

    image-20201124100602850

    • 角色表

      image-20201124100621949

    • 权限表

      image-20201124100632156

    • 用户表(这个在认证中已经建立了)

    • 角色权限关联表 (此处为管理员有1和2两个权限)

      image-20201124100719214

    • 用户角色关联表

      image-20201124100823160

      • 关联表是为了方便拓展, 正常的业务都是这样的, 存在一对多和多对多的关系

    2. 添加查询权限的接口

    权限的信息也是存放在UserDetails中的, 因此我们也要实现通过用户id查询用户对应权限的功能

    • 建立对应的实体类(此处省略)

      image-20201124101044063

    • 添加接口以及其实现类

      • dao

        //根据用户id查询用户权限
        List<PermissionDTO> getPermissionByUserId(String userId);
        
      • service接口

        List<PermissionDTO> getPermissionByUserId(String userId);
        
      • 接口实现类

        @Override
        public List<PermissionDTO> getPermissionByUserId(String userId) {
            return userMapper.getPermissionByUserId(userId);
        }
        
    • 在xml中添加查询的sql

      <!--根据用户id查询用户权限-->
      <select id="getPermissionByUserId" parameterType="string" resultType="permissionDTO">
          select *
          from t_permission
          where `id` in (
              select permission_id from t_role_permission
              where role_id = (
                  select role_id from t_user_role
                  where user_id = #{userId}
              )
          )
      </select>
      
      • 注意, 这里我们使用了子查询, 同时, 由于会查出多条结果, 我们要酌情考虑是否要使用 in 语句, 将多个结果放在select语句的条件中

    3. 前端页面的编写

    我们使用的是 layui的默认后端模板

    • 模板的定义

      先编写一个后台模板文件

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
      <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
          <title>layout 后台大布局 - Layui</title>
          <link rel="stylesheet" th:href="@{css/layui.css}">
      </head>
      <body class="layui-layout-body">
      <div class="layui-layout layui-layout-admin">
          <div class="layui-header">
              <div class="layui-logo">layui 后台布局</div>
              <!-- 头部区域(可配合layui已有的水平导航) -->
              <ul class="layui-nav layui-layout-left">
                  <li class="layui-nav-item"><a href="">控制台</a></li>
                  <li class="layui-nav-item"><a href="">商品管理</a></li>
                  <li class="layui-nav-item"><a href="">用户</a></li>
                  <li class="layui-nav-item">
                      <a href="javascript:;">其它系统</a>
                      <dl class="layui-nav-child">
                          <dd><a href="">邮件管理</a></dd>
                          <dd><a href="">消息管理</a></dd>
                          <dd><a href="">授权管理</a></dd>
                      </dl>
                  </li>
              </ul>
              <ul class="layui-nav layui-layout-right">
                  <li class="layui-nav-item">
                      <a href="javascript:;">
                          <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
                          <span sec:authentication="principal.username"></span>
                      </a>
                      <dl class="layui-nav-child">
                          <dd><a href="">基本资料</a></dd>
                          <dd><a href="">安全设置</a></dd>
                      </dl>
                  </li>
                  <li class="layui-nav-item"><a id="logout" href="javascript:void(0);" onclick="logout()">退了</a></li>
              </ul>
          </div>
      
          <div class="layui-side layui-bg-black">
              <div class="layui-side-scroll">
                  <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
                  <ul class="layui-nav layui-nav-tree" lay-filter="test">
                      <li class="layui-nav-item layui-nav-itemed">
                          <a class="" href="javascript:;">所有商品</a>
                          <dl class="layui-nav-child">
                              <dd><a href="javascript:;">列表一</a></dd>
                              <dd><a href="javascript:;">列表二</a></dd>
                              <dd><a href="javascript:;">列表三</a></dd>
                              <dd><a href="">超链接</a></dd>
                          </dl>
                      </li>
                      <li class="layui-nav-item">
                          <a href="javascript:;">解决方案</a>
                          <dl class="layui-nav-child">
                              <dd><a href="javascript:;">列表一</a></dd>
                              <dd><a href="javascript:;">列表二</a></dd>
                              <dd><a href="">超链接</a></dd>
                          </dl>
                      </li>
                      <li class="layui-nav-item"><a href="">云市场</a></li>
                      <li class="layui-nav-item"><a href="">发布商品</a></li>
                  </ul>
              </div>
          </div>
      
          <!--    <div class="layui-body">-->
          <!--        &lt;!&ndash; 内容主体区域 &ndash;&gt;-->
          <!--        <div style="padding: 15px;">内容主体区域</div>-->
          <!--    </div>-->
      
          <div class="layui-footer">
              <!-- 底部固定区域 -->
              © layui.com - 底部固定区域
          </div>
      </div>
      <script type="text/javascript" th:src="@{js/jquery.min.js}"></script>
      <script type="text/javascript" th:src="@{js/jquery-ui.min.js}"></script>
      <script type="text/javascript" th:src="@{js/jquery.mockjax.js}"></script>
      <script th:src="@{layui.js}"></script>
      <script>
          //JavaScript代码区域
          layui.use('element', function () {
              var element = layui.element;
      
          });
      
          function logout() {
              layui.use('layer', function () {
                  //退出登录
                  layer.confirm('确定要退出么?', {icon: 3, title: '提示'}, function (index) {
                      //do something
                      let url = '/logout';
                      $.ajax({
                          url: url,
                          type: "post",
                          dataType: "json",
                          contentType: "application/json;charset=utf-8",
                          success: function (data) {
                              alert("进入success---");
                              let code = data.code;
                              let url = data.url;
                              let msg = data.msg;
                              if (code == 203) {
                                  alert(msg);
                                  window.location.href = url;
                              } else {
                                  alert("未知错误!");
                              }
                          },
                          error: function (xhr, textStatus, errorThrown) {
                              alert("进入error---");
                              alert("状态码:" + xhr.status);
                              alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。
                              alert("错误信息:" + xhr.statusText);
                              alert("返回响应信息:" + xhr.responseText);//这里是详细的信息
                              alert("请求状态:" + textStatus);
                              alert(errorThrown);
                              alert("请求失败");
                          }
                      });
                      layer.close(index);
                  });
              });
          }
      </script>
      </body>
      </html>
      
      • 注意
        • 我们为了获得SpringSecurity中的用户名, 使用了SpringSecurity的thymeleaf方言, 可以实现细粒度的控制(依据权限是否显示某些标签)
        • 引入命名空间, 这样就有代码提示了(不引入其实也无所谓~) xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
    • 测试用页面

      为了测试权限, 我们定义了三个按钮, 分别对应三个权限(我们在下一节的SpringSecurity配置类中可以看到)

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>页面</title>
      </head>
      <body class="layui-layout-body">
      
      <div th:include="content/layout"></div>
      
      <div class="layui-layout layui-layout-admin">
          <div class="layui-body">
              <!-- 内容主体区域 -->
              <div style="padding: 15px;">成功应用模板!</div>
              <div class="layui-btn-container">
                  <button type="button" class="layui-btn" onclick="btnClick1()">按钮一</button>
                  <button type="button" class="layui-btn" onclick="btnClick2()">按钮二</button>
                  <button type="button" class="layui-btn" onclick="btnClick3()">按钮三</button>
              </div>
          </div>
      </div>
      <script>
          $.ajaxSetup({
              type: "post",
              dataType: "json",
              contentType: "application/json;charset=utf-8",
              success: function (data) {
                  let code = data.code;
                  let url = data.url;
                  let msg = data.msg;
                  if (code == 204) {
                      alert(msg);
                      window.location.href = url;
                  } else {
                      alert("未知错误!");
                  }
              },
              error: function (xhr, textStatus, errorThrown) {
                  alert("进入error---");
                  alert("状态码:" + xhr.status);
                  alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。
                  alert("错误信息:" + xhr.statusText);
                  alert("返回响应信息:" + xhr.responseText);//这里是详细的信息
                  alert("请求状态:" + textStatus);
                  alert(errorThrown);
                  alert("请求失败");
              }
          });
      
          function btnClick1() {
              let url = "/toR1";
              $.ajax({
                  url: url,
                  type: "post",
                  dataType: "json",
                  contentType: "application/json;charset=utf-8",
      
              });
          }
      
          function btnClick2() {
              let url = "/toR2";
              $.ajax({
                  url: url,
                  type: "post",
                  dataType: "json",
                  contentType: "application/json;charset=utf-8",
      
              });
          }
      
          function btnClick3() {
              let url = "/toR3";
              $.ajax({
                  url: url,
                  type: "post",
                  dataType: "json",
                  contentType: "application/json;charset=utf-8",
      
              });
          }
      </script>
      </body>
      </html>
      

    4. SpringSecurity配置

    //授权
    http
            .authorizeRequests()
            .antMatchers("/r/r1").hasAnyAuthority("p1")
            .antMatchers("/r/r2").hasAnyAuthority("p2")
            .antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")
            .antMatchers("/r/**").authenticated().anyRequest().permitAll();
    

    在配置类中配置授权的规则

    注意

    • Authority和Role都是角色管理, 区别是Role会加一个 ROLE_ 前缀, 具体的区别可以在网上自行查看, 一般来说, 我们使用Authority就行了

    5. Controller配置

    • RestController处理AJAX
    package com.wang.spring_security_framework.controller;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    public class RestfulJumpController {
        @RequestMapping("/toR1")
        public String toR1Page() {
            String url = "/r/r1";
            String code = "204";
            String msg = "即将跳转!";
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("url", url);
            resultMap.put("code", code);
            resultMap.put("msg", msg);
            return JSON.toJSONString(resultMap);
        }
    
        @RequestMapping("/toR2")
        public String toR2Page() {
            String url = "/r/r2";
            String code = "204";
            String msg = "即将跳转!";
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("url", url);
            resultMap.put("code", code);
            resultMap.put("msg", msg);
            return JSON.toJSONString(resultMap);
        }
    
        @RequestMapping("/toR3")
        public String toR3Page() {
            String url = "/r/r3";
            String code = "204";
            String msg = "即将跳转!";
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("url", url);
            resultMap.put("code", code);
            resultMap.put("msg", msg);
            return JSON.toJSONString(resultMap);
        }
    
    }
    

    我们用RestController处理了Ajax的跳转请求, 并返回了JSON信息, 让前端进行url跳转

    • 测试的授权Controller

    这里的Controller用于显示对应的资源目录下的一些内容, 其中我们从会话中获取了当前登录的用户名

    package com.wang.spring_security_framework.controller;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/r")
    public class AuthorizeTestController {
    
        //从会话中获取当前登录用户名
        private String getUserName(){
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            //未登录, 返回null
            if(!authentication.isAuthenticated()) {
                return null;
            }
            Object principal = authentication.getPrincipal();
            String username;
            if (principal instanceof UserDetails) {
                username = ((UserDetails) principal).getUsername();
            } else {
                username = principal.toString();
            }
            return username;
        }
    
        @RequestMapping("/r1")
        public String R1() {
            return getUserName() + "访问资源1";
        }
    
        @RequestMapping("/r2")
        public String R2() {
            return getUserName() + "访问资源2";
        }
    
        @RequestMapping("/r3")
        public String R3() {
            return getUserName() + "访问资源3";
        }
    }
    

    3. 结语

    本篇看起来很简单, 事实上也确实很简单......

    主要需要理解的是数据库的建立以及关联表的处理, 其中的SQL语句才是最难搞的

    访问没有授权的页面, 会爆出 405 错误, 我们可以自定义错误页面来处理这种错误(此处就不叙述了, 属于SpringBoot的内容~)

    欢迎小伙伴批评与交流~

  • 相关阅读:
    FineBI与power BI,一个是国外风生水起的微软巨头,一个是方兴未艾的国产BI厂商领导者
    Linux下的crontab定时执行任务命令详解举例
    手把手教你搭建SSH框架(Eclipse版)
    一、连接池的定义
    centos安装sftp服务win搭建 sftp 服务器
    前往阿里云的企业优惠活动页面
    世界可能是思想最为混乱的时候,无论你说什么
    python面向对象编程class1
    Python 文件I/O 文件读写模式r,r+,w,w+,a,a+的区别
    Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里
  • 原文地址:https://www.cnblogs.com/wang-sky/p/14028896.html
Copyright © 2020-2023  润新知