新增短信登录功能

This commit is contained in:
阿沐 2022-08-30 22:39:03 +08:00
parent b5010197d3
commit 0f09fce119
22 changed files with 631 additions and 208 deletions

View File

@ -0,0 +1,39 @@
package net.maku.api.module.message;
import java.util.Map;
/**
* 短信服务API
*
* @author 阿沐 babamu@126.com
*/
public interface SmsApi {
/**
* 发送短信
*
* @param mobile 手机号
* @param params 参数
* @return 是否发送成功
*/
boolean send(String mobile, Map<String, String> params);
/**
* 发送短信
*
* @param mobile 手机号
* @param key 参数KEY
* @param value 参数Value
* @return 是否发送成功
*/
boolean sendCode(String mobile, String key, String value);
/**
* 效验短信验证码
*
* @param mobile 手机号
* @param code 验证码
* @return 是否效验成功
*/
boolean verifyCode(String mobile, String code);
}

View File

@ -16,6 +16,11 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>net.maku</groupId>
<artifactId>fast-boot-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
</dependency>
@ -31,6 +36,7 @@
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,55 @@
package net.maku.message.api;
import lombok.AllArgsConstructor;
import net.maku.api.module.message.SmsApi;
import net.maku.message.cache.SmsSendCache;
import net.maku.message.sms.service.SmsService;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 短信服务Api
*
* @author 阿沐 babamu@126.com
*/
@Component
@AllArgsConstructor
public class SmsApiImpl implements SmsApi {
private final SmsService smsService;
private final SmsSendCache smsSendCache;
@Override
public boolean send(String mobile, Map<String, String> params) {
return smsService.send(mobile, params);
}
@Override
public boolean sendCode(String mobile, String key, String value) {
// 短信参数
Map<String, String> params = new HashMap<>();
params.put(key, value);
// 发送短信
boolean flag = smsService.send(mobile, params);
if (flag) {
smsSendCache.saveCode(mobile, value);
}
return flag;
}
@Override
public boolean verifyCode(String mobile, String code) {
String value = smsSendCache.getCode(mobile);
if (value != null) {
// 删除短信验证码
smsSendCache.deleteCode(mobile);
// 效验
return value.equalsIgnoreCase(code);
}
return false;
}
}

View File

@ -0,0 +1,43 @@
package net.maku.message.cache;
import lombok.AllArgsConstructor;
import net.maku.framework.common.cache.RedisCache;
import org.springframework.stereotype.Service;
/**
* 短信发送 Cache
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class SmsSendCache {
private final RedisCache redisCache;
/**
* 获取发送手机短信验证码KEY
*
* @param mobile 手机号
* @return KEY
*/
private String getCodeKey(String mobile) {
return "message:sms:code" + mobile;
}
public void saveCode(String mobile, String code) {
String key = getCodeKey(mobile);
// 保存到Redis有效期10分钟
redisCache.set(key, code, 10 * 60);
}
public String getCode(String mobile) {
String key = getCodeKey(mobile);
return (String) redisCache.get(key);
}
public void deleteCode(String mobile) {
String key = getCodeKey(mobile);
redisCache.delete(key);
}
}

View File

@ -0,0 +1,33 @@
package net.maku.security.service;
import lombok.AllArgsConstructor;
import net.maku.framework.security.mobile.MobileUserDetailsService;
import net.maku.system.dao.SysUserDao;
import net.maku.system.entity.SysUserEntity;
import net.maku.system.service.SysUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 手机验证码登录 MobileUserDetailsService
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class MobileUserDetailsServiceImpl implements MobileUserDetailsService {
private final SysUserDetailsService sysUserDetailsService;
private final SysUserDao sysUserDao;
@Override
public UserDetails loadUserByMobile(String mobile) throws UsernameNotFoundException {
SysUserEntity userEntity = sysUserDao.getByMobile(mobile);
if (userEntity == null) {
throw new UsernameNotFoundException("手机号或验证码错误");
}
return sysUserDetailsService.getUserDetails(userEntity);
}
}

View File

