如何使用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