手把手教你用Java实现用户登录注册的功能

手把手教你用Java实现用户登录注册的功能说起用户登录注册其实主要还是几个点 首先第一个就是我们常说的一些验证码

大家好,欢迎来到IT知识分享网。

登陆注册功能

说起用户登录注册其实主要还是几个点,首先第一个就是我们常说的一些验证码。因为验证码可以防止用户频繁的请求接口,比如有一些刻意攻击的请求用来检测账户是否存在,验证码起到了至关重要的一个作用防止重复恶意请求。接着就是一个用户的一个加密密码加密,不要小看这个加密,虽然说加密的方式千变万化,但是作为微服务程序来说,大部分网站还是会用HTTPS的证书,传输还是加密传输的,只是到服务端才进行加密校验。所以总的来说只是存的数据库的密码是进行一个加密的,这里我们采用的是一个加盐的md5加密的方式,虽然说md5也之前被激活成功教程过,但是你只要多包几层应该是没有关系的,另外你还配了加盐,所以也是ok的。

手把手教你用Java实现用户登录注册的功能

登录

说起用户登录,就会涉及到有一个权限问题。因为用户他分普通用户和一些管理员用户之类的。简单的一些注解判断就可以处理好了。

public class PermissionInterceptor implements HandlerInterceptor { private static final Logger log = LogManager.getLogger(); @Autowired private TokenCacheService tokenCache; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { / * 静态目录 */ if (handler instanceof ResourceHttpRequestHandler) { return true; } else if (!(handler instanceof HandlerMethod)) { log.error("This handler object is not HandlerMethod instance! handler class: {}", handler.getClass()); return true; } log.debug("preHandle:{}", request.getRequestURI()); try { Method method = ((HandlerMethod) handler).getMethod(); Annotation[] annotations = method.getAnnotations(); / * 不需要登录的放行 */ for (Annotation annotation : annotations) { if (annotation.annotationType().equals(NotNeedLogin.class)) { return true; } } String token = request.getHeader(AuthConstants.TOKEN); / * 空的tokne */ if (StrUtil.isBlankIfStr(token)) { responseError(response, ResultCode.UNAUTHORIZED); return false; } / * token过期 */ CacheToken tokenCache = this.tokenCache.getTokenCache(token); if (tokenCache == null) { responseError(response, ResultCode.UNAUTHORIZED); return false; } / * 鉴权 */ / * 超级管理员直接放行 */ // if (SimUserRoleEnum.ADMIN.getCode() == tokenCache.getRoleLevel()) { // this.tokenCache.refreshToken(token); // return true; // } / * 普通用户鉴权 */ for (Annotation annotation : annotations) { / * 权限标识和超级管理员标识只会二选一,不存在 */ if (annotation.annotationType().equals(RequiresAuthority.class)) { RequiresAuthority authority = (RequiresAuthority) annotation; / * 管理员用户只能查看管理员的权限 */ if(tokenCache.hasAdmin() && !containsAdminPermissions(authority.value())){ log.info("用户{}无权限访问的{}方法{}权限需要:{}", method.getDeclaringClass().getName(), method.getName(), authority.value()); permissionError(response, ResultCode.FORBIDDEN); return false; / * 判断用户是否有权限 */ } else if (!containsPermissions(authority.value(), tokenCache.getAuthority())) { log.info("用户{}无权限访问的{}方法{}权限需要:{}", method.getDeclaringClass().getName(), method.getName(), authority.value()); permissionError(response, ResultCode.FORBIDDEN); return false; } } else if (!tokenCache.hasAdmin() && annotation.annotationType().equals(Administrator.class)) { / * 不是管理员访问超级管理员接口直接禁止 */ permissionError(response, ResultCode.FORBIDDEN); return false; } } this.tokenCache.refreshToken(token); return true; } catch (Exception e) { log.error("拦截异常:", e); if (e instanceof ApiException) { ApiException apiException = (ApiException) e; Response.filterError(response, apiException.getCode(), apiException.getMsg()); } else { responseError(response, ResultCode.FAILED); } return false; } } private boolean containsAdminPermissions(AuthorityEnum[] value) { for (AuthorityEnum auth : value) { / * 管理员权限只可以查看包含管理的权限的方法 * note:可能后期权限更改 */ if (auth.getCode() == AuthorityEnum.MANAGEMENT.getCode()) { return true; } } return false; } private void permissionError(HttpServletResponse response, IErrorCode unauthorized) { Response.permissionError(response, unauthorized.getCode(), unauthorized.getMessage()); } private void responseError(HttpServletResponse response, IErrorCode unauthorized) { Response.filterError(response, unauthorized.getCode(), unauthorized.getMessage()); } / * 判断是否有权限 * @param value 权限枚举数组(方法权限标识) * @param authority 用户权限等级 * @return */ private boolean containsPermissions(AuthorityEnum[] value, Integer authority ) { for (AuthorityEnum auth : value) { / * 普通权限可以查看普通加只读 */ if (authority.equals(AuthorityEnum.NORMAL.getCode())) { if(auth.getCode() == AuthorityEnum.NORMAL.getCode() || auth.getCode() == AuthorityEnum.READ_ONLY.getCode()){ return true; } } / * 只读权限只能只读 */ if (authority.equals(AuthorityEnum.READ_ONLY.getCode()) && auth.getCode() == AuthorityEnum.READ_ONLY.getCode()) { return true; } } return false; } } 

token失效的问题

接着还说到一些用户的一些请求的token失效的问题。现在大部分都是微服务的架构,基本上都会采用redis去做一个缓存,而不是像之前的直接用web的一个session里面设置一个缓存。因为用户普遍存在于不同的微服务的服务里面,进行一个请求转发。所以放redis最好的,而且redis是天生会有一个设置过期的一个作用。

/ * 登陆 * @param loginDto * @return */ public TokenVo login(LoginDto loginDto) { SimUser user = findUserByUsername(loginDto.getPhone()); if (user == null) { Asserts.fail(ResultCode.USER_OR_PASSWORD_ERROR); } / * 检查用户权限 */ if (AuthorityEnum.DISABLED.getCode() == user.getAuthority()) { Asserts.fail(ResultCode.USER_DISABLED); } / * 检查是否不允许登录 */ if (redisUtil.hasKey(RedisCacheKey.USER_LOGIN_LOCK + user.getId())){ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); long expSec = redisUtil.ttl(RedisCacheKey.USER_LOGIN_LOCK + user.getId()); long timestamp = System.currentTimeMillis() + expSec * 1000; String timeStr = sdf.format(new Date(Long.valueOf(timestamp).longValue())); throw new ApiException("连续5次输入密码错误,请于 " + timeStr + " 后再尝试登录"); } / * 优先使用密码登录 * 1.默认密码登录强制修改密码 * 2.验证密码正确性 * */ if (StrUtil.isNotBlank(loginDto.getPwd())) { / * 验证密码格式 */ if (!PwdUtils.validatePwdFormat(loginDto.getPwd())) { Asserts.fail(ResultCode.USER_OR_PASSWORD_ERROR); } / * 验证密码,默认流程 */ if (checkPassword(user, loginDto.getPwd())) { if (ComConstant.DEFAULT_PWD.equals(loginDto.getPwd())) { / * 如果时初始密码并且能登录成功,代表第一次登录,需要强制修改密码,不存入缓存 */ return TokenVo.firstLogin(user); } redisUtil.del(RedisCacheKey.USER_LOGIN_PWD_RETRY + user.getId()); return setUserLoginTokenCache(user, loginDto.isFifteenFreeLogin()); } else { //一日内,用户连续输入5次密码错误,则锁定半小时不允许账号密码登录 Integer num = (Integer) redisUtil.get(RedisCacheKey.USER_LOGIN_PWD_RETRY + user.getId()); num = (null == num) ? 1 : ++num; if (num >= 5){ redisUtil.set(RedisCacheKey.USER_LOGIN_LOCK + user.getId(), user,30*60); } else { redisUtil.set(RedisCacheKey.USER_LOGIN_PWD_RETRY + user.getId(), num, DateUtils.getRemainSecondsOneDay()); } Asserts.fail(ResultCode.USER_OR_PASSWORD_ERROR); } / * 或者使用验证码登录 */ }else if (StrUtil.isNotBlank(loginDto.getCode())) { if (smsService.verifyCaptcha(loginDto.getPhone(), loginDto.getCode())) { / * 如果时初始密码并且能登录成功,代表第一次登录,需要强制修改密码,不存入缓存 */ if (checkPassword(user, ComConstant.DEFAULT_PWD)) { / * 如果时初始密码并且能登录成功,代表第一次登录,需要强制修改密码,不存入缓存 */ return TokenVo.firstLogin(user); } return setUserLoginTokenCache(user, loginDto.isFifteenFreeLogin()); } else { Asserts.fail(ResultCode.CAPTCHA_ERROR); } } Asserts.fail(ResultCode.USER_OR_PASSWORD_ERROR); return null; } 

注册

说起注册,可能首先想到的就是验证码,验证码一般分为手机验证码或者邮箱验证码,手机验证码里可以去调用一些第三方的服务接口。然后邮箱验证码现在目前开源的一些很多第三方包也大部分包含了。所以你也无需太过担心,以下是发送短信的一些逻辑判断。

 public boolean sendSms(SmsPhoneDto smsPhoneDto) { //短信开关 if (!SendSmsConfig.SMS_SWITCH_OPEN.equals(smsConfig.getSmsSwitch())) { return true; } //校验手机号是否合法 if (!Validator.isMobile(smsPhoneDto.getPhone())) { Asserts.fail(ResultCode.PHONE_NUMBER_IS_ILLEGAL); } //使用短信验证码登录须为系统非不可用用户 if (null != smsPhoneDto.getIsLogin() && smsPhoneDto.getIsLogin()) { SimUser user = simUserDao.findUserByPhone(smsPhoneDto.getPhone()); if (user == null) { Asserts.fail(ResultCode.USER_NOT_EXIST); } if (AuthorityEnum.DISABLED.getCode() == user.getAuthority()) { Asserts.fail(ResultCode.USER_DISABLED); } } //60秒内重复发送校验 if (redisUtil.hasKey(RedisCacheKey.USER_CAPTCHA_INTERVAL_PREFIX + smsPhoneDto.getPhone())) { Asserts.fail("已经发送验证码,请勿重复发送!"); } //同用户,每天20次上限校验 Integer count = (Integer) redisUtil.get(RedisCacheKey.USER_VERIFY_CODE_SEND_COUNT_PREFIX + smsPhoneDto.getPhone()); count = (null == count) ? 1 : ++count; if (count > Integer.valueOf(smsConfig.getVerifyCodeSendLimit())){ Asserts.fail(ResultCode.VERIFY_CODE_LIMIT_ERROR); } redisUtil.set(RedisCacheKey.USER_VERIFY_CODE_SEND_COUNT_PREFIX + smsPhoneDto.getPhone(), count, DateUtils.getRemainSecondsOneDay()); int code = RandomUtil.getRandom().nextInt(, ); Boolean sendStatus = smsUtil.sendSmsSingle(smsConfig.getVerifyCodeTemplateId(), new String[]{code + "", smsConfig.getVerifyCodeEffectiveTime()}, smsPhoneDto.getPhone()); if (!sendStatus) { return false; } redisUtil.set(RedisCacheKey.USER_CAPTCHA_INTERVAL_PREFIX + smsPhoneDto.getPhone(), code, cacheConfig.getCaptchaSendInterval() * 60); redisUtil.set(RedisCacheKey.USER_CAPTCHA_PREFIX + smsPhoneDto.getPhone(), String.valueOf(code), cacheConfig.getCaptchaDuration() * 60); return true; }

用户ID

最后也是这个更重要的一个点就是关于用户的一个ID。因为你用户的ID时常可能需要保存到缓存或者到页面上面做一些呈现,你自增的ID肯定是不行的。因为用户可以根据你的ID知道你数据的用户量,或者说推你下一个用户的一个ID。这显然是不安全的,另外的话有一些人使用uuid,我觉得这个ID好是好,就是太长了,目前来说,最常用的可能还是一些雪花算法做的一些ID。有一些也自己去配置的一些规则数字,其实雪花算法ID就有一个好处,就是方便检索,因为本身存储到数据库,它需要进行一个排序数字类的非常适合排序。

	/
	 * 获取单例的Twitter的Snowflake 算法生成器对象<br>
	 * 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
	 *
	 * <p>
	 * snowflake的结构如下(每部分用-分开):<br>
	 *
	 * <pre>
	 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
	 * </pre>
	 * <p>
	 * 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
	 * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)<br>
	 * 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
	 *
	 * <p>
	 * 参考:http://www.cnblogs.com/relucent/p/4955340.html
	 *
	 * @return {@link Snowflake}
	 * @since 5.7.3
	 */
IdUtil.getSnowflake().nextId()

最后

点赞关注评论一键三连,每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/141193.html

(0)
上一篇 2025-05-21 15:33
下一篇 2025-05-21 15:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信