如何使用spring security,相信百度過的都知道,總共有四種用法,從簡到深為:
1、不用數據庫,全部數據寫在配置文件,這個也是官方文檔里面的demo;
2、使用數據庫,根據spring security默認實現代碼設計數據庫,也就是說數據庫已經固定了,這種方法不靈活,而且那個數據庫設計得很簡陋,實用性差;
3、spring security和acegi不同,它不能修改默認filter了,但支持插入filter,所以根據這個,我們可以插入自己的filter來靈活使用;
4、暴力手段,修改源碼,前面說的修改默認filter只是修改配置文件以替換filter而已,這種是直接改了里面的源碼,但是這種不符合oo設計原則,而且不實際,不可用。
本文主要介紹了關于spring security自定義認證登錄的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
1.概要
1.1.簡介
spring security是一種基于 spring aop 和 servlet 過濾器的安全框架,以此來管理權限認證等。
1.2.spring security 自定義認證流程
1)認證過程
生成未認證的authenticationtoken
1
2
3
4
5
6
7
8
|
↑(獲取信息) (根據authenticationtoken分配provider) authenticationfilter -> authenticationmanager -> authenticationprovider ↓(認證) userdetails(一般查詢數據庫獲取) ↓(通過) 生成認證成功的authenticationtoken ↓(存放) securitycontextholder |
2)將authenticationfilter加入到security過濾鏈(資源服務器中配置),如:
1
|
http.addfilterbefore(authenticationfilter, abstractpreauthenticatedprocessingfilter. class ) |
或者:
1
|
http.addfilterafter(authenticationfilter, usernamepasswordauthenticationfilter. class ) |
2.以手機號短信登錄為例
2.1.開發環境
- springboot
- spring security
- redis
2.2.核心代碼分析
2.2.1.自定義登錄認證流程
2.2.1.1.自定義認證登錄token
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
|
/** * 手機登錄token * * @author : catalpaflat */ public class mobileloginauthenticationtoken extends abstractauthenticationtoken { private static final long serialversionuid = springsecuritycoreversion.serial_version_uid; private static final logger logger = loggerfactory.getlogger(mobileloginauthenticationtoken. class .getname()); private final object principal; public mobileloginauthenticationtoken(string mobile) { super ( null ); this .principal = mobile; this .setauthenticated( false ); logger.info( "mobileloginauthenticationtoken setauthenticated ->false loading ..." ); } public mobileloginauthenticationtoken(object principal, collection<? extends grantedauthority> authorities) { super (authorities); this .principal = principal; // must use super, as we override super .setauthenticated( true ); logger.info( "mobileloginauthenticationtoken setauthenticated ->true loading ..." ); } @override public void setauthenticated( boolean authenticated) { if (authenticated) { throw new illegalargumentexception( "cannot set this token to trusted - use constructor which takes a grantedauthority list instead" ); } super .setauthenticated( false ); } @override public object getcredentials() { return null ; } @override public object getprincipal() { return this .principal; } @override public void erasecredentials() { super .erasecredentials(); } } |
注:
setauthenticated():判斷是否已認證
- 在過濾器時,會生成一個未認證的authenticationtoken,此時調用的是自定義token的setauthenticated(),此時設置為false -> 未認證
- 在提供者時,會生成一個已認證的authenticationtoken,此時調用的是父類的setauthenticated(),此時設置為true -> 已認證
2.2.1.1.自定義認證登錄過濾器
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
|
/** * 手機短信登錄過濾器 * * @author : catalpaflat */ public class mobileloginauthenticationfilter extends abstractauthenticationprocessingfilter { private boolean postonly = true ; private static final logger logger = loggerfactory.getlogger(mobileloginauthenticationfilter. class .getname()); @getter @setter private string mobileparametername; public mobileloginauthenticationfilter(string mobileloginurl, string mobileparametername, string httpmethod) { super ( new antpathrequestmatcher(mobileloginurl, httpmethod)); this .mobileparametername = mobileparametername; logger.info( "mobileloginauthenticationfilter loading ..." ); } @override public authentication attemptauthentication(httpservletrequest request, httpservletresponse response) throws authenticationexception, ioexception, servletexception { if (postonly && !request.getmethod().equals(httpmethod.post.name())) { throw new authenticationserviceexception( "authentication method not supported: " + request.getmethod()); } //get mobile string mobile = obtainmobile(request); //assemble token mobileloginauthenticationtoken authrequest = new mobileloginauthenticationtoken(mobile); // allow subclasses to set the "details" property setdetails(request, authrequest); return this .getauthenticationmanager().authenticate(authrequest); } /** * 設置身份認證的詳情信息 */ private void setdetails(httpservletrequest request, mobileloginauthenticationtoken authrequest) { authrequest.setdetails(authenticationdetailssource.builddetails(request)); } /** * 獲取手機號 */ private string obtainmobile(httpservletrequest request) { return request.getparameter(mobileparametername); } public void setpostonly( boolean postonly) { this .postonly = postonly; } } |
注:attemptauthentication()方法:
- 過濾指定的url、httpmethod
- 獲取所需請求參數數據封裝生成一個未認證的authenticationtoken
- 傳遞給authenticationmanager認證
2.2.1.1.自定義認證登錄提供者
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
|
/** * 手機短信登錄認證提供者 * * @author : catalpaflat */ public class mobileloginauthenticationprovider implements authenticationprovider { private static final logger logger = loggerfactory.getlogger(mobileloginauthenticationprovider. class .getname()); @getter @setter private userdetailsservice customuserdetailsservice; public mobileloginauthenticationprovider() { logger.info( "mobileloginauthenticationprovider loading ..." ); } /** * 認證 */ @override public authentication authenticate(authentication authentication) throws authenticationexception { //獲取過濾器封裝的token信息 mobileloginauthenticationtoken authenticationtoken = (mobileloginauthenticationtoken) authentication; //獲取用戶信息(數據庫認證) userdetails userdetails = customuserdetailsservice.loaduserbyusername((string) authenticationtoken.getprincipal()); //不通過 if (userdetails == null ) { throw new internalauthenticationserviceexception( "unable to obtain user information" ); } //通過 mobileloginauthenticationtoken authenticationresult = new mobileloginauthenticationtoken(userdetails, userdetails.getauthorities()); authenticationresult.setdetails(authenticationtoken.getdetails()); return authenticationresult; } /** * 根據token類型,來判斷使用哪個provider */ @override public boolean supports( class <?> authentication) { return mobileloginauthenticationtoken. class .isassignablefrom(authentication); } } |
注:authenticate()方法
- 獲取過濾器封裝的token信息
- 調取userdetailsservice獲取用戶信息(數據庫認證)->判斷通過與否
- 通過則封裝一個新的authenticationtoken,并返回
2.2.1.1.自定義認證登錄認證配置
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
|
@configuration (springbeannameconstant.default_custom_mobile_login_authentication_security_config_bn) public class mobileloginauthenticationsecurityconfig extends securityconfigureradapter<defaultsecurityfilterchain, httpsecurity> { private static final logger logger = loggerfactory.getlogger(mobileloginauthenticationsecurityconfig. class .getname()); @value ( "${login.mobile.url}" ) private string defaultmobileloginurl; @value ( "${login.mobile.parameter}" ) private string defaultmobileloginparameter; @value ( "${login.mobile.httpmethod}" ) private string defaultmobileloginhttpmethod; @autowired private customymlconfig customymlconfig; @autowired private userdetailsservice customuserdetailsservice; @autowired private authenticationsuccesshandler customauthenticationsuccesshandler; @autowired private authenticationfailurehandler customauthenticationfailurehandler; public mobileloginauthenticationsecurityconfig() { logger.info( "mobileloginauthenticationsecurityconfig loading ..." ); } @override public void configure(httpsecurity http) throws exception { mobilepojo mobile = customymlconfig.getlogins().getmobile(); string url = mobile.geturl(); string parameter = mobile.getparameter().getmobile(); string httpmethod = mobile.gethttpmethod(); mobileloginauthenticationfilter mobileloginauthenticationfilter = new mobileloginauthenticationfilter(stringutils.isblank(url) ? defaultmobileloginurl : url, stringutils.isblank(parameter) ? defaultmobileloginurl : parameter, stringutils.isblank(httpmethod) ? defaultmobileloginhttpmethod : httpmethod); mobileloginauthenticationfilter.setauthenticationmanager(http.getsharedobject(authenticationmanager. class )); mobileloginauthenticationfilter.setauthenticationsuccesshandler(customauthenticationsuccesshandler); mobileloginauthenticationfilter.setauthenticationfailurehandler(customauthenticationfailurehandler); mobileloginauthenticationprovider mobileloginauthenticationprovider = new mobileloginauthenticationprovider(); mobileloginauthenticationprovider.setcustomuserdetailsservice(customuserdetailsservice); http.authenticationprovider(mobileloginauthenticationprovider) .addfilterafter(mobileloginauthenticationfilter, usernamepasswordauthenticationfilter. class ); } } |
注:configure()方法
實例化authenticationfilter和authenticationprovider
將authenticationfilter和authenticationprovider添加到spring security中。
2.2.2.基于redis自定義驗證碼校驗
2.2.2.1.基于redis自定義驗證碼過濾器
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
|
/** * 驗證碼過濾器 * * @author : catalpaflat */ @component (springbeannameconstant.default_validate_code_filter_bn) public class validatecodefilter extends onceperrequestfilter implements initializingbean { private static final logger logger = loggerfactory.getlogger(validatecodefilter. class .getname()); @autowired private customymlconfig customymlconfig; @autowired private redistemplate<object, object> redistemplate; /** * 驗證請求url與配置的url是否匹配的工具類 */ private antpathmatcher pathmatcher = new antpathmatcher(); public validatecodefilter() { logger.info( "loading validatecodefilter..." ); } @override protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain) throws servletexception, ioexception { string url = customymlconfig.getlogins().getmobile().geturl(); if (pathmatcher.match(url, request.getrequesturi())) { string deviceid = request.getheader( "deviceid" ); if (stringutils.isblank(deviceid)) { throw new customexception(httpstatus.not_acceptable.value(), "not deviceid in the head of the request" ); } string codeparamname = customymlconfig.getlogins().getmobile().getparameter().getcode(); string code = request.getparameter(codeparamname); if (stringutils.isblank(code)) { throw new customexception(httpstatus.not_acceptable.value(), "not code in the parameters of the request" ); } string key = systemconstant.default_mobile_key_pix + deviceid; smscodepo smscodepo = (smscodepo) redistemplate.opsforvalue().get(key); if (smscodepo.isexpried()){ throw new customexception(httpstatus.bad_request.value(), "the verification code has expired" ); } string smscode = smscodepo.getcode(); if (stringutils.isblank(smscode)) { throw new customexception(httpstatus.bad_request.value(), "verification code does not exist" ); } if (stringutils.equals(code, smscode)) { redistemplate.delete(key); //let it go filterchain.dofilter(request, response); } else { throw new customexception(httpstatus.bad_request.value(), "validation code is incorrect" ); } } else { //let it go filterchain.dofilter(request, response); } } } |
注:dofilterinternal()
自定義驗證碼過濾校驗
2.2.2.2.將自定義驗證碼過濾器添加到spring security過濾器鏈
1
|
http.addfilterbefore(validatecodefilter, abstractpreauthenticatedprocessingfilter. class ) |
注:添加到認證預處理過濾器前
3.測試效果
最后附上源碼地址:https://gitee.com/catalpaflat/springsecurity.git
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://juejin.im/post/5a3b7f5c6fb9a0452936ee2a