spring boot系列03--spring security (基於數據庫)登陸和權限控制(上)

 這篇打算寫一下登錄權限驗證相關javascript

提及來也都是淚,以前涉及權限的比較少因此此次準備起來就比較困難。css

踩了好幾個大坑,還好最終都一一消化掉(這是廢話你沒解決你寫個什麼勁 😂) 也補充了一下本身在權限知識的的空白,仍是很欣慰的。html

試着能把遭遇到的坑都寫出來 ,能耐有限 盡力吧java

(由於我是基於上一篇的基礎上寫的 理論上能夠拿着上一版的代碼直接用 固然我也會在最後放上此次的代碼jquery

說是"理論上" 是由於此次遭遇的問題太多反覆刪,改 不少地方我本身也沒有作對比 也記不住了😌)ajax

 

好了,先從配置開始spring

配置sql


 向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)express

###############
## 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="請輸入用戶名 &#8727;" 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 &#8727;" 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>

說一下這個畫面遭遇的問題吧

坑1、L8,9 的資源引用

spring boot 有本身默認的資源引用文件夾(/META-INF/resources/ ,/resources/ ,/static/ ,/public/)因此用到的資源文件必定要這些

下面不然會讀取不到,固然本身也能夠本身定義直接寫在application.properties文件裏就行了,有一個問題就是 若是本身配置了默認的就不能用了

 

坑2、L40,48 

這裏的用戶名和密碼的控件的name是和後臺傳給

  formLogin().usernameParameter("txtUserCd") .passwordParameter("txtUserPwd")  這個兩個函數的參數要保持一致 

在網上看到說必定要 用 username 和 password 看來也不是必需  但 保持一致 仍是有必要的

 

坑3、此次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的畫面上

 

原本想一篇寫完呢 寫着寫着就多了 還有不少重要的沒寫 都放到一篇裏 篇幅有點多了 那就寫兩篇吧

上 就先到這。。。

相關文章
相關標籤/搜索