【若依RuoYi短信验证码登录】汇总

【若依RuoYi短信验证码登录】汇总

遇到一个场景,需要同时支持手机号或者邮箱和密码或者验证码进行登录的场景,故来记录一下。

说明:此流程主要是基于若依框架集成的多种方式登录,主要演示登录业务逻辑和前端登录密码和验证码切换组件和配置Security

一:后端登录业务逻辑代码:

因为有多个端,多个语言共享登录接口,所以,接口定义尽量简单,接口内的逻辑判断尽量全面,判断手机号还是邮箱登录,再判断密码还是验证码登录,验证完了之后,再去验证用户是否存在数据库中,如果是密码登录的,则需要对比密码,然后再创建一个登录的token,返回。

public AjaxResult login(LoginBody loginBody){

//验证手机号和邮箱是否符合格式或者是否为空

boolean isPhone = false;

//先判断是手机号还是邮箱登录

if(StringUtils.isNotEmpty(loginBody.getTel()) && Pattern.compile("^[1][1,2,3,4,5,6,7,8,9][0-9]{9}$").matcher(loginBody.getTel()).matches()){

isPhone = true;

}else if(StringUtils.isNotEmpty(loginBody.getEmail()) && loginBody.getEmail().matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){

isPhone = false;

}else{

return AjaxResult.error("登录失败,邮箱和手机号不能同时为空!");

}

//在判断是密码还是验证码登录

boolean isPassword = false;

if(StringUtils.isNotEmpty(loginBody.getPassword())){

isPassword = true;

}else if(StringUtils.isNotEmpty(loginBody.getCode())){

isPassword = false;

}else{

return AjaxResult.error("登录失败,密码和验证码不能同时为空!");

}

//验证码验证

if(!isPassword){

String codeKey = "0:" + isPhone? loginBody.getTel(): loginBody.getEmail());

String value = redisCache.getCacheObject(codeKey);

if (StringUtils.isNotEmpty(value)) {

if (!value.equals(loginBody.getCode())) {

return AjaxResult.error("验证码错误!");

}

}else{

return AjaxResult.error("验证码超时!");

}

}

// 用户验证

Authentication authentication = null;

try

{

if(isPassword){

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(isPhone? loginBody.getTel(): loginBody.getEmail(), loginBody.getPassword());

AuthenticationContextHolder.setContext(authenticationToken);

// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername

authentication = authenticationManager.authenticate(authenticationToken);

}else{

// 该方法会去调用UsernamePhoneUserDetailsServiceImpl.loadUserByUsername

authentication = authenticationManager.authenticate(new UsernamePhoneAuthenticationToken(isPhone? loginBody.getTel(): loginBody.getEmail()));

}

}

catch (Exception e)

{

if (e instanceof BadCredentialsException)

{

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));

throw new UserPasswordNotMatchException();

}

else

{

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));

throw new ServiceException(e.getMessage());

}

}

finally

{

AuthenticationContextHolder.clearContext();

}

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));

LoginUser loginUser = (LoginUser) authentication.getPrincipal();

recordLoginInfo(loginUser.getUserId());

// 生成token

return tokenService.createToken(loginUser);

}

二:前端登录密码和验证码切换组件:

三:配置Security:

按照Security的流程图可知,实现多种方式登录,只需要重写三个主要的组件,第一个用户认证处理过滤器,第二个用户认证token类,第三个,自定义短信登录身份认证。

1.参考UsernamePasswordAuthenticationToken类,继承AbstractAuthenticationToken,重写以下几个方法,自定义短信登录token验证。

/**

* 自定义短信登录token验证

*/

public class UsernamePhoneAuthenticationToken extends AbstractAuthenticationToken {

/**

* 手机号

*/

private final Object principal;

public UsernamePhoneAuthenticationToken(Object principals){

super(null);

this.principal = principals;

setAuthenticated(false);

}

public UsernamePhoneAuthenticationToken(Object principal, Collection authorities){

super(authorities);

this.principal = principal;

super.setAuthenticated(true);

}

@Override

public Object getCredentials() {

return null;

}

@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");

}

super.setAuthenticated(false);

}

@Override

public void eraseCredentials(){

super.eraseCredentials();

}

重写UserDetailsService类的loadUserByUsername方法,实现用户验证处理。

/**

* 用户验证处理

*/

@Service("userDetailsByPhone")

public class UsernamePhoneUserDetailsServiceImpl implements UserDetailsService {

private static final Logger logger = LoggerFactory.getLogger(UsernamePhoneUserDetailsServiceImpl.class);

@Autowired

private ISysUserService userService;

@Autowired

private SysUserMapper sysUserMapper;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

SysUser sysUser;

if(Pattern.compile("^[1][1,2,3,4,5,6,7,8,9][0-9]{9}$").matcher(username).matches()){

sysUser = sysUserMapper.selectUserByTel(username);

}else if(username.matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){

sysUser = sysUserMapper.selectUserByEmail(username);

}else{

throw new ServiceException("请使用手机号或者邮箱进行登录!");

}

if(StringUtils.isNull(sysUser)){

logger.info("登录用户:{} 不存在.", username);

throw new ServiceException("登录用户:" + username+ " 不存在");

}

return createLoginUser(sysUser);

}