@ -0,0 +1,22 @@
package net.maku.security.service;
import lombok.AllArgsConstructor;
import net.maku.api.module.message.SmsApi;
import net.maku.framework.security.mobile.MobileVerifyCodeService;
import org.springframework.stereotype.Service;
/**
* 短信验证码效验
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class MobileVerifyCodeServiceImpl implements MobileVerifyCodeService {
private final SmsApi smsApi;
@Override
public boolean verifyCode(String mobile, String code) {
return smsApi.verifyCode(mobile, code);
}
}

View File

@ -0,0 +1,33 @@
package net.maku.security.service;
import lombok.AllArgsConstructor;
import net.maku.system.dao.SysUserDao;
import net.maku.system.entity.SysUserEntity;
import net.maku.system.service.SysUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 账号登录 UserDetailsService
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserDetailsService sysUserDetailsService;
private final SysUserDao sysUserDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity userEntity = sysUserDao.getByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("用户名或密码错误");
}
return sysUserDetailsService.getUserDetails(userEntity);
}
}

View File

@ -1,28 +1,19 @@
package net.maku.system.controller;
import cn.hutool.core.lang.UUID;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import net.maku.framework.common.constant.Constant;
import net.maku.framework.common.utils.Result;
import net.maku.framework.security.cache.TokenStoreCache;
import net.maku.framework.security.user.UserDetail;
import net.maku.framework.security.utils.TokenUtils;
import net.maku.system.enums.LoginOperationEnum;
import net.maku.system.service.SysAuthService;
import net.maku.system.service.SysCaptchaService;
import net.maku.system.service.SysLogLoginService;
import net.maku.system.vo.SysLoginVO;
import net.maku.system.vo.SysAccountLoginVO;
import net.maku.system.vo.SysCaptchaVO;
import net.maku.system.vo.SysMobileLoginVO;
import net.maku.system.vo.SysTokenVO;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* 认证管理
@ -35,72 +26,44 @@ import java.util.Map;
@AllArgsConstructor
public class SysAuthController {
private final SysCaptchaService sysCaptchaService;
private final TokenStoreCache tokenStoreCache;
private final AuthenticationManager authenticationManager;
private final SysLogLoginService sysLogLoginService;
private final SysAuthService sysAuthService;
@GetMapping("captcha")
@Operation(summary = "验证码")
public Result<Map<String, Object>> captcha() {
// 生成key
String key = UUID.randomUUID().toString();
// 生成base64验证码
String image = sysCaptchaService.generate(key);
public Result<SysCaptchaVO> captcha() {
SysCaptchaVO captchaVO = sysCaptchaService.generate();
// 封装返回数据
Map<String, Object> data = new HashMap<>();
data.put("key", key);
data.put("image", image);
return Result.ok(data);
return Result.ok(captchaVO);
}
@PostMapping("login")
@Operation(summary = "登录")
public Result<SysTokenVO> login(@RequestBody SysLoginVO login) {
// 验证码效验
boolean flag = sysCaptchaService.validate(login.getKey(), login.getCaptcha());
if (!flag) {
// 保存登录日志
sysLogLoginService.save(login.getUsername(), Constant.FAIL, LoginOperationEnum.CAPTCHA_FAIL.getValue());
@Operation(summary = "账号密码登录")
public Result<SysTokenVO> login(@RequestBody SysAccountLoginVO login) {
SysTokenVO token = sysAuthService.loginByAccount(login);
return Result.error("验证码错误");
}
return Result.ok(token);
}
Authentication authentication;
try {
// 用户认证
authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()));
} catch (BadCredentialsException e) {
return Result.error("用户名密码错误");
}
@PostMapping("send/code")
@Operation(summary = "发送短信验证码")
public Result<String> sendCode(String mobile) {
sysAuthService.sendCode(mobile);
// 用户信息
UserDetail user = (UserDetail) authentication.getPrincipal();
return Result.ok();
}
// 生成 accessToken
String accessToken = TokenUtils.generator();
@PostMapping("mobile")
@Operation(summary = "手机号登录")
public Result<SysTokenVO> mobile(@RequestBody SysMobileLoginVO login) {
SysTokenVO token = sysAuthService.loginByMobile(login);
// 保存用户信息到缓存
tokenStoreCache.saveUser(accessToken, user);
return Result.ok(new SysTokenVO(accessToken));
return Result.ok(token);
}
@PostMapping("logout")
@Operation(summary = "退出")
public Result<String> logout(HttpServletRequest request) {
String accessToken = TokenUtils.getAccessToken(request);
// 用户信息
UserDetail user = tokenStoreCache.getUser(accessToken);
// 删除用户信息
tokenStoreCache.deleteUser(accessToken);
// 保存登录日志
sysLogLoginService.save(user.getUsername(), Constant.SUCCESS, LoginOperationEnum.LOGOUT_SUCCESS.getValue());
sysAuthService.logout(TokenUtils.getAccessToken(request));
return Result.ok();
}

View File

@ -0,0 +1,41 @@
package net.maku.system.service;
import net.maku.system.vo.SysAccountLoginVO;
import net.maku.system.vo.SysMobileLoginVO;
import net.maku.system.vo.SysTokenVO;
/**
* 权限认证服务
*
* @author 阿沐 babamu@126.com
*/
public interface SysAuthService {
/**
* 账号密码登录
*
* @param login 登录信息
*/
SysTokenVO loginByAccount(SysAccountLoginVO login);
/**
* 手机短信登录
*
* @param login 登录信息
*/
SysTokenVO loginByMobile(SysMobileLoginVO login);
/**
* 发送手机验证码
*
* @param mobile 手机号
*/
void sendCode(String mobile);
/**
* 退出登录
*
* @param accessToken accessToken
*/
void logout(String accessToken);
}

View File

@ -1,5 +1,7 @@
package net.maku.system.service;
import net.maku.system.vo.SysCaptchaVO;
/**
* 验证码
*
@ -8,11 +10,8 @@ package net.maku.system.service;
public interface SysCaptchaService {
/**
* 生成验证码
*
* @param key key
* @return 返回base64图片验证码
*/
String generate(String key);
SysCaptchaVO generate();
/**
* 验证码效验

View File

@ -0,0 +1,12 @@
package net.maku.system.service;
import net.maku.system.entity.SysUserEntity;
import org.springframework.security.core.userdetails.UserDetails;
public interface SysUserDetailsService {
/**
* 获取 UserDetails 对象
*/
UserDetails getUserDetails(SysUserEntity userEntity);
}

View File

@ -24,10 +24,13 @@ public interface SysUserService extends BaseService<SysUserEntity> {
void delete(List<Long> idList);
SysUserVO getByMobile(String mobile);
/**
* 修改密码
* @param id 用户ID
* @param newPassword 新密码
*
* @param id 用户ID
* @param newPassword 新密码
*/
void updatePassword(Long id, String newPassword);

View File

@ -0,0 +1,122 @@
package net.maku.system.service.impl;
import cn.hutool.core.util.RandomUtil;
import lombok.AllArgsConstructor;
import net.maku.api.module.message.SmsApi;
import net.maku.framework.common.constant.Constant;
import net.maku.framework.common.exception.FastException;
import net.maku.framework.security.cache.TokenStoreCache;
import net.maku.framework.security.mobile.MobileAuthenticationToken;
import net.maku.framework.security.user.UserDetail;
import net.maku.framework.security.utils.TokenUtils;
import net.maku.system.enums.LoginOperationEnum;
import net.maku.system.service.SysAuthService;
import net.maku.system.service.SysCaptchaService;
import net.maku.system.service.SysLogLoginService;
import net.maku.system.service.SysUserService;
import net.maku.system.vo.SysAccountLoginVO;
import net.maku.system.vo.SysMobileLoginVO;
import net.maku.system.vo.SysTokenVO;
import net.maku.system.vo.SysUserVO;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
/**
* 权限认证服务
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class SysAuthServiceImpl implements SysAuthService {
private final SysCaptchaService sysCaptchaService;
private final TokenStoreCache tokenStoreCache;
private final AuthenticationManager authenticationManager;
private final SysLogLoginService sysLogLoginService;
private final SysUserService sysUserService;
private final SmsApi smsApi;
@Override
public SysTokenVO loginByAccount(SysAccountLoginVO login) {
// 验证码效验
boolean flag = sysCaptchaService.validate(login.getKey(), login.getCaptcha());
if (!flag) {
// 保存登录日志
sysLogLoginService.save(login.getUsername(), Constant.FAIL, LoginOperationEnum.CAPTCHA_FAIL.getValue());
throw new FastException("验证码错误");
}
Authentication authentication;
try {
// 用户认证
authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()));
} catch (BadCredentialsException e) {
throw new FastException("用户名或密码错误");
}
// 用户信息
UserDetail user = (UserDetail) authentication.getPrincipal();
// 生成 accessToken
String accessToken = TokenUtils.generator();
// 保存用户信息到缓存
tokenStoreCache.saveUser(accessToken, user);
return new SysTokenVO(accessToken);
}
@Override
public SysTokenVO loginByMobile(SysMobileLoginVO login) {
Authentication authentication;
try {
// 用户认证
authentication = authenticationManager.authenticate(
new MobileAuthenticationToken(login.getMobile(), login.getCode()));
} catch (BadCredentialsException e) {
throw new FastException("手机号或验证码错误");
}
// 用户信息
UserDetail user = (UserDetail) authentication.getPrincipal();
// 生成 accessToken
String accessToken = TokenUtils.generator();
// 保存用户信息到缓存
tokenStoreCache.saveUser(accessToken, user);
return new SysTokenVO(accessToken);
}
@Override
public void sendCode(String mobile) {
// 生成6位验证码
String code = RandomUtil.randomNumbers(6);
SysUserVO user = sysUserService.getByMobile(mobile);
if (user == null) {
throw new FastException("手机号未注册");
}
// 发送短信
smsApi.sendCode(mobile, "code", code);
}
@Override
public void logout(String accessToken) {
// 用户信息
UserDetail user = tokenStoreCache.getUser(accessToken);
// 删除用户信息
tokenStoreCache.deleteUser(accessToken);
// 保存登录日志
sysLogLoginService.save(user.getUsername(), Constant.SUCCESS, LoginOperationEnum.LOGOUT_SUCCESS.getValue());
}
}

View File

@ -1,5 +1,6 @@
package net.maku.system.service.impl;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
@ -7,6 +8,7 @@ import lombok.AllArgsConstructor;
import net.maku.framework.common.cache.RedisCache;
import net.maku.framework.common.cache.RedisKeys;
import net.maku.system.service.SysCaptchaService;
import net.maku.system.vo.SysCaptchaVO;
import org.springframework.stereotype.Service;
/**
@ -20,17 +22,26 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
private final RedisCache redisCache;
@Override
public String generate(String key) {
public SysCaptchaVO generate() {
// 生成验证码key
String key = UUID.randomUUID().toString();
// 生成验证码
SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(5);
captcha.setCharType(Captcha.TYPE_DEFAULT);
String image = captcha.toBase64();
// 保存到缓存
key = RedisKeys.getCaptchaKey(key);
redisCache.set(key, captcha.text(), 300);
String redisKey = RedisKeys.getCaptchaKey(key);
redisCache.set(redisKey, captcha.text(), 300);
return captcha.toBase64();
// 封装返回数据
SysCaptchaVO captchaVO = new SysCaptchaVO();
captchaVO.setKey(key);
captchaVO.setImage(image);
return captchaVO;
}
@Override
@ -43,11 +54,7 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
String captcha = getCache(key);
// 效验成功
if (code.equalsIgnoreCase(captcha)) {
return true;
}
return false;
return code.equalsIgnoreCase(captcha);
}
private String getCache(String key) {

View File

@ -1,19 +1,17 @@
package net.maku.security.service;
package net.maku.system.service.impl;
import lombok.AllArgsConstructor;
import net.maku.framework.security.user.UserDetail;
import net.maku.system.convert.SysUserConvert;
import net.maku.system.dao.SysRoleDao;
import net.maku.system.dao.SysRoleDataScopeDao;
import net.maku.system.dao.SysUserDao;
import net.maku.system.entity.SysUserEntity;
import net.maku.system.enums.DataScopeEnum;
import net.maku.system.enums.UserStatusEnum;
import net.maku.system.service.SysMenuService;
import net.maku.system.service.SysOrgService;
import net.maku.system.service.SysUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -21,26 +19,20 @@ import java.util.List;
import java.util.Set;
/**
* UserDetailsService
* 用户 UserDetails 信息
*
* @author 阿沐 babamu@126.com
*/
@Service
@AllArgsConstructor
public class FastUserDetailsService implements UserDetailsService {
public class SysUserDetailsServiceImpl implements SysUserDetailsService {
private final SysMenuService sysMenuService;
private final SysOrgService sysOrgService;
private final SysUserDao sysUserDao;
private final SysRoleDao sysRoleDao;
private final SysRoleDataScopeDao sysRoleDataScopeDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity userEntity = sysUserDao.getByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("用户名或密码错误");
}
public UserDetails getUserDetails(SysUserEntity userEntity) {
// 转换成UserDetail对象
UserDetail userDetail = SysUserConvert.INSTANCE.convertDetail(userEntity);
@ -91,5 +83,4 @@ public class FastUserDetailsService implements UserDetailsService {
return new ArrayList<>();
}
}

View File

@ -11,10 +11,10 @@ import net.maku.system.dao.SysUserDao;
import net.maku.system.entity.SysUserEntity;
import net.maku.system.enums.SuperAdminEnum;
import net.maku.system.query.SysRoleUserQuery;
import net.maku.system.query.SysUserQuery;
import net.maku.system.service.SysUserPostService;
import net.maku.system.service.SysUserRoleService;
import net.maku.system.service.SysUserService;
import net.maku.system.query.SysUserQuery;
import net.maku.system.vo.SysUserVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -49,7 +49,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
return new PageResult<>(SysUserConvert.INSTANCE.convertList(list), page.getTotal());
}
private Map<String, Object> getParams(SysUserQuery query){
private Map<String, Object> getParams(SysUserQuery query) {
Map<String, Object> params = new HashMap<>();
params.put("username", query.getUsername());
params.put("mobile", query.getMobile());
@ -62,7 +62,6 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
}
@Override
@Transactional(rollbackFor = Exception.class)
public void save(SysUserVO vo) {
@ -71,13 +70,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
// 判断用户名是否存在
SysUserEntity user = baseMapper.getByUsername(entity.getUsername());
if(user != null) {
if (user != null) {
throw new FastException("用户名已经存在");
}
// 判断手机号是否存在
user = baseMapper.getByMobile(entity.getMobile());
if(user != null) {
if (user != null) {
throw new FastException("手机号已经存在");
}
@ -97,13 +96,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
// 判断用户名是否存在
SysUserEntity user = baseMapper.getByUsername(entity.getUsername());
if(user != null && !user.getId().equals(entity.getId())) {
if (user != null && !user.getId().equals(entity.getId())) {
throw new FastException("用户名已经存在");
}
// 判断手机号是否存在
user = baseMapper.getByMobile(entity.getMobile());
if(user != null && !user.getId().equals(entity.getId())) {
if (user != null && !user.getId().equals(entity.getId())) {
throw new FastException("手机号已经存在");
}
@ -130,6 +129,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
}
@Override
public SysUserVO getByMobile(String mobile) {
SysUserEntity user = baseMapper.getByMobile(mobile);
return SysUserConvert.INSTANCE.convert(user);
}
@Override
public void updatePassword(Long id, String newPassword) {
// 修改密码
SysUserEntity user = getById(id);

View File

@ -6,13 +6,13 @@ import lombok.Data;
import java.io.Serializable;
/**
* 用户登录
* 账号登录
*
* @author 阿沐 babamu@126.com
*/
@Data
@Schema(description = "用户登录")
public class SysLoginVO implements Serializable {
@Schema(description = "账号登录")
public class SysAccountLoginVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "用户名")

View File

@ -0,0 +1,23 @@
package net.maku.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 图片验证码
*
* @author 阿沐 babamu@126.com
*/
@Data
@Schema(description = "图片验证码")
public class SysCaptchaVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "key")
private String key;
@Schema(description = "image base64")
private String image;
}

View File

@ -0,0 +1,23 @@
package net.maku.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 手机号登录
*
* @author 阿沐 babamu@126.com
*/
@Data
@Schema(description = "手机号登录")
public class SysMobileLoginVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "手机号")
private String mobile;
@Schema(description = "验证码")
private String code;
}

View File

@ -2,4 +2,6 @@ auth:
ignore_urls:
- /sys/auth/captcha
- /sys/auth/login
- /sys/auth/send/code
- /sys/auth/mobile
- /upload/**