這篇打算寫一下登陸權限驗證相關
說起來也都是淚,之前涉及權限的比較少所以這次准備起來就比較困難。
踩了好幾個大坑,還好最終都一一消化掉(這是廢話你沒解決你寫個什么勁 😂) 也補充了一下自己在權限知識的的空白,還是很欣慰的。
試着能把遭遇到的坑都寫出來 ,能耐有限 盡力吧
(因為我是基於上一篇的基礎上寫的 理論上可以拿着上一版的代碼直接用 當然我也會在最后放上這次的代碼
說是"理論上" 是因為這次遭遇的問題太多反復刪,改 很多地方我自己也沒有做對比 也記不住了😌)
好了,先從配置開始
配置
向pom.xml文件里追加數據庫用和Security用及mybatis用jar 如下
<!--spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <!--postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <!--mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency>
數據庫連接配置和端口號(我自己電腦上還有別的項目在跑避免多端口沖突 配置了端口號 9090)
############### ## DB ############### spring.datasource.url=jdbc:postgresql://localhost:5432/springboot
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
###############
## Server Port
###############
server.port = 9090
根據自己情況調整,換成其他數據庫也是改這兩個文件
使用的到的表,主要是用戶表和權限表,如下
CREATE TABLE public.mst_authorities ( user_id integer, authority character varying(256), permission_flag character varying(1), create_user character varying(256), create_date_time character varying(256), update_date_time character varying(256), CONSTRAINT authorities_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.mst_users (user_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ); CREATE TABLE public.mst_users ( user_id integer NOT NULL, login_id character varying(256), password character varying(256), display_name character varying(255), enabled boolean, CONSTRAINT users_pkey PRIMARY KEY (user_id) );
下面是我用的數據提供參考 上面是權限下面是用戶
畫面
先說一下登錄(login.html)畫面
1 <!DOCTYPE HTML> 2 <!-- thymeleaf 導入 --> 3 <html xmlns:th="http://www.thymeleaf.org" 4 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> 5 <head> 6 <title>登錄</title> 7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 8 <script type="text/javascript" src="../js/jquery-3.2.1.min.js" th:src="@{/js/jquery-3.2.1.min.js}"></script> 9 <link rel="stylesheet" href="../css/bootstrap.min.css" th:src="@{/css/bootstrap.min.css}" type="text/css"></link> 10 <script type="text/javascript"> 11 $(function() { 12 }) 13 </script> 14 <body> 15 <!-- common 的 定義好的 header模板 引用 參數 title --> 16 <!-- 17 Bootstrap的容器Class使用 18 container :用於固定寬度並支持響應式布局的容器 19 container-fluid :用於 100% 寬度,占據全部視口(viewport)的容器。 20 --> 21 <div class="container" th:replace="common :: header('login') "></div> 22 <div class="container"> 23 <!--Bootstrap 24 .row:行控制 一行有12列 25 .clo-md-4:列控制 表示 占 4列 26 .md-offfset-4:向右側偏移4 27 --> 28 <div class="row"> 29 <div class="col-md-4 col-md-offset-4"> 30 <div class="panel panel-default"> 31 <div class="panel-heading"> 32 <h3 class="panel-title">Login</h3> 33 </div> 34 <div class="panel-body"> 35 <form id="frmLogin" method="post" th:action="@{'/login'}" > 36 <div class="form-group form-inline"> 37 <label for="txtUserName" class="col-md-3 control-label" 38 style="text-align: right;">用戶名</label> 39 <div class="col-md-9"> 40 <input class="form-control" style="width:60%;" placeholder="請輸入用戶名 ∗" autofocus="autofocus" name="txtUserCd" type="text" value="" 41 th:id="usrCode" required="required"/> 42 </div> 43 </div> 44 <div class="form-group form-inline" style="padding-top:45px"> 45 <label for="txtUserPwd" class="col-sm-3 control-label" 46 style="text-align: right;">密碼</label> 47 <div class="col-md-9"> 48 <input class="form-control" style="width:60%;" placeholder="password ∗" name="txtUserPwd" type="password" value="" required="required" /> 49 </div> 50 </div> 51 <div class="form-group"> 52 <div class="col-md-offset-3 col-md-9"> 53 <div class="checkbox"> 54 <label> <input type="checkbox" />請記住我(未實現) 55 </label> 56 </div> 57 </div> 58 </div> 59 <div class="form-group"> 60 <div class="col-md-offset-3 col-md-5"> 61 <button class="btn btn-primary btn-block" type="submit" name="action" 62 value="login">登錄</button> 63 </div> 64 65 </div> 66 <div class="form-group"> 67 <div class=" col-md-12" style="padding-top:15px"> 68 <div class="alert alert-danger" role="alert" th:if="${loginError}"> 69 <strong>用戶名或者用戶密碼不正確,請重新輸入</strong> 70 </div> 71 </div> 72 </div> 73 </form> 74 75 </div> 76 </div> 77 </div> 78 </div> 79 </div> 80 81 <!-- common 的 定義好的 fotter模板引用 無參 --> 82 <div class="container" th:replace="common :: footer"></div> 83 </body> 84 </head> 85 </html>
說一下這個畫面遭遇的問題吧
坑一、L8,9 的資源引用
spring boot 有自己默認的資源引用文件夾(/META-INF/resources/ ,/resources/ ,/static/ ,/public/)所以用到的資源文件一定要這些
下面否則會讀取不到,當然自己也可以自己定義直接寫在application.properties文件里就好了,有一個問題就是 如果自己配置了默認的就不能用了
坑二、L40,48
這里的用戶名和密碼的控件的name是和后台傳給
formLogin().usernameParameter("txtUserCd") .passwordParameter("txtUserPwd") 這個兩個函數的參數要保持一致
在網上看到說一定要 用 username 和 password 看來也不是必需 但 保持一致 還是有必要的
坑三、這次demo里沒有用到但遭遇過 關於ajax 被攔截的問題
參考我這篇 Spring Security Ajax 被攔截
然后說一下登錄成功跳轉到的main_menu.html畫面
1 <!DOCTYPE HTML> 2 <html xmlns:th="http://www.thymeleaf.org" 3 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> 4 <head> 5 <title>MAIN MENU</title> 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 7 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 8 <script type="text/javascript" src="../js/jquery-3.2.1.min.js" th:src="@{/js/jquery-3.2.1.min.js}"></script> 9 <link rel="stylesheet" href="../css/bootstrap.min.css" th:src="@{/css/bootstrap.min.css}" type="text/css"></link> 10 11 </head> 12 <body style="margin:0;" > 13 <div class="container-fluid" th:replace="common :: header('MAIN MENU')"></div> 14 <div class="container" style="margin-top:50px;"> 15 <div class="row"> 16 <div class="col-md-offset-2 col-md-8"> 17 <div th:if="${#authorization.expression('hasAnyAuthority(''__${session.authorityKindMap}__'')')}"> 18 當前用戶權限:<div th:text="${session.authorityKindMap}">...</div> 19 </div> 20 21 <a href="stow_menu.html" th:href="@{/stow_menu.html}" sec:authorize="hasAnyAuthority('ROLE_USER','ROLE_ADMIN')" 22 class="btn btn-primary btn-lg btn-block" >MENU(普通以上權限)</a> 23 <a href="#" sec:authorize="!hasAnyAuthority('ROLE_USER','ROLE_ADMIN')" 24 class="btn btn-primary btn-lg btn-block" disabled="disabled">AAAAA</a> 25 <hr/> 26 <a href="manage_menu.html" th:href="@{/manage_menu.html}" sec:authorize="hasAnyAuthority('ROLE_ADMIN')" 27 class="btn btn-primary btn-lg btn-block">MENU(管理員以上權限)</a> 28 <a href="#" sec:authorize="!hasAnyAuthority('ROLE_ADMIN')" 29 class="btn btn-primary btn-lg btn-block" disabled="disabled">管理MENU</a> 30 <hr/> 31 </div> 32 </div> 33 </div> 34 <div class="container" th:replace="common :: footer"></div> 35 </body> 36 </html>
spring security 是可以控制到控件的
L17 authorization.expression 先說這個函數 從網上查 然而並沒有發現有效資料 沒辦法只好去翻一下源碼了
從這里並看不出什么 只能推測返回的是一個Boolean 和 配合 thymeleaf 的 th:if
繼續 看它調用的 函數 AuthUtils.authorizeUsingAccessExpression
源碼比較多截取一下關鍵信息 這里值得注意的就是 L237 這個判斷以為都是返回的false
從 L240 和 L249 的log 來分析看 一個是 Access GRANTED(授予訪問權限) 一個是Access DENIED(拒絕訪問)
到此也可以推出你對你傳入表達式的權限判斷,如果在看一下L237的代碼基本就比較明了
1 public final class ExpressionUtils { 2 3 public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) { 4 try { 5 return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue(); 6 } 7 catch (EvaluationException e) { 8 throw new IllegalArgumentException("Failed to evaluate expression '" 9 + expr.getExpressionString() + "'", e); 10 } 11 } 12 }
尤其是 new IllegalArgumentException 這個錯誤信息 文檔里給的描述 [Represent an exception that occurs during expression evaluation.]執行表達式出錯
當然這樣的說還是有些武斷的 但這次不是解讀源碼 所以能服務於我們這次demo就可以了
然后是還是當前行(L17)的 hasAnyAuthority 這個函數
先說一下 研究它的時候一個小插曲
我查這函數的時候是英文版和中文版對照着看 發現所謂的中文版就是 谷歌出來 貼上了 我也是無力吐槽了 貼下來感受一下
【hasAnyAuthority([authority1,authority2])
如果當前的主體有任何提供的角色(給定的作為一個逗號分隔的字符串列表)的話,返回true
.】
其實這里就是用這個函數來控制像訪問這個控件或者畫面所需要的權限可以一個也可以是多個
最后再說一個細節也是當前行(L17)
hasAnyAuthority(''__${session.authorityKindMap}__'')
就是這個標紅的下划線 意思是預處理 如果不這么寫 hasAnyAuthority會報上面說到的這個EvaluationException錯 因為直接 被解析字符串了
所以加上預處理把session里的內容提前取出來
然后說加這一行的原因是 下面的都是權限都是硬編碼寫死的這里寫一個從數據庫取的動態的
L21,26
sec:authorize 這個就是 spring security的標准標簽 沒什么要說的
hasAnyAuthority('ROLE_USER','ROLE_ADMIN') 函數是說當前的這個標簽 需要 'ROLE_USER','ROLE_ADMIN' 只有擁有這兩個權限的任何一個就能訪問
然后要說的 這兩個參數是取決於你自己定義的是什么 或作說你在后台向這個 Collection<GrantedAuthority> list 放進取的是什么
也是不是網上說必須 以 "ROLE_"開頭 也可以是其他的
最后說一下 如果沒有權限或者 其他的錯誤默認被攔截到 error.html
畫面本身是沒什么可以說的 就是 顯示error 或者 提示重新登錄
這個 畫面名值得說一下 因為 spring security 默認 把一些 錯比如 404,403 甚至 空指針 直接映射到這個 叫error.html的畫面上
本來想一篇寫完呢 寫着寫着就多了 還有很多重要的沒寫 都放到一篇里 篇幅有點多了 那就寫兩篇吧
上 就先到這。。。