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

登陆注册功能

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

登录

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

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(111111, 1000000);
        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()

最后

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

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《大鱼吃小鱼》是一款简单而有趣的游戏,适合初学者学习Java游戏开发。下面我将手把手教你如何用Java开发这款游戏,并提供相应的源码。 步骤一:创建游戏窗口和游戏主类 首先,我们需要创建游戏的窗口,可以使用Java提供的Swing框架来实现。创建一个GameWindow类,继承JFrame类,并在构造方法中设置窗口的基本属性。 步骤二:添加游戏画布 在GameWindow类中,创建一个GameCanvas类,继承JPanel类,并重写paintComponent()方法,在此方法中实现游戏场景的绘制,包括大鱼、小鱼和其他游戏元素。 步骤三:添加游戏逻辑 在GameWindow类中,添加游戏逻辑的处理方法,包括大鱼的移动、小鱼的生成和碰撞检测等。 步骤四:添加游戏控制 在GameWindow类中,添加游戏控制的方法,包括键盘事件的处理和游戏状态的切换等。 步骤五:运行游戏 在GameWindow类中,添加一个main()方法,创建游戏窗口对象,并启动游戏循环。 以上是《大鱼吃小鱼》游戏的基本开发步骤,下面提供相应的源码供参考: ```java import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class GameWindow extends JFrame implements KeyListener{ private GameCanvas gameCanvas; private boolean isRunning; public GameWindow(){ super("大鱼吃小鱼"); setSize(800, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); addKeyListener(this); gameCanvas = new GameCanvas(); add(gameCanvas, BorderLayout.CENTER); isRunning = true; } public void start(){ while(isRunning){ update(); gameCanvas.repaint(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } } } public void update(){ // 游戏逻辑更新 } public void keyPressed(KeyEvent e){ // 键盘按下事件处理 } public void keyReleased(KeyEvent e){ // 键盘释放事件处理 } public void keyTyped(KeyEvent e){ // 键盘输入事件处理 } public static void main(String[] args){ GameWindow gameWindow = new GameWindow(); gameWindow.setVisible(true); gameWindow.start(); } } class GameCanvas extends JPanel{ protected void paintComponent(Graphics g){ super.paintComponent(g); // 游戏场景绘制 } } ``` 通过以上源码和步骤,我们可以实现《大鱼吃小鱼》游戏的基本开发,通过添加更多的游戏逻辑和个性化设计,可以进一步完善这款游戏。希望对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

查拉图斯特拉talk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值