- 前言
接上篇, 以短信登录为例, 记录一下 Spring Secyrity 自定义登录流程的方式.
项目 GitHub 地址: sign-demo
- 原理
简单介绍一下 Spring Secyrity 的认证流程, 这里以原生的表单登录为例如下图:
UsernamePasswordAuthenticationToken: Authentication接口的实现, 负责保存登录用户的认证信息。
UsernamePasswordAuthenticationFilter: 认证认证过滤器, 配置在过滤器链上, 负责拦截登录请求, 提取请求中的参数构建为未认证的 Authentication 并传递给 AuthenticationProvider。
ProviderManager: 管理系统中的所有 AuthenticationProvider实现, 负责将对应的登录请求导向对应的AuthenticationProvider实现类。
DaoAuthenticationProvider: AuthenticationProvider实现类, 负责处理实际的认证逻辑。
UserDetailService: 负责查询用户相关逻辑。
- 具体实现
按照流程图实现指定接口即可:
1. SmsCodeAuthenticationToken
package com.niu.sign.authentication.sms;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 短信短信登录认证Token
*
* @version 1.0 [2020/10/15 14:45]
* @author [nza]
* @createTime [2020/10/15 14:45]
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = -6833557082904186230L;
private final Object principal;
/**
* 未认证调用
*/
public SmsCodeAuthenticationToken(Object mobile) {
super(null);
this.principal = mobile;
this.setAuthenticated(false);
}
/**
* 已认证调用
*/
public SmsCodeAuthenticationToken(Object principal, Collection authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return principal;
}
@Override
public void setAuthenticated(boolean authenticated) {
super.setAuthenticated(authenticated);
}
}
2. SmsAuthenticationFilter.java
SmsAuthenticationFilter.java
package com.niu.sign.authentication.sms;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 短信登录过滤器
*
* @author [nza]
* @version 1.0 [2020/10/15 15:01]
* @createTime [2020/10/15 15:01]
*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* 请求方式
*/
private static final String POST = "POST";
/**
* 手机号参数
*/
private String mobileParameter = "mobile";
/**
* 仅支持 post 方法请求
*/
private boolean postOnly = true;
public SmsAuthenticationFilter() {
super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (this.postOnly && !POST.equals(request.getMethod())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String mobile = this.obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
// 调用未认证构造函数
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
this.setDetails(request, authRequest);
// 调用认证方法
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public String getMobileParameter() {
return mobileParameter;
}
}
3. SmsCodeAuthenticationProvider.java
package com.niu.sign.authentication.sms;
import com.niu.sign.service.SysUserService;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* SmsCodeAuthenticationProvider
*
* @author [nza]
* @version 1.0 [2020/10/15 15:08]
* @createTime [2020/10/15 15:08]
*/
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
/**
* 用户业务类
*/
private final SysUserService sysUserService;
public SmsCodeAuthenticationProvider(UserDetailsService userDetailsService) {
this.sysUserService = (SysUserService) userDetailsService;
}
/**
* 短信登录认证逻辑
*
* @param authentication token
* @return {@link org.springframework.security.core.Authentication}
* @author nza
* @createTime 2020/10/15 15:10
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken token = (SmsCodeAuthenticationToken) authentication;
UserDetails userDetails = sysUserService.loadUserByMobile((String) token.getPrincipal());
if (userDetails == null) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
// 调用已认证构造函数
SmsCodeAuthenticationToken res = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
res.setDetails(token.getDetails());
return res;
}
/**
* 判断当前provider是否支持当前登录方式
*
* @param aClass 登陆方式
* @return boolean
* @author nza
* @createTime 2020/10/15 15:09
*/
@Override
public boolean supports(Class aClass) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
}
4. SysUserDetailServiceImpl.java
package com.niu.sign.service.impl;
import com.niu.sign.pojo.SysUser;
import com.niu.sign.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* 用户详情服务
*
* @author [nza]
* @version 1.0 [2020/10/10 16:25]
* @createTime [2020/10/10 16:25]
*/
@Service
@Slf4j
public class SysUserDetailServiceImpl implements SysUserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username: " + username);
// todo: 自己登录实现逻辑
SysUser user = new SysUser();
user.setName("admin");
user.setPassword(passwordEncoder.encode("admin"));
return user;
}
@Override
public SysUser loadUserByMobile(String mobile) throws UsernameNotFoundException {
log.info("mobile: " + mobile);
SysUser user = new SysUser();
user.setName("admin");
user.setPassword(passwordEncoder.encode("admin"));
return user;
}
}
- 配置
SmsCodeAuthenticationSecurityConfig.java
package com.niu.sign.config;
import com.niu.sign.authentication.sms.SmsAuthenticationFilter;
import com.niu.sign.authentication.sms.SmsCodeAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
/**
* 短信认证配置类
*
* @author [nza]
* @version 1.0 [2020/10/15 15:27]
* @createTime [2020/10/15 15:27]
*/
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) {
// 配置filter
SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
// 配置 provider
SmsCodeAuthenticationProvider provider = new SmsCodeAuthenticationProvider(userDetailsService);
// 添加到过滤器链上
http.authenticationProvider(provider)
.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
}
},>
具体代码可下载源码查看