前言
spring security是一個能夠為基于spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在spring應用上下文中配置的bean,充分利用了spring ioc,di(控制反轉inversion of control ,di:dependency injection 依賴注入)和aop(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。
本文主要給大家介紹了關于spring boot整合spring security實現登入登出的相關內容,下面話不多說了,來一起看看詳細的介紹吧
技術棧 : springboot + springsecurity + jpa + freemark ,完整項目地址 : https://github.com/ealenxie/spring-security-login
方法如下:
1 . 新建一個spring-security-login的maven項目 ,pom.xml添加基本依賴 :
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
56
57
58
59
60
61
62
63
64
65
66
|
<?xml version= "1.0" encoding= "utf-8" ?> <project xmlns= "http://maven.apache.org/pom/4.0.0" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation= "http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelversion> 4.0 . 0 </modelversion> <groupid>com.wuxicloud</groupid> <artifactid>spring-security-login</artifactid> <version> 1.0 </version> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version> 1.5 . 6 .release</version> </parent> <properties> <author>ealenxie</author> <description>springboot整合springsecurity實現簡單登入登出</description> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</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-test</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-freemarker</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency> <!--alibaba--> <dependency> <groupid>com.alibaba</groupid> <artifactid>druid</artifactid> <version> 1.0 . 24 </version> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> <version> 1.2 . 31 </version> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <scope>runtime</scope> </dependency> </dependencies> </project> |
2 . 準備你的數據庫,設計表結構,要用戶使用登入登出,新建用戶表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
drop table if exists `user`; create table `user` ( `id` int ( 11 ) not null auto_increment, `user_uuid` varchar( 70 ) character set utf8 collate utf8_general_ci default null , `username` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `password` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `email` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `telephone` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `role` int ( 10 ) default null , `image` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `last_ip` varchar( 255 ) character set utf8 collate utf8_general_ci default null , `last_time` varchar( 255 ) character set utf8 collate utf8_general_ci default null , primary key (`id`) using btree ) engine = innodb auto_increment = 2 character set = utf8 collate = utf8_general_ci row_format = compact; set foreign_key_checks = 1 ; |
3 . 用戶對象user.java :
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
import javax.persistence.*; /** * created by ealenxie on 2018/7/5 15:17 */ @entity @table (name = "user" ) public class user { @id @generatedvalue (strategy = generationtype.auto) private integer id; private string user_uuid; //用戶uuid private string username; //用戶名 private string password; //用戶密碼 private string email; //用戶郵箱 private string telephone; //電話號碼 private string role; //用戶角色 private string image; //用戶頭像 private string last_ip; //上次登錄ip private string last_time; //上次登錄時間 public integer getid() { return id; } public string getrole() { return role; } public void setrole(string role) { this .role = role; } public string getimage() { return image; } public void setimage(string image) { this .image = image; } public void setid(integer id) { this .id = id; } public string getusername() { return username; } public void setusername(string username) { this .username = username; } public string getemail() { return email; } public void setemail(string email) { this .email = email; } public string gettelephone() { return telephone; } public void settelephone(string telephone) { this .telephone = telephone; } public string getpassword() { return password; } public void setpassword(string password) { this .password = password; } public string getuser_uuid() { return user_uuid; } public void setuser_uuid(string user_uuid) { this .user_uuid = user_uuid; } public string getlast_ip() { return last_ip; } public void setlast_ip(string last_ip) { this .last_ip = last_ip; } public string getlast_time() { return last_time; } public void setlast_time(string last_time) { this .last_time = last_time; } @override public string tostring() { return "user{" + "id=" + id + ", user_uuid='" + user_uuid + '\ '' + ", username='" + username + '\ '' + ", password='" + password + '\ '' + ", email='" + email + '\ '' + ", telephone='" + telephone + '\ '' + ", role='" + role + '\ '' + ", image='" + image + '\ '' + ", last_ip='" + last_ip + '\ '' + ", last_time='" + last_time + '\ '' + '}' ; } } |
4 . application.yml配置一些基本屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
spring: resources: static -locations: classpath:/ freemarker: template-loader-path: classpath:/templates/ suffix: .html content-type: text/html charset: utf- 8 datasource: url: jdbc:mysql: //localhost:3306/yourdatabase username: yourname password: yourpass driver- class -name: com.mysql.jdbc.driver type: com.alibaba.druid.pool.druiddatasource server: port: 8083 error: whitelabel: enabled: true |
5 . 考慮我們應用的效率 , 可以配置數據源和線程池 :
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
package com.wuxicloud.config; import com.alibaba.druid.pool.druiddatasource; import com.alibaba.druid.pool.druiddatasourcefactory; import com.alibaba.druid.support.http.statviewservlet; import com.alibaba.druid.support.http.webstatfilter; import org.springframework.beans.factory.annotation.autowired; import org.springframework.boot.context.properties.configurationproperties; import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.boot.web.servlet.servletregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.core.env.*; import javax.sql.datasource; import java.util.hashmap; import java.util.map; import java.util.properties; @configuration public class druidconfig { private static final string db_prefix = "spring.datasource." ; @autowired private environment environment; @bean @configurationproperties (prefix = db_prefix) public datasource druiddatasource() { properties dbproperties = new properties(); map<string, object> map = new hashmap<>(); for (propertysource<?> propertysource : ((abstractenvironment) environment).getpropertysources()) { getpropertiesfromsource(propertysource, map); } dbproperties.putall(map); druiddatasource dds; try { dds = (druiddatasource) druiddatasourcefactory.createdatasource(dbproperties); dds.init(); } catch (exception e) { throw new runtimeexception( "load datasource error, dbproperties is :" + dbproperties, e); } return dds; } private void getpropertiesfromsource(propertysource<?> propertysource, map<string, object> map) { if (propertysource instanceof mappropertysource) { for (string key : ((mappropertysource) propertysource).getpropertynames()) { if (key.startswith(db_prefix)) map.put(key.replacefirst(db_prefix, "" ), propertysource.getproperty(key)); else if (key.startswith(db_prefix)) map.put(key.replacefirst(db_prefix, "" ), propertysource.getproperty(key)); } } if (propertysource instanceof compositepropertysource) { for (propertysource<?> s : ((compositepropertysource) propertysource).getpropertysources()) { getpropertiesfromsource(s, map); } } } @bean public servletregistrationbean druidservlet() { return new servletregistrationbean( new statviewservlet(), "/druid/*" ); } @bean public filterregistrationbean filterregistrationbean() { filterregistrationbean filterregistrationbean = new filterregistrationbean(); filterregistrationbean.setfilter( new webstatfilter()); filterregistrationbean.addurlpatterns( "/*" ); filterregistrationbean.addinitparameter( "exclusions" , "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" ); return filterregistrationbean; } } |
配置線程池 :
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
|
package com.wuxicloud.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.scheduling.annotation.enableasync; import org.springframework.scheduling.concurrent.threadpooltaskexecutor; import java.util.concurrent.executor; import java.util.concurrent.threadpoolexecutor; @configuration @enableasync public class threadpoolconfig { @bean public executor getexecutor() { threadpooltaskexecutor executor = new threadpooltaskexecutor(); executor.setcorepoolsize( 5 ); //線程池維護線程的最少數量 executor.setmaxpoolsize( 30 ); //線程池維護線程的最大數量 executor.setqueuecapacity( 8 ); //緩存隊列 executor.setrejectedexecutionhandler( new threadpoolexecutor.callerrunspolicy()); //對拒絕task的處理策略 executor.setkeepaliveseconds( 60 ); //允許的空閑時間 executor.initialize(); return executor; } } |
6.用戶需要根據用戶名進行登錄,訪問數據庫 :
1
2
3
4
5
6
7
8
9
10
11
|
import com.wuxicloud.model.user; import org.springframework.data.jpa.repository.jparepository; /** * created by ealenxie on 2018/7/11 14:23 */ public interface userrepository extends jparepository<user, integer> { user findbyusername(string username); } |
7.構建真正用于springsecurity登錄的安全用戶(userdetails),我這里使用新建了一個pojo來實現 :
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
56
57
58
|
package com.wuxicloud.security; import com.wuxicloud.model.user; import org.springframework.security.core.grantedauthority; import org.springframework.security.core.authority.simplegrantedauthority; import org.springframework.security.core.userdetails.userdetails; import java.util.arraylist; import java.util.collection; public class securityuser extends user implements userdetails { private static final long serialversionuid = 1l; public securityuser(user user) { if (user != null ) { this .setuser_uuid(user.getuser_uuid()); this .setusername(user.getusername()); this .setpassword(user.getpassword()); this .setemail(user.getemail()); this .settelephone(user.gettelephone()); this .setrole(user.getrole()); this .setimage(user.getimage()); this .setlast_ip(user.getlast_ip()); this .setlast_time(user.getlast_time()); } } @override public collection<? extends grantedauthority> getauthorities() { collection<grantedauthority> authorities = new arraylist<>(); string username = this .getusername(); if (username != null ) { simplegrantedauthority authority = new simplegrantedauthority(username); authorities.add(authority); } return authorities; } @override public boolean isaccountnonexpired() { return true ; } @override public boolean isaccountnonlocked() { return true ; } @override public boolean iscredentialsnonexpired() { return true ; } @override public boolean isenabled() { return true ; } } |
8 . 核心配置,配置springsecurity訪問策略,包括登錄處理,登出處理,資源訪問,密碼基本加密。
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
package com.wuxicloud.config; import com.wuxicloud.dao.userrepository; import com.wuxicloud.model.user; import com.wuxicloud.security.securityuser; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.security.config.annotation.authentication.builders.authenticationmanagerbuilder; import org.springframework.security.config.annotation.web.builders.httpsecurity; import org.springframework.security.config.annotation.web.configuration.enablewebsecurity; import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter; import org.springframework.security.core.authentication; import org.springframework.security.core.userdetails.userdetails; import org.springframework.security.core.userdetails.userdetailsservice; import org.springframework.security.core.userdetails.usernamenotfoundexception; import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder; import org.springframework.security.web.authentication.savedrequestawareauthenticationsuccesshandler; import org.springframework.security.web.authentication.logout.logoutsuccesshandler; import javax.servlet.servletexception; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.io.ioexception; /** * created by ealenxie on 2018/1/11. */ @configuration @enablewebsecurity public class websecurityconfig extends websecurityconfigureradapter { private static final logger logger = loggerfactory.getlogger(websecurityconfig. class ); @override protected void configure(httpsecurity http) throws exception { //配置策略 http.csrf().disable(); http.authorizerequests(). antmatchers( "/static/**" ).permitall().anyrequest().authenticated(). and().formlogin().loginpage( "/login" ).permitall().successhandler(loginsuccesshandler()). and().logout().permitall().invalidatehttpsession( true ). deletecookies( "jsessionid" ).logoutsuccesshandler(logoutsuccesshandler()). and().sessionmanagement().maximumsessions( 10 ).expiredurl( "/login" ); } @autowired public void configureglobal(authenticationmanagerbuilder auth) throws exception { auth.userdetailsservice(userdetailsservice()).passwordencoder(passwordencoder()); auth.erasecredentials( false ); } @bean public bcryptpasswordencoder passwordencoder() { //密碼加密 return new bcryptpasswordencoder( 4 ); } @bean public logoutsuccesshandler logoutsuccesshandler() { //登出處理 return new logoutsuccesshandler() { @override public void onlogoutsuccess(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, authentication authentication) throws ioexception, servletexception { try { securityuser user = (securityuser) authentication.getprincipal(); logger.info( "user : " + user.getusername() + " logout success ! " ); } catch (exception e) { logger.info( "logout exception , e : " + e.getmessage()); } httpservletresponse.sendredirect( "/login" ); } }; } @bean public savedrequestawareauthenticationsuccesshandler loginsuccesshandler() { //登入處理 return new savedrequestawareauthenticationsuccesshandler() { @override public void onauthenticationsuccess(httpservletrequest request, httpservletresponse response, authentication authentication) throws ioexception, servletexception { user userdetails = (user) authentication.getprincipal(); logger.info( "user : " + userdetails.getusername() + " login success ! " ); super .onauthenticationsuccess(request, response, authentication); } }; } @bean public userdetailsservice userdetailsservice() { //用戶登錄實現 return new userdetailsservice() { @autowired private userrepository userrepository; @override public userdetails loaduserbyusername(string s) throws usernamenotfoundexception { user user = userrepository.findbyusername(s); if (user == null ) throw new usernamenotfoundexception( "username " + s + " not found" ); return new securityuser(user); } }; } } |
9.至此,已經基本將配置搭建好了,從上面核心可以看出,配置的登錄頁的url 為/login,可以創建基本的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
|
package com.wuxicloud.web; import com.wuxicloud.model.user; import org.springframework.security.core.authentication; import org.springframework.security.core.context.securitycontext; import org.springframework.security.core.context.securitycontextholder; import org.springframework.security.core.userdetails.userdetails; import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.context.request.requestcontextholder; import org.springframework.web.context.request.servletrequestattributes; import javax.servlet.http.httpservletrequest; /** * created by ealenxie on 2018/1/11. */ @controller public class logincontroller { @requestmapping (value = "/login" , method = requestmethod.get) public string login() { return "login" ; } @requestmapping ( "/" ) public string root() { return "index" ; } public user getuser() { //為了session從獲取用戶信息,可以配置如下 user user = new user(); securitycontext ctx = securitycontextholder.getcontext(); authentication auth = ctx.getauthentication(); if (auth.getprincipal() instanceof userdetails) user = (user) auth.getprincipal(); return user; } public httpservletrequest getrequest() { return ((servletrequestattributes) requestcontextholder.getrequestattributes()).getrequest(); } } |
11 . springboot基本的啟動類 application.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.wuxicloud; import org.springframework.boot.springapplication; import org.springframework.boot.autoconfigure.springbootapplication; /** * created by ealenxie on 2018/7/11 15:01 */ @springbootapplication public class application { public static void main(string[] args) { springapplication.run(application. class , args); } } |
11.根據freemark和controller里面可看出配置的視圖為 /templates/index.html和/templates/index.login。所以創建基本的登錄頁面和登錄成功頁面。
login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!doctype html> <html lang= "en" > <head> <meta charset= "utf-8" > <title>用戶登錄</title> </head> <body> <form action= "/login" method= "post" > 用戶名 : <input type= "text" name= "username" /> 密碼 : <input type= "password" name= "password" /> <input type= "submit" value= "登錄" > </form> </body> </html> |
注意 : 這里方法必須是post,因為get在controller被重寫了,用戶名的name屬性必須是username,密碼的name屬性必須是password
index.html
1
2
3
4
5
6
7
8
9
10
11
12
|
<!doctype html> <html lang= "en" > <head> <meta charset= "utf-8" > <title>首頁</title> <#assign user=session.spring_security_context.authentication.principal/> </head> <body> 歡迎你,${user.username}<br/> <a href= "/logout" >注銷</a> </body> </html> |
注意 : 為了從session中獲取到登錄的用戶信息,根據配置springsecurity的用戶信息會放在session.spring_security_context.authentication.principal里面,根據freemarker模板引擎的特點,可以通過這種方式進行獲取 : <#assign user=session.spring_security_context.authentication.principal/>
12 . 為了方便測試,我們在數據庫中插入一條記錄,注意,從websecurity.java配置可以知道密碼會被加密,所以我們插入的用戶密碼應該是被加密的。
這里假如我們使用的密碼為admin,則加密過后的字符串是 $2a$04$1oiua3yechbxqbji8jamyukznlwzwvfeqjkahnwaeqwnacjt6ukqu
測試類如下 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package com.wuxicloud.security; import org.junit.test; import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder; /** * created by ealenxie on 2018/7/11 15:13 */ public class testencoder { @test public void encoder() { string password = "admin" ; bcryptpasswordencoder encoder = new bcryptpasswordencoder( 4 ); string enpassword = encoder.encode(password); system.out.println(enpassword); } } |
測試登錄,從上面的加密的密碼我們插入一條數據到數據庫中。
1
|
insert into `user` values ( 1 , 'd242ae49-4734-411e-8c8d-d2b09e87c3c8' , 'ealenxie' , '$2a$04$petexpgclkfdln4tyfxk0u8ryazmzdhlaswlx/xxm8hgqar1c892w' , 'sssss' , 'ssssssssss' , 1 , 'g' , '0:0:0:0:0:0:0:1' , '2018-07-11 11:26:27' ); |
13 . 啟動項目進行測試 ,訪問 localhost:8083
點擊登錄,登錄失敗會留在當前頁面重新登錄,成功則進入index.html
登錄如果成功,可以看到后臺打印登錄成功的日志 :
頁面進入index.html :
點擊注銷 ,則回重新跳轉到login.html,后臺也會打印登出成功的日志 :
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/ealenxie/p/9293768.html