java抽奖系统登录下(四)

发布于:2024-12-18 ⋅ 阅读:(25) ⋅ 点赞:(0)

6.4 关于登录

最简单的登录:

        1、web登录页填写登录信息,前端发送登录信息到后端;

        2、后端接受登录信息,并校验。校验成功,返回成功结果。

        这种登录会出现一个问题,用户1成功登录之后,获取到后台管理页,将这个页面的url分享给用户2,用户2没有进行登录就会直接进入到后台管理页,容易造成信息泄露;

        解决方法:用户在进入后台管理页之前要进行校验;

几种常见的登录验证的方式:

方式1、cookie-session

1、用户带登录的时候,就会把用户信息发送到后端,后端根据用户信息创建一个sessionid,将用户登录的相关信息放入到session中,后续通过sessionid来进行查找校验,并将sessionid传送到前端;

2、前端接收到sieeionid之后将其存入到cookie中,后续在进行登陆之后会携带cookie,后端会根据前端发送过来的cookie解析到其中的sieeionid,通过sessionid来查找是否用这个id所对应的用户消息。如果存在用户信息,则表示之前这个用户登录过,就会放心该用户跳转到其他界面;如果该id没有查找到相关的用户信息,则表示该用户之前没有进行登录过,不会放行;

缺点:1、cookie只在web网页中生成,存在环境限制;

        2、这种方式不能跨域;

        3、cookie只能在本机保存,不能在集群环境中使用;

方式2: token认证

        上图的token是存储在redis,由于redis是可以部署在集群环境中,所以解决了cookie-session的一下缺陷;

        缺点:用户数量大会导致频繁的访问redis校验token,对于内存来说有很大的压力。

方式三:基于token的jwt令牌认证

 

        1、前端的登录信息发送到后端之后,后端基于jwt服务产生一个string类型的字符串,成为jwt令牌,这个令牌是不需要存储在后端的,而是直接返回到前端;

        2、前端使用本地的存储方式,将jwt存储起来,后续在进行操作的话会带着jwt令牌到后端,后端会对jwt令牌进行校验,如果能够正确使用jwt解密,说明校验通过了;

        jwt:实际上是一个加解密的过程,这个过程是依靠jwt独立的工具包来完成和实现的。

        jwt:结构是由负载,签名和头部组成的,由这三部分组成一个串;

6.5 jwt令牌工具类的实现

        该工具类主要完成jwt的加密和解密操作:

 首先引入相关的依赖:

新建jwtutil类:

public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
    /**
     * 密钥:Base64编码的密钥
     */
    private static final String SECRET = "weS2l8Tp9wDFov9ic72l/9VRT3j9aYfhEfi8qwGMDgU=";
    /**
     * 生成安全密钥:将一个Base64编码的密钥解码并创建一个HMAC SHA密钥。
     */
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
            Decoders.BASE64.decode(SECRET));
    /**
     * 过期时间(单位: 毫秒)
     */
    private static final long EXPIRATION = 60*60*1000*24*30;//一个月

    /**
     * 生成密钥
     *
     * @param claim  {"id": 12, "name":"张山"}
     * @return
     */
    public static String genJwt(Map<String, Object> claim){
        //签名算法
        String jwt = Jwts.builder()
                .setClaims(claim)             // 自定义内容(载荷)
                .setIssuedAt(new Date())      // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 设置过期时间
                .signWith(SECRET_KEY)         // 签名算法,和加解密相关
                .compact();
        return jwt;
    }

    /**
     * 验证密钥
     */
//    Claims是jwt里面定义的对象
    public static Claims parseJWT(String jwt){
        if (!StringUtils.hasLength(jwt)){
            return null;
        }
        // 创建解析器, 设置签名密钥
        JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder().setSigningKey(SECRET_KEY);
        Claims claims = null;
        try {
            //解析token
            claims = jwtParserBuilder.build().parseClaimsJws(jwt).getBody();
        }catch (ExpiredJwtException e){
            // 签名验证失败
//            logger.error("解析令牌错误,jwt:{}", jwt, e);
            claims = e.getClaims();
        }
        return claims;

    }

    /**
     * 从token中获取用户ID
     */
    public static Integer getUserIdFromToken(String jwtToken) {
        Claims claims = JWTUtil.parseJWT(jwtToken);
        if (claims != null) {
            Map<String, Object> userInfo = new HashMap<>(claims);
            return (Integer) userInfo.get("userId");
        }
        return null;
    }
}

controller层登录接口设计:

    @RequestMapping("/password/login")
    public CommonResult<UserLoginResult> userPasswordLogin(
            @Validated @RequestBody UserPasswordLoginParam param) {
        logger.info("userPasswordLogin UserPasswordLoginParam:{}",
                JacksonUtil.writeValueAsString(param));
        UserLoginDTO userLoginDTO = userService.login(param);
        return CommonResult.success(convertToUserLoginResult(userLoginDTO));
    }

 service层登录接口实现:

@Override
    public UserLoginDTO login(UserLoginParam param) {
        UserLoginDTO userLoginDTO;
        // 类型检查与类型转换,java 14及以上版本
        if (param instanceof UserPasswordLoginParam loginParam) {
            // 密码登录流程
            userLoginDTO = loginByUserPassword(loginParam);
        } else if (param instanceof ShortMessageLoginParam loginParam) {
            // 短信验证码登录流程
            userLoginDTO = loginByShortMessage(loginParam);
        } else {
            throw new ServiceException(ServiceErrorCodeConstants.LOGIN_INFO_NOT_EXIST);
        }

        return userLoginDTO;
    }

    private UserLoginDTO loginByShortMessage(ShortMessageLoginParam loginParam) {
        if (!RegexUtil.checkMobile(loginParam.getLoginMobile())) {
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        // 获取用户数据
        UserDO userDO = userMapper.selectByPhoneNumber(
                new Encrypt(loginParam.getLoginMobile()));
        if (userDO == null) {
            throw new ServiceException(ServiceErrorCodeConstants.USER_INFO_IS_EMPTY);
        } else if (StringUtils.hasText(loginParam.getMandatoryIdentity())
                && !loginParam.getMandatoryIdentity().equalsIgnoreCase(userDO.getIdentity())) {
            throw new ServiceException(ServiceErrorCodeConstants.IDENTITY_ERROR);
        }
        // 校验验证码
        String code = verificationCodeService.getVerificationCode(loginParam.getLoginMobile());
        if (!loginParam.getVerificationCode().equals(code)) {
            throw new ServiceException(ServiceErrorCodeConstants.VERIFICATION_CODE_ERROR);
        }
        // 塞入返回值(JWT)
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", userDO.getId());
        claim.put("identity", userDO.getIdentity());
        String token = JWTUtil.genJwt(claim);
        UserLoginDTO userLoginDTO = new UserLoginDTO();
        userLoginDTO.setToken(token);
        userLoginDTO.setIdentity(UserIdentityEnum.forName(userDO.getIdentity()));
        return userLoginDTO;
    }

    private UserLoginDTO loginByUserPassword(UserPasswordLoginParam loginParam) {
        UserDO userDO = null;
        // 判断手机登录还是邮箱登录
        if (RegexUtil.checkMail(loginParam.getLoginName())) {
            // 邮箱登录
            // 根据邮箱查询用户表
            userDO = userMapper.selectByMail(loginParam.getLoginName());
        } else if (RegexUtil.checkMobile(loginParam.getLoginName())) {
            // 手机号登录
            // 根据手机号查询用户表
            userDO = userMapper.selectByPhoneNumber(new Encrypt(loginParam.getLoginName()));
        } else {
            throw new ServiceException(ServiceErrorCodeConstants.LOGIN_NOT_EXIST);
        }

        // 校验登录信息
        if (null == userDO) {
            throw new ServiceException(ServiceErrorCodeConstants.USER_INFO_IS_EMPTY);
        } else if (StringUtils.hasText(loginParam.getMandatoryIdentity())
                && !loginParam.getMandatoryIdentity().equalsIgnoreCase(userDO.getIdentity())) {
            // 强制身份登录,身份校验不通过
            throw new ServiceException(ServiceErrorCodeConstants.IDENTITY_ERROR);
        } else if (!DigestUtil.sha256Hex(loginParam.getPassword()).equals(userDO.getPassword())) {
            // 校验密码不同
            throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_ERROR);
        }

        // 塞入返回值(JWT)
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", userDO.getId());
        claim.put("identity", userDO.getIdentity());
        String token = JWTUtil.genJwt(claim);
        UserLoginDTO userLoginDTO = new UserLoginDTO();
        userLoginDTO.setToken(token);
        userLoginDTO.setIdentity(UserIdentityEnum.forName(userDO.getIdentity()));
        return userLoginDTO;
    }

使用postman对登录进行测试:

账号密码:

验证码登录:

 开启redis缓存验证码:service redis-server start

发送验证码:

验证码登录:

6.6 前端登录完善

进行前端测试:

密码登录:

短信验证码登录:

登录成功之后进入新的界面:

在登录页面点击注册进入注册见面,注册成功之后返回登录界面:

6.7 登录拦截器

        在设置好jwt令牌登陆之后,用户进行登录操作,会得到后端传过来的jwt令牌,其次用户会拿着这个令牌去访问其他界面的时候,后端会对这个jwt令牌进行校验,这里采用的是拦截器,当然不是所有的请求都需要校验,会进行相关配置;

登录拦截器:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    /**登录拦截器
     * 预处理,业务请求之前调用
     * @param request
     * @param response
     * @param handler
     * @return
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取请求头,jwt是在request的请求头中的
        String token = request.getHeader("user_token");
        log.info("获取token:{}", token);
        log.info("获取路径:{}", request.getRequestURI());
        // 令牌解析
        Claims claims = JWTUtil.parseJWT(token);
        if (claims == null) {
            log.error("解析JWT令牌失败!");
            response.setStatus(401);
            return false;
        }
        log.info("解析JWT令牌成功!放行");
        return true;
    }
}

配置登录拦截资源:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    //配置项
    @Autowired
    private LoginInterceptor loginInterceptor;
    private final List<String> excludes = Arrays.asList(
            "/**/*.html",
            "/css/**",
            "/js/**",
            "/pic/**",
            "/*.jpg",
            "/*.png",
            "/favicon.ico",
            "/**/login",
            "/register",
            "/verification-code/send"
    );
    @Override
    //添加自定义的登录拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludes);
    }
}

ps:关于登录的内容就到这里了,谢谢观看!!!


网站公告

今日签到

点亮在社区的每一天
去签到