public UserDetails createLoginUser(SysUser user){

return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));

}

3.注意,此时会有两个用户验证的处理类,一个是原来的UserDetailsServiceImpl,另一个是现在的UsernamePhoneUserDetailsServiceImpl,需要去SecurityConfig配置类去配置不同的用户认证业务类,通过@Qualifer指定注入的bean。

/**

* spring security配置

*

* @author victor_zhang

*/

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter

{

/**

* 自定义用户认证逻辑(账号密码)

*/

@Autowired

@Qualifier("userDetailsByPass")

private UserDetailsService userDetailsService;

/**

* 自定义用户认证逻辑(手机号验证码)

*/

@Autowired

@Qualifier("userDetailsByPhone")

private UserDetailsService userDetailsByPhone;

//此处省略若干代码......

}

4.自定义一个短信登录的身份鉴权, UserDetailsService 只负责根据用户名返回用户信息,AuthenticationProvider负责将 UserDetails 组装成 Authentication 向调用者返回。

/**

* 自定义短信登录身份认证

*/

public class UsernamePhoneAuthenticationProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

public UsernamePhoneAuthenticationProvider(UserDetailsService userDetailsService){

setUserDetailsService(userDetailsService);

}

/**

* 重写authentication方法,实现身份验证逻辑

*/

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

UsernamePhoneAuthenticationToken authenticationToken = (UsernamePhoneAuthenticationToken) authentication;

String phone = (String) authenticationToken.getPrincipal();

//委托 UserDetailsService 查找系统用户

UserDetails userDetails = userDetailsService.loadUserByUsername(phone);

//鉴权成功,返回一个拥有鉴权的AbstractAuthenticationToken

UsernamePhoneAuthenticationToken authenticationTokenRes = new UsernamePhoneAuthenticationToken(userDetails, userDetails.getAuthorities());

authenticationTokenRes.setDetails(authenticationToken.getDetails());

return authenticationTokenRes;

}

/**

* 重写supports方法,指定此AuthenticationProvider 仅支持短信验证码身份验证

*/

@Override

public boolean supports(Class authentication){

return UsernamePhoneAuthenticationToken.class.isAssignableFrom(authentication);

}

public UserDetailsService getUserDetailsService() {

return userDetailsService;

}

public void setUserDetailsService(UserDetailsService userDetailsService) {

this.userDetailsService = userDetailsService;

}

5.配置SecurityConfig 的configure方法

/**

* spring security配置

*

* @author victor_zhang

*/

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter

{

/**

* 自定义用户认证逻辑(账号密码)

*/

@Autowired

@Qualifier("userDetailsByPass")

private UserDetailsService userDetailsService;

/**

* 自定义用户认证逻辑(手机号验证码)

*/

@Autowired

@Qualifier("userDetailsByPhone")

private UserDetailsService userDetailsByPhone;

/**

* 认证失败处理类

*/

@Autowired

private AuthenticationEntryPointImpl unauthorizedHandler;

/**

* 退出处理类

*/

@Autowired

private LogoutSuccessHandlerImpl logoutSuccessHandler;

/**

* token认证过滤器

*/

@Autowired

private JwtAuthenticationTokenFilter authenticationTokenFilter;

//此处省略n行代码......

/**

* 身份认证接口

*/

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception

{

//手机或邮箱的验证码的验证

auth.authenticationProvider(new UsernamePhoneAuthenticationProvider(userDetailsByPhone));

//账号密码的验证

auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

}

大概就这么多,如果有更好的方式,欢迎交流。

相关推荐

office365人工客服电话 如何将Windows PC变成Wi-Fi热点?这里提供详细步骤

如何将Windows PC变成Wi-Fi热点?这里提供详细步骤

📅 07-08 👁️ 7925
英国365bet官方 霓虹深渊武器强度排名 霓虹深渊最好用的武器

霓虹深渊武器强度排名 霓虹深渊最好用的武器

📅 09-26 👁️ 2410
office365人工客服电话 8条长城主题国家级旅游线路发布!山西多地入选

8条长城主题国家级旅游线路发布!山西多地入选

📅 10-02 👁️ 4403
office365人工客服电话 “中国美味”燃爆年轻圈层,卫龙凭什么“逆势”增长?

“中国美味”燃爆年轻圈层,卫龙凭什么“逆势”增长?

📅 07-29 👁️ 5443