欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-02-14 16:43:58
spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为: 1、不用数据库,全部数据写在配置文件...

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.测试效果

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

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

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

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

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

总结

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