本文講述spring boot整合spring security在方法上使用注解實現權限控制,使用自定義userdetailservice,從mysql中加載用戶信息。使用security自帶的md5加密,對用戶密碼進行加密。頁面模板采用thymeleaf引擎。
源碼地址:https://github.com/li5454yong/springboot-security.git
1、引入pom依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version> 1.4 . 4 .release</version> </parent> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>org.springframework.security.oauth</groupid> <artifactid>spring-security-oauth2</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-thymeleaf</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-jpa</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-jdbc</artifactid> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version> 5.1 . 34 </version> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>druid</artifactid> <version> 1.0 . 15 </version> </dependency> </dependencies> |
這里使用druid連接池,spring data jpa實現數據庫訪問。
2、配置spring security
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
@configuration @enablewebmvcsecurity @enableglobalmethodsecurity (prepostenabled = true ) //開啟security注解 public class websecurityconfig extends websecurityconfigureradapter { @bean @override protected authenticationmanager authenticationmanager() throws exception { return super .authenticationmanager(); } @override protected void configure(httpsecurity http) throws exception { //允許所有用戶訪問"/"和"/home" http.authorizerequests() .antmatchers( "/" , "/home" ).permitall() //其他地址的訪問均需驗證權限 .anyrequest().authenticated() .and() .formlogin() //指定登錄頁是"/login" .loginpage( "/login" ) .defaultsuccessurl( "/hello" ) //登錄成功后默認跳轉到"/hello" .permitall() .and() .logout() .logoutsuccessurl( "/home" ) //退出登錄后的默認url是"/home" .permitall(); } @autowired public void configureglobal(authenticationmanagerbuilder auth) throws exception { auth .userdetailsservice(customuserdetailsservice()) .passwordencoder(passwordencoder()); } /** * 設置用戶密碼的加密方式為md5加密 * @return */ @bean public md5passwordencoder passwordencoder() { return new md5passwordencoder(); } /** * 自定義userdetailsservice,從數據庫中讀取用戶信息 * @return */ @bean public customuserdetailsservice customuserdetailsservice(){ return new customuserdetailsservice(); } } |
這里只做了基本的配置,設置了登錄url、登錄成功后跳轉的url、退出后跳轉到的url。使用@enableglobalmethodsecurity(prepostenabled = true)這個注解,可以開啟security的注解,我們可以在需要控制權限的方法上面使用@preauthorize,@prefilter這些注解。
3、自定義userdetailservice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class customuserdetailsservice implements userdetailsservice { @autowired //數據庫服務類 private suserservice suserservice; @override public userdetails loaduserbyusername(string username) throws usernamenotfoundexception { //suser對應數據庫中的用戶表,是最終存儲用戶和密碼的表,可自定義 //本例使用suser中的email作為用戶名: suser user = suserservice.finduserbyemail(username); if (user == null ) { throw new usernamenotfoundexception( "username " + username + " not found" ); } // securityuser實現userdetails并將suser的email映射為username securityuser securityuser = new securityuser(user); collection<simplegrantedauthority> authorities = new arraylist<simplegrantedauthority>(); authorities.add( new simplegrantedauthority( "role_admin" )); return securityuser; } } |
這里只需要實現userdetailsservice 接口,重寫loaduserbyusername方法,從數據庫中取出用戶信息。最后返回一個userdetails 實現類。
4、定義錯誤處理配置
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@configuration public class errorpageconfig { @bean public embeddedservletcontainercustomizer embeddedservletcontainercustomizer(){ return new mycustomizer(); } private static class mycustomizer implements embeddedservletcontainercustomizer { @override public void customize(configurableembeddedservletcontainer container) { container.adderrorpages( new errorpage(httpstatus.forbidden, "/403" )); } } } |
訪問發生錯誤時,跳轉到”/403”.
5、controller接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
@controller public class indexcontroller { @resource private suserservice suserservice; @requestmapping ( "/home" ) public string home() { return "home" ; } @preauthorize ( "hasrole('user')" ) @requestmapping (value = "/admin" ,method = requestmethod.get) public string toadmin(){ return "helloadmin" ; } @requestmapping ( "/hello" ) public string hello() { return "hello" ; } @requestmapping ( "/login" ) public string login(){ return "login" ; } @requestmapping ( "/" ) public string root() { return "index" ; } @requestmapping ( "/403" ) public string error(){ return "403" ; } } |
在toadmin()方法上面使用了@preauthorize(“hasrole(‘user')”),表示訪問這個方法需要擁有user角色。如果希望控制到權限層面,可以使用@preauthorize(“haspermission()”)。這里只是用了其中的一個用法,更多的使用方法可以去看官方文檔。需要注意的是,spring security默認的角色前綴是”role_”,使用hasrole方法時已經默認加上了,因此我們在數據庫里面的用戶角色應該是“role_user”,在user前面加上”role_”前綴。
6、測試
啟動項目,訪問http://localhost:1130/login
點擊登錄后進入到“/hello”
點擊跳轉到管理員頁面
在后臺的“/admin”這個url對應的方法上面,限制了用戶必須要擁有“user”角色。在數據庫中也設置了登錄的用戶有這個角色。
現在我們修改數據庫中的用戶角色,改為“role_admin”。退出登錄后重新登錄,再次點擊“前往管理員頁面”按鈕,會跳轉到如下頁面。
因為現在沒有了“user”權限,所以訪問的時候拋出了異常,被攔截后重定向到了“/403”。
7、post方式訪問,錯誤碼403
首先,把“/admin”改為post請求
1
2
3
4
5
|
@preauthorize ( "hasrole('user')" ) @requestmapping (value = "/admin" ,method = requestmethod.post) public string toadmin(){ return "helloadmin" ; } |
把“前往管理員頁面”按鈕的請求方式從原來的form表達get提交,改為ajax方式post提交。至于為什么不是用form的post提交后面再講。先修改代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<body> <h1 th:inline= "text" >hello [[${#httpservletrequest.remoteuser}]]!</h1> <!--<form th:action= "@{/logout}" method= "post" > <input type= "submit" value= "sign out" /> </form> <form th:action= "@{/admin}" method= "get" > <input th:type= "submit" th:value= "前往管理員頁面" /> </form>--> <a th:href= "@{/admin}" rel= "external nofollow" >前往管理員用戶頁面</a> <input th:type= "submit" onclick= "testpost()" th:value= "前往管理員頁面" /> </body> <script> function testpost() { $.ajax({ url: "/admin" , type: 'post' , success:function (data) { } }); } </script> |
點擊“前往管理員頁面”按鈕,在調試臺可以看到如下
這是因為框架內部防止csrf(cross-site request forgery跨站請求偽造)的發生,限制了除了get以外的大多數方法。
下面說解決辦法:
首先在標簽內添加如下內容。
1
2
|
<meta name= "_csrf" th:content= "${_csrf.token}" /> <meta name= "_csrf_hader" th:content= "${_csrf.headername}" /> |
只要添加這個token,后臺就會驗證這個token的正確性,如果正確,則接受post訪問。
然后在ajax代碼中添加以下代碼:
1
2
3
4
5
|
var token = $( 'meta[name="_csrf"]' ).attr( "content" ); var header = $( 'meta[name="_csrf_hader"]' ).attr( "content" ); $(document).ajaxsend(function(e,xhr,opt){ xhr.setrequestheader(header,token); }); |
這樣就可以正常使用post、delete等其他方式來訪問了。
上面說到使用表單的post方式來提交,通過查看頁面源代碼可以看到
框架在form表單中自動插入了一個隱藏域,value值就是那個token,所以使用form表單來提交post請求是可以直接通過的,而ajax方式提交的話,需要加上那段代碼。
好了,這篇文章就講到這,后面還會有文章講述rest api風格如何來使用spring security來控制權限。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/u283056051/article/details/55803855