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的內容~)

歡迎小伙伴批評與交流~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM