当前位置: 移动技术网 > IT编程>开发语言>Java > spring security自定义认证登录的全过程记录

spring security自定义认证登录的全过程记录

2019年07月19日  | 移动技术网IT编程  | 我要评论

spring security使用分类:

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

 ↑(获取信息)  (根据authenticationtoken分配provider)     
 authenticationfilter -> authenticationmanager -> authenticationprovider
        ↓(认证)
       userdetails(一般查询数据库获取)
        ↓(通过)
        生成认证成功的authenticationtoken
         ↓(存放)
        securitycontextholder

2)将authenticationfilter加入到security过滤链(资源服务器中配置),如:

http.addfilterbefore(authenticationfilter, abstractpreauthenticatedprocessingfilter.class)

或者:

http.addfilterafter(authenticationfilter, usernamepasswordauthenticationfilter.class)

2.以手机号短信登录为例

2.1.开发环境

  • springboot
  • spring security
  • redis

2.2.核心代码分析

2.2.1.自定义登录认证流程

2.2.1.1.自定义认证登录token

/**
 * 手机登录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.自定义认证登录过滤器

/**
 * 手机短信登录过滤器
 *
 * @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.自定义认证登录提供者

/**
 * 手机短信登录认证提供者
 *
 * @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.自定义认证登录认证配置

@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自定义验证码过滤器

/**
 * 验证码过滤器
 *
 * @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过滤器链

http.addfilterbefore(validatecodefilter, abstractpreauthenticatedprocessingfilter.class)

注:添加到认证预处理过滤器前

3.测试效果

最后附上源码地址:https://gitee.com/catalpaflat/springsecurity.git  ()

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网