springsecurity短信登录
继springboot+springsecurity+JWT文章,实现短信登录
springboot+springsecurity+JWT
短信登录代码在SendSMs中,SendSmsSecurityConfig类为配置类,需引入SecurityConfig类中,也可直接在SecurityConfig类配置
短信验证流程:
文中把短信信息保存到redis中进行校验
在controller写获取短信验证码的类
/**
* 发送短信登录
*
* 根据实际获取短信验证码方式进行编写
*/
@RestController
@RequestMapping("/")
public class SendSmsController {
@Autowired
StringRedisTemplate stringRedisTemplate;
//获取验证码
@RequestMapping("/SendSms")
public String sendSms(@RequestParam("/telephone") String telephone) {
//随机生成4位数字验证码
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
//把验证码存储到redis中过期时间为120秒
stringRedisTemplate.opsForValue().set(telephone, code, 120, TimeUnit.SECONDS);
return null;
}
}
为了方便在test中进行测试无需在controller发送短信
@SpringBootTest
@RunWith(SpringRunner.class)
public class testRedis {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testredis(){
stringRedisTemplate.opsForValue().set("1","1234",1200, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire("1", TimeUnit.SECONDS);
System.out.println(expire);
String s = stringRedisTemplate.opsForValue().get("1");
System.out.println(s);
}
@Test
public void testredis1(){
//把key,value值保存到redis中,时间为1200
stringRedisTemplate.opsForValue().set("123","1234",1200, TimeUnit.SECONDS);
//根据key值查询剩余时间
Long expire = stringRedisTemplate.getExpire("123", TimeUnit.SECONDS);
System.out.println(expire);
//根据key值输出value
String s = stringRedisTemplate.opsForValue().get("123");
System.out.println(s);
}
}
SendSmsFilter 类
配置验证码校验的过滤器
继承OncePerRequestFilter类,每一次运行都会优先执行SendSmsFilter类
/**
* 进行验证码校验的过滤器
*/
@Component
public class SendSmsFilter extends OncePerRequestFilter {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
//请求路径为Smslogin,并且为post类型
if (StringUtils.equals("/Smslogin",request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
//获取手机号
String username = request.getParameter("telephone");
//获取验证码
String password = request.getParameter("word");
//获取redis中保存的验证码
String code = stringRedisTemplate.opsForValue().get(username);
if (code != null && password != null && password.equals(code)) {
//删除redis中保存的验证码
stringRedisTemplate.delete(username);
System.out.println("验证码输入正确");
}else {
throw new InternalAuthenticationServiceException("验证码错误");
}
}
chain.doFilter(request, response);
}
}
“/Smslogin”,"telephone"是在SendSmsAuthenticationFilter类中自定义的。
SendSmsAuthenticationFilter类
SendSmsAuthenticationFilter相当于图片中的SmsAuthenticationFilter
该类模拟 UsernamePasswordAuthenticationFilter类使用手机号进行身份验证的一个过滤器
/**
*
* UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter针对使用用户名和密码进行身份验证而定制化的一个过滤器。
*
*
* 模拟 UsernamePasswordAuthenticationFilter类使用手机号进行身份验证的一个过滤器
*/
public class SendSmsAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_TELEPHONE_KEY = "telephone";
//验证短信验证码,传递手机号的参数的名称【telephone】
private String telephoneParameter = SPRING_SECURITY_FORM_TELEPHONE_KEY;
//指定请求时post形式
private boolean postOnly = true;
public SendSmsAuthenticationFilter() {
//拦截访问路径,访问请求方式
super(new AntPathRequestMatcher("/Smslogin", "POST"));
}
@Override
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 telephone = obtainTelephone(request);
if (telephone == null) {
telephone = "";
}
telephone = telephone.trim();
//自定义SendSmsAuthenticationToken类实现其方法
SendSmsAuthenticationToken authRequest = new SendSmsAuthenticationToken(
telephone);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainTelephone(HttpServletRequest request) {
return request.getParameter(telephoneParameter);
}
/*
* 功能描述:提供身份验证请求的详细属性
* 入参:[request 为此创建身份验证请求, authRequest 详细信息集的身份验证请求对象]
*/
protected void setDetails(HttpServletRequest request,
SendSmsAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/*
* 功能描述:设置用于获取用户名的参数名的登录请求。
* 入参:[telephoneParameter 默认为“用户名”。]
*/
public void setTelephoneParameter(String telephoneParameter) {
Assert.hasText(telephoneParameter, "Telephone parameter must not be empty or null");
this.telephoneParameter = telephoneParameter;
}
/**
* 功能描述:定义此筛选器是否只允许HTTP POST请求。如果设置为true,则接收不到POST请求将立即引发异常并不再继续身份认证
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getTelephoneParameter() {
return telephoneParameter;
}
}
SendSmsAuthenticationToken类
SendSmsAuthenticationToken相当于图片中的SmsAuthenticationToken
该类模拟 UsernamePasswordAuthenticationToken类
/**
*
* 模拟 UsernamePasswordAuthenticationToken类
*
*/
public class SendSmsAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 510L;
//存放认证信息,认证之前放的是手机号,认证之后UserDetails
private final Object principal;
/**
* 功能描述:创建用户身份验证令牌需要用到此构造函数
* 返回值:通过身份验证的代码返回false
*/
public SendSmsAuthenticationToken(Object principal) {
super((Collection)null);
this.principal = principal;
this.setAuthenticated(false);
}
/**
* 功能描述:产生身份验证令牌
* @param principal
* @param authorities
*/
public SendSmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
@Override
public Object getCredentials() {
return null;
}
}
SendSmsAuthenticationProvider类
SendSmsAuthenticationProvider相当于图片中的SmsAuthenticationProvider
该类模拟DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider类中实现的AuthenticationProvider类
/**
* 模拟:DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider类中实现的AuthenticationProvider类
*/
public class SendSmsAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
/**
*实现AuthenticationProvider类中的两个方法
*
* 1,授权
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SendSmsAuthenticationToken authenticationToken = (SendSmsAuthenticationToken)authentication;
UserDetails userDetails = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
if (userDetails == null){
throw new InternalAuthenticationServiceException("无法根据手机号获取用户信息");
}
SendSmsAuthenticationToken authenticationResult = new SendSmsAuthenticationToken(userDetails,userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/**
* 2,通过不同的Token进行不同的Provider验证
*/
@Override
public boolean supports(Class<?> aClass) {
return SendSmsAuthenticationToken.class.isAssignableFrom(aClass);
}
}
SendSmsSecurityConfig类
配置SendSmsSecurityConfig类用于短信认证的
@Component
public class SendSmsSecurityConfig
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
MyUserDetailsService myUserDetailsService;
@Autowired
SendSmsFilter sendSmsFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
//认证授权
SendSmsAuthenticationFilter sendSmsAuthenticationFilter = new SendSmsAuthenticationFilter();
sendSmsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
sendSmsAuthenticationFilter.setAuthenticationSuccessHandler(new LoginSuccessHandler());
sendSmsAuthenticationFilter.setAuthenticationFailureHandler(new LoginFailureHandler());
SendSmsAuthenticationProvider sendSmsAuthenticationProvider = new SendSmsAuthenticationProvider();
sendSmsAuthenticationProvider.setUserDetailsService(myUserDetailsService);
//sendSmsFilter在UsernamePasswordAuthenticationFilter前面执行
http.addFilterBefore(sendSmsFilter, UsernamePasswordAuthenticationFilter.class);
http.authenticationProvider(sendSmsAuthenticationProvider)
.addFilterAfter(sendSmsAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);
}
}
在SecurityConfig类中声明引入
在Postman中进行测试
telephone为在SendSmsAuthenticationFilter类自定义的参数
执行成功后会在LoginSuccessHandler类(登陆成功处理)中生成token ——参考springboot-springsecurity-JWT
在Headers中会生成token在验证时,在Key中存放 Authorization ,在Value中把生成的token保存进去,然后会在MyJwtTokenFilter类中进行令牌校验
成功示例:
继springboot+springsecurity+JWT文章,实现短信登录
springboot+springsecurity+JWT https://blog.csdn.net/weixin_45498999/article/details/105973865
本文参考https://www.kancloud.cn/hanxt/springsecurity/1472507
视频链接:
https://www.bilibili.com/video/av76522147/