在使用springsecurity中,大伙都知道默認的登錄數據是通過key/value的形式來傳遞的,默認情況下不支持json格式的登錄數據,如果有這種需求,就需要自己來解決,本文主要和小伙伴來聊聊這個話題。
基本登錄方案
在說如何使用json登錄之前,我們還是先來看看基本的登錄吧,本文為了簡單,springsecurity在使用中就不連接數據庫了,直接在內存中配置用戶名和密碼,具體操作步驟如下:
創建spring boot工程
首先創建springboot工程,添加springsecurity依賴,如下:
1
2
3
4
5
6
7
8
|
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> |
添加security配置
創建securityconfig,完成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
|
@configuration public class securityconfig extends websecurityconfigureradapter { @bean passwordencoder passwordencoder() { return new bcryptpasswordencoder(); } @override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.inmemoryauthentication().withuser( "zhangsan" ).password( "$2a$10$2o4ewlrrfpebotfdotc0f.rpumk.3q3kvbhrx7xxkumlbgjoobs8q" ).roles( "user" ); } @override public void configure(websecurity web) throws exception { } @override protected void configure(httpsecurity http) throws exception { http.authorizerequests() .anyrequest().authenticated() .and() .formlogin() .loginprocessingurl( "/dologin" ) .successhandler( new authenticationsuccesshandler() { @override public void onauthenticationsuccess(httpservletrequest req, httpservletresponse resp, authentication authentication) throws ioexception, servletexception { respbean ok = respbean.ok( "登錄成功!" ,authentication.getprincipal()); resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); out.write( new objectmapper().writevalueasstring(ok)); out.flush(); out.close(); } }) .failurehandler( new authenticationfailurehandler() { @override public void onauthenticationfailure(httpservletrequest req, httpservletresponse resp, authenticationexception e) throws ioexception, servletexception { respbean error = respbean.error( "登錄失敗" ); resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); out.write( new objectmapper().writevalueasstring(error)); out.flush(); out.close(); } }) .loginpage( "/login" ) .permitall() .and() .logout() .logouturl( "/logout" ) .logoutsuccesshandler( new logoutsuccesshandler() { @override public void onlogoutsuccess(httpservletrequest req, httpservletresponse resp, authentication authentication) throws ioexception, servletexception { respbean ok = respbean.ok( "注銷成功!" ); resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); out.write( new objectmapper().writevalueasstring(ok)); out.flush(); out.close(); } }) .permitall() .and() .csrf() .disable() .exceptionhandling() .accessdeniedhandler( new accessdeniedhandler() { @override public void handle(httpservletrequest req, httpservletresponse resp, accessdeniedexception e) throws ioexception, servletexception { respbean error = respbean.error( "權限不足,訪問失敗" ); resp.setstatus( 403 ); resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); out.write( new objectmapper().writevalueasstring(error)); out.flush(); out.close(); } }); } } |
這里的配置雖然有點長,但是很基礎,配置含義也比較清晰,首先提供bcryptpasswordencoder作為passwordencoder,可以實現對密碼的自動加密加鹽,非常方便,然后提供了一個名為zhangsan
的用戶,密碼是123
,角色是user
,最后配置登錄邏輯,所有的請求都需要登錄后才能訪問,登錄接口是/dologin
,用戶名的key是username,密碼的key是password,同時配置登錄成功、登錄失敗以及注銷成功、權限不足時都給用戶返回json提示,另外,這里雖然配置了登錄頁面為/login
,實際上這不是一個頁面,而是一段json,在logincontroller中提供該接口,如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@restcontroller @responsebody public class logincontroller { @getmapping ( "/login" ) public respbean login() { return respbean.error( "尚未登錄,請登錄" ); } @getmapping ( "/hello" ) public string hello() { return "hello" ; } } |
這里/login
只是一個json提示,而不是頁面, /hello
則是一個測試接口。
ok,做完上述步驟就可以開始測試了,運行springboot項目,訪問/hello
接口,結果如下:
此時先調用登錄接口進行登錄,如下:
登錄成功后,再去訪問/hello
接口就可以成功訪問了。
使用json登錄
上面演示的是一種原始的登錄方案,如果想將用戶名密碼通過json的方式進行傳遞,則需要自定義相關過濾器,通過分析源碼我們發現,默認的用戶名密碼提取在usernamepasswordauthenticationfilter過濾器中,部分源碼如下:
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
|
public class usernamepasswordauthenticationfilter extends abstractauthenticationprocessingfilter { public static final string spring_security_form_username_key = "username" ; public static final string spring_security_form_password_key = "password" ; private string usernameparameter = spring_security_form_username_key; private string passwordparameter = spring_security_form_password_key; private boolean postonly = true ; public usernamepasswordauthenticationfilter() { super ( new antpathrequestmatcher( "/login" , "post" )); } public authentication attemptauthentication(httpservletrequest request, httpservletresponse response) throws authenticationexception { if (postonly && !request.getmethod().equals( "post" )) { throw new authenticationserviceexception( "authentication method not supported: " + request.getmethod()); } string username = obtainusername(request); string password = obtainpassword(request); if (username == null ) { username = "" ; } if (password == null ) { password = "" ; } username = username.trim(); usernamepasswordauthenticationtoken authrequest = new usernamepasswordauthenticationtoken( username, password); // allow subclasses to set the "details" property setdetails(request, authrequest); return this .getauthenticationmanager().authenticate(authrequest); } protected string obtainpassword(httpservletrequest request) { return request.getparameter(passwordparameter); } protected string obtainusername(httpservletrequest request) { return request.getparameter(usernameparameter); } //... //... } |
從這里可以看到,默認的用戶名/密碼提取就是通過request中的getparameter來提取的,如果想使用json傳遞用戶名密碼,只需要將這個過濾器替換掉即可,自定義過濾器如下:
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 customauthenticationfilter extends usernamepasswordauthenticationfilter { @override public authentication attemptauthentication(httpservletrequest request, httpservletresponse response) throws authenticationexception { if (request.getcontenttype().equals(mediatype.application_json_utf8_value) || request.getcontenttype().equals(mediatype.application_json_value)) { objectmapper mapper = new objectmapper(); usernamepasswordauthenticationtoken authrequest = null ; try (inputstream is = request.getinputstream()) { map<string,string> authenticationbean = mapper.readvalue(is, map. class ); authrequest = new usernamepasswordauthenticationtoken( authenticationbean.get( "username" ), authenticationbean.get( "password" )); } catch (ioexception e) { e.printstacktrace(); authrequest = new usernamepasswordauthenticationtoken( "" , "" ); } finally { setdetails(request, authrequest); return this .getauthenticationmanager().authenticate(authrequest); } } else { return super .attemptauthentication(request, response); } } } |
這里只是將用戶名/密碼的獲取方案重新修正下,改為了從json中獲取用戶名密碼,然后在securityconfig中作出如下修改:
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
|
@override protected void configure(httpsecurity http) throws exception { http.authorizerequests().anyrequest().authenticated() .and() .formlogin() .and().csrf().disable(); http.addfilterat(customauthenticationfilter(), usernamepasswordauthenticationfilter. class ); } @bean customauthenticationfilter customauthenticationfilter() throws exception { customauthenticationfilter filter = new customauthenticationfilter(); filter.setauthenticationsuccesshandler( new authenticationsuccesshandler() { @override public void onauthenticationsuccess(httpservletrequest req, httpservletresponse resp, authentication authentication) throws ioexception, servletexception { resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); respbean respbean = respbean.ok( "登錄成功!" ); out.write( new objectmapper().writevalueasstring(respbean)); out.flush(); out.close(); } }); filter.setauthenticationfailurehandler( new authenticationfailurehandler() { @override public void onauthenticationfailure(httpservletrequest req, httpservletresponse resp, authenticationexception e) throws ioexception, servletexception { resp.setcontenttype( "application/json;charset=utf-8" ); printwriter out = resp.getwriter(); respbean respbean = respbean.error( "登錄失敗!" ); out.write( new objectmapper().writevalueasstring(respbean)); out.flush(); out.close(); } }); filter.setauthenticationmanager(authenticationmanagerbean()); return filter; } |
將自定義的customauthenticationfilter類加入進來即可,接下來就可以使用json進行登錄了,如下:
好了,本文就先介紹到這里,有問題歡迎留言討論。 希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000018157525