当前位置: 首页 > news >正文

spring security 手机号 短信验证码认证、验证码认证 替换默认的用户名密码认证132

         spring security内置的有用户名密码认证规则,还可以调用第三方微信、qq登录接口实现登录认证,这里使用自定义的手机号和短信验证码实现登录认证。   

         要实现自定义的手机号和短信验证码认证需要了解用户名密码认证的逻辑,仿照该逻辑就可以写出任何自定义的登录认证:

                Filter用来过滤对应的登录请求

                Manager用来寻找具体能匹配上Token的Provider对象校验

                Provider调用Service,Service返回一个已经存储的用户信息封装为Token对象,

 Provider 拿去该Token对象和用户登录封装的Token值比较。如果匹配成功返回一个新的已认证的Token。

        一、熟悉security框架用户名密码校验逻辑,Filter、Provider、Authentication。  

0)UsernamePasswordAuthenticationToken

        UsernamePasswordAuthenticationToken就是Authentication的一个实现类

        



1)UsernamePasswordAuthenticationFilter:



2)AuthenticationManager

        security实现认证的实现类为ProviderManager。



3)DaoAuthenticationProvider

                ①AbstractUserDetailsAuthenticationProvider

                                       DaoAuthenticationProvider的authenticate()方法在父类                     AbstractUserDetailsAuthenticationProvider中;

             ②  DaoAuthenticationProvider的retrieveUser()方法

   



            4)UserDetilsService

                        调用实现类WebSecurityConfigurerAdapter完成创建一个包含用户名和密码的Authentication对象。

                        密码由内部类随机生成。

 二、自定义手机号验证码登录认证。

                实现该认证,只需要重写Token,Filter,Provider,UserDetilsService。这里验证码可以使用阿里云的免费短信测试,因为我的白嫖短信已经过期,所以这里事先写死验证码,最后调用阿里云的短信验证API。

                0)登录业务的两个url

                                用户点击发送短信调用的单元方法,使用直接响应

    @RequestMapping("/sendPhoneCode")@ResponseBodypublic String sendPhoneCode(String phone,HttpSession session) throws ExecutionException, InterruptedException {String code = RandomNumberCode.creatCode();SendSmsResponse sendSmsResponse = SmsUtils.sendCode(phone,code);session.setAttribute("smsCode",code);
//        if (sendSmsResponse.getStatusCode() == 200){
//            return AjaxResultVo.success(200);
//        }return AjaxResultVo.success(200);}

                              用户点击登录的拦截url,使用过滤器实现

        

           1)Token

                             模仿UsernamePasswordAuthenticationToken,把密码删除。验证码直接在controller层比较,如果验证码不对没必要比较手机号是否注册。

public class SmsAuthenticationToekn extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;private final Object principal;public SmsAuthenticationToekn(Object principal) {super(null);this.principal = principal;setAuthenticated(false);}public SmsAuthenticationToekn(Object principal,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true); // must use super, as we override}@Overridepublic Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principal;}@Overridepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {Assert.isTrue(!isAuthenticated,"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");super.setAuthenticated(false);}@Overridepublic void eraseCredentials() {super.eraseCredentials();}
}


        2)Filter

                修改拦截路径,比较发送短信的验证码和用户的验证码是否一致,不一致直接异常。这里发送短信的验证码直接为1234

package com.xja.filter;import com.xja.domain.SmsAuthenticationToekn;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;/*** @author rk* @description: TODO* @date 2024/9/16 19:27*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/phoneLogin","POST");private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;private boolean postOnly = true;public SmsAuthenticationFilter() {super(DEFAULT_ANT_PATH_REQUEST_MATCHER);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {System.out.println("过滤器生效");if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}HttpSession session = request.getSession();String smsCode = (String) session.getAttribute("smsCode");
//        if (smsCode.equals(request.getParameter("code"))){
//                throw  new UsernameNotFoundException("用户名或验证码错误");
//            }if (!"1234".equals(request.getParameter("code"))){throw  new UsernameNotFoundException("用户名或验证码错误");}String username = obtainUsername(request);username = (username != null) ? username : "";username = username.trim();SmsAuthenticationToekn authRequest = new SmsAuthenticationToekn(username);setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}@Nullableprotected String obtainUsername(HttpServletRequest request) {return request.getParameter(this.usernameParameter);}protected void setDetails(HttpServletRequest request, SmsAuthenticationToekn authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}
}


        3)Provider

                如果比较成功 返回一个新创建的Token对象存储用户信息。

public class SmsAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {System.out.println("校验器生效");SmsAuthenticationToekn smsAuthenticationToekn = (SmsAuthenticationToekn) authentication;UserDetails userDetails = userDetailsService.loadUserByUsername(smsAuthenticationToekn.getName());if (userDetails == null){throw  new UsernameNotFoundException("用户名或验证码错误");}System.out.println();return new SmsAuthenticationToekn(userDetails.getUsername(),userDetails.getAuthorities());}@Overridepublic boolean supports(Class<?> authentication) {return (SmsAuthenticationToekn.class.isAssignableFrom(authentication));}}


        4)UserDetilsService

                        从数据库中校验是否有该手机号,这里的业务是没有提示用户输入有误,

很多登录界面使用手机号验证码时不需要注册,那么在这里也可以直接调用mapper注册该手机号.

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {System.out.println("service生效");User user = userMapper.SelectUserByUserName(name);System.out.println("user = " + user.toString());if (user == null){throw new UsernameNotFoundException("用户名或密码错误");}List<SimpleGrantedAuthority> powerNameList = new ArrayList<SimpleGrantedAuthority>();powerNameList.add(new SimpleGrantedAuthority("ROLE_"+"userManage"));powerNameList.add(new SimpleGrantedAuthority("system:user:add"));return new org.springframework.security.core.userdetails.User(user.getUname(),user.getUpassword(),powerNameList);}

        5)调用阿里云api发送手机验证码

        5.1)复制SDK,导入依赖

                     AccessKey需要手动创建

                     手机号和验证码在用户发送请求时获取

        5.2)创建AccessKey

        5.3)封装发送短信的工具类

package com.xja.util;import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dysmsapi20170525.models.AddSmsSignRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.AddSmsSignResponse;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;/*** @author rk* @description: TODO* @date 2024/9/18 17:07*/
public class SmsUtils {public static SendSmsResponse sendCode(String phone,String code) throws ExecutionException, InterruptedException {// HttpClient Configuration/*HttpClient httpClient = new ApacheAsyncHttpClientBuilder().connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds.responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds.maxConnections(128) // Set the connection pool size.maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds// Configure the proxy.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001)).setCredentials("<your-proxy-username>", "<your-proxy-password>"))// If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true)).x509TrustManagers(new X509TrustManager[]{}).keyManagers(new KeyManager[]{}).ignoreSSL(false).build();*/// Configure Credentials authentication information, including ak, secret, tokenStaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()// Please ensure that the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set..accessKeyId("xxx").accessKeySecret("xxx")//.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // use STS token.build());// Configure the ClientAsyncClient client = AsyncClient.builder().region("cn-hangzhou") // Region ID//.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient).credentialsProvider(provider)//.serviceConfiguration(Configuration.create()) // Service-level configuration// Client-level configuration rewrite, can set Endpoint, Http request parameters, etc..overrideConfiguration(ClientOverrideConfiguration.create()// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi.setEndpointOverride("dysmsapi.aliyuncs.com")//.setConnectTimeout(Duration.ofSeconds(30))).build();// Parameter settings for API requestSendSmsRequest sendSmsRequest = SendSmsRequest.builder().signName("阿里云短信测试").templateCode("SMS_134567").phoneNumbers(phone).templateParam("{\"code\":\""+code+"\"}")// Request-level configuration rewrite, can set Http request parameters, etc.// .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders())).build();// Asynchronously get the return value of the API requestCompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);// Synchronously get the return value of the API requestSendSmsResponse resp = response.get();System.out.println(new Gson().toJson(resp));// Asynchronous processing of return values/*response.thenAccept(resp -> {System.out.println(new Gson().toJson(resp));}).exceptionally(throwable -> { // Handling exceptionsSystem.out.println(throwable.getMessage());return null;});*/// Finally, close the clientclient.close();return resp;}
}

 6)发送短信

        也算是成功了吧


http://www.mrgr.cn/news/29625.html

相关文章:

  • itk c++ 3D医学图像刚性配准
  • 【与C++的邂逅】--- C++的IO流
  • wifi中的相干带宽
  • Windows系统下使用VS排查内存泄露的两种办法
  • 如何在 Qt 的 QListWidget 中逐行添加和显示数据
  • eNSP简单用法
  • 超级简约的B端页面,B端系统就该如此简化设计。
  • 自动化流程机器人(RPA)
  • Nginx反向代理出现502 Bad Gateway问题的解决方案
  • ram和rom的种类迭代和介绍
  • 【隐私计算篇】不经意传输协议(OT/OTE)的进一步补充
  • 自组织是管理者和成员的双向奔赴
  • 示波器的使用
  • Lucene详解介绍以及底层原理说明
  • maven pom文件中的变量定义
  • Qt安卓开发连接手机调试(红米K60为例)
  • 数据结构入门学习(全是干货)——树(中)
  • 【研发日记】嵌入式处理器技能解锁(六)——ARM的Cortex-M4内核
  • Mybatis中sql数组为空判断
  • 智能优化算法-遗传算法(GA)(附源码)