• jwt 实现用户登录完整java


    登录校验逻辑

    用户登录的校验逻辑分为三个主要步骤,分别是校验验证码校验用户状态校验密码,具体逻辑如下

    • 前端发送usernamepasswordcaptchaKeycaptchaCode请求登录。
    • 判断captchaCode是否为空,若为空,则直接响应验证码为空;若不为空进行下一步判断。
    • 根据captchaKey从Redis中查询之前保存的code,若查询出来的code为空,则直接响应验证码已过期;若不为空进行下一步判断。
    • 比较captchaCodecode,若不相同,则直接响应验证码不正确;若相同则进行下一步判断。
    • 根据username查询数据库,若查询结果为空,则直接响应账号不存在;若不为空则进行下一步判断。
    • 查看用户状态,判断是否被禁用,若禁用,则直接响应账号被禁;若未被禁用,则进行下一步判断。
    • 比对password和数据库中查询的密码,若不一致,则直接响应账号或密码错误,若一致则进行入最后一步。
    • 创建JWT,并响应给浏览器。

    请求数据结构

    package com.orchids.springmybatisplus.model.entity;
    
    import io.swagger.v3.oas.annotations.media.Schema;
    import lombok.Data;
    
    /**
     * @Author qwh
     * @Date 2024/6/2 22:31
     */
    @Data
    @Schema(description = "后台管理系统登录信息")
    public class LoginVo {
    
        @Schema(description="用户名")
        private String username;
    
        @Schema(description="密码")
        private String password;
    
        @Schema(description="验证码key")
        private String captchaKey;
    
        @Schema(description="验证码code")
        private String captchaCode;
    }
    
    

    枚举类

    package com.orchids.lovehouse.common.result;
    
    import lombok.Getter;
    
    /**
     * 统一返回结果状态信息类
     */
    @Getter
    public enum ResultCodeEnum {
    
        SUCCESS(200, "成功"),
        FAIL(201, "失败"),
        PARAM_ERROR(202, "参数不正确"),
        SERVICE_ERROR(203, "服务异常"),
        DATA_ERROR(204, "数据异常"),
        ILLEGAL_REQUEST(205, "非法请求"),
        REPEAT_SUBMIT(206, "重复提交"),
        DELETE_ERROR(207, "请先删除子集"),
    
        ADMIN_ACCOUNT_EXIST_ERROR(301, "账号已存在"),
        ADMIN_CAPTCHA_CODE_ERROR(302, "验证码错误"),
        ADMIN_CAPTCHA_CODE_EXPIRED(303, "验证码已过期"),
        ADMIN_CAPTCHA_CODE_NOT_FOUND(304, "未输入验证码"),
        ADMIN_ACCOUNT_NOT_EXIST(330,"用户不存在"),
    
    
        ADMIN_LOGIN_AUTH(305, "未登陆"),
        ADMIN_ACCOUNT_NOT_EXIST_ERROR(306, "账号不存在"),
        ADMIN_ACCOUNT_ERROR(307, "用户名或密码错误"),
        ADMIN_ACCOUNT_DISABLED_ERROR(308, "该用户已被禁用"),
        ADMIN_ACCESS_FORBIDDEN(309, "无访问权限"),
        APP_LOGIN_AUTH(501, "未登陆"),
        APP_LOGIN_PHONE_EMPTY(502, "手机号码为空"),
        APP_LOGIN_CODE_EMPTY(503, "验证码为空"),
        APP_SEND_SMS_TOO_OFTEN(504, "验证法发送过于频繁"),
        APP_LOGIN_CODE_EXPIRED(505, "验证码已过期"),
        APP_LOGIN_CODE_ERROR(506, "验证码错误"),
        APP_ACCOUNT_DISABLED_ERROR(507, "该用户已被禁用"),
    
    
        TOKEN_EXPIRED(601, "token过期"),
        TOKEN_INVALID(602, "token非法");
    
    
        private final Integer code;
    
        private final String message;
    
        ResultCodeEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    }
    
    

    全局异常处理

    package com.orchids.lovehouse.common.exception;
    
    import com.orchids.lovehouse.common.result.ResultCodeEnum;
    import lombok.Data;
    
    /**
     * @Author qwh
     * @Date 2024/6/1 20:18
     */
    @Data
    public class LovehouseException extends RuntimeException {
    
        //异常状态码
        private Integer code;
        /**
         * 通过状态码和错误消息创建异常对象
         * @param message
         * @param code
         */
        public LovehouseException(String message, Integer code) {
            super(message);
            this.code = code;
        }
    
        /**
         * 根据响应结果枚举对象创建异常对象
         * @param resultCodeEnum
         */
        public LovehouseException(ResultCodeEnum resultCodeEnum) {
            super(resultCodeEnum.getMessage());
            this.code = resultCodeEnum.getCode();
        }
    
        @Override
        public String toString() {
            return "LovehouseException{" +
                    "code=" + code +
                    ", message=" + this.getMessage() +
                    '}';
        }
    }
    

    配置所需依赖
    登录接口需要为登录成功的用户创建并返回JWT,本项目使用开源的JWT工具Java-JWT,配置如下,具体内容可参考官方文档

    • 引入Maven依赖
    
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-apiartifactId>
        <version>0.11.2version>
    dependency>
    
    
    <dependency>
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-implartifactId>
        <version>0.11.2version>
        <scope>runtimescope>
    dependency>
      
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-jacksonartifactId>
        <version>0.11.2version>
        <scope>runtimescope>
    dependency>
    
    

    创建JWT和工具类 common.utils.JwtUtil

    package com.orchids.lovehouse.common.utils;
    
    import com.orchids.lovehouse.common.exception.LovehouseException;
    import com.orchids.lovehouse.common.result.ResultCodeEnum;
    import io.jsonwebtoken.*;
    import io.jsonwebtoken.security.Keys;
    import io.jsonwebtoken.security.SignatureException;
    
    import javax.crypto.SecretKey;
    import java.util.Date;
    
    /**
     * @Author qwh
     * @Date 2024/6/2 21:01
     */
    public class JwtUtil {
        private static long tokenExpiration = 60  * 60 * 1000L;
        public static SecretKey secretKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());
        public static String createToken(Long userId,String username){
            String token  = Jwts.builder().
            setSubject("USER_INFO").
            setExpiration(new Date(System.currentTimeMillis()+tokenExpiration)).
            claim("userId",userId).
            claim("username",username).
            signWith(secretKey,SignatureAlgorithm.HS256).
            compact();
            return token;
        }
        public static Claims parsToken(String token){
            if (token==null) {
                throw new LovehouseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
            }
            try {
                JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
                Jws<Claims> claims = jwtParser.parseClaimsJws(token);
                return claims.getBody();
            } catch (ExpiredJwtException e) {
                throw new LovehouseException(ResultCodeEnum.TOKEN_EXPIRED);
            } catch (JwtException e){
                throw new LovehouseException(ResultCodeEnum.TOKEN_INVALID);
            }
        }
    
        public static void main(String[] args) {
            System.out.println(createToken(2l,"user"));
        }
    }
    
    

    controller逻辑

    package com.orchids.lovehouse.web.admin.controller.login;
    
    
    import com.orchids.lovehouse.common.login.LoginUserHolder;
    import com.orchids.lovehouse.common.result.Result;
    import com.orchids.lovehouse.common.utils.JwtUtil;
    import com.orchids.lovehouse.web.admin.service.LoginService;
    import com.orchids.lovehouse.web.admin.vo.login.CaptchaVo;
    import com.orchids.lovehouse.web.admin.vo.login.LoginVo;
    import com.orchids.lovehouse.web.admin.vo.system.user.SystemUserInfoVo;
    import com.orchids.lovehouse.web.admin.vo.system.user.SystemUserItemVo;
    import io.jsonwebtoken.Claims;
    import io.swagger.v3.oas.annotations.Operation;
    import io.swagger.v3.oas.annotations.tags.Tag;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @Tag(name = "后台管理系统登录管理")
    @RestController
    @RequestMapping("/admin")
    public class LoginController {
        @Autowired
        private LoginService loginService;
    
        @Operation(summary = "获取图形验证码")
        @GetMapping("login/captcha")
        public Result<CaptchaVo> getCaptcha() {
            CaptchaVo captcha = loginService.getCaptcha();
            return Result.ok(captcha);
        }
    
        @Operation(summary = "登录")
        @PostMapping("login")
        public Result<String> login(@RequestBody LoginVo loginVo) {
            String token =  loginService.login(loginVo);
            return Result.ok(token);
        }
    
        @Operation(summary = "获取登陆用户个人信息")
        @GetMapping("info")
        public Result<SystemUserInfoVo> info () {
            SystemUserInfoVo systemUserInfo  = loginService.getLoginUserInfo(LoginUserHolder.getLoginUser().getUserId());
            return Result.ok(systemUserInfo);
        }
    }
    

    service逻辑

    package com.orchids.lovehouse.web.admin.service;
    
    import com.orchids.lovehouse.web.admin.vo.login.CaptchaVo;
    import com.orchids.lovehouse.web.admin.vo.login.LoginVo;
    import com.orchids.lovehouse.web.admin.vo.system.user.SystemUserInfoVo;
    
    public interface LoginService {
    
        CaptchaVo getCaptcha();
    
        String login(LoginVo loginVo);
    
        SystemUserInfoVo getLoginUserInfo(Long userId);
    }
    
    

    sreviceImpl

    package com.orchids.lovehouse.web.admin.service.impl;
    
    import com.orchids.lovehouse.common.constant.RedisConstant;
    import com.orchids.lovehouse.common.exception.GlobalExceptionHandler;
    import com.orchids.lovehouse.common.exception.LovehouseException;
    import com.orchids.lovehouse.common.result.ResultCodeEnum;
    import com.orchids.lovehouse.common.utils.JwtUtil;
    import com.orchids.lovehouse.model.entity.SystemUser;
    import com.orchids.lovehouse.model.enums.BaseStatus;
    import com.orchids.lovehouse.web.admin.mapper.SystemUserMapper;
    import com.orchids.lovehouse.web.admin.service.LoginService;
    import com.orchids.lovehouse.web.admin.vo.login.CaptchaVo;
    import com.orchids.lovehouse.web.admin.vo.login.LoginVo;
    import com.orchids.lovehouse.web.admin.vo.system.user.SystemUserInfoVo;
    import com.orchids.lovehouse.web.admin.vo.system.user.SystemUserItemVo;
    import com.wf.captcha.SpecCaptcha;
    import com.wf.captcha.base.Captcha;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.util.DigestUtils;
    import org.springframework.util.StringUtils;
    
    import java.util.UUID;
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class LoginServiceImpl implements LoginService {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Autowired
        private SystemUserMapper systemUserMapper;
    
        @Override
        public CaptchaVo getCaptcha() {
            SpecCaptcha  specCaptcha = new SpecCaptcha(100, 40, 5);
            specCaptcha.setCharType(Captcha.TYPE_DEFAULT);
    
            String code = specCaptcha.text().toLowerCase();
            String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID();
    
            String img = specCaptcha.toBase64();
            stringRedisTemplate.opsForValue().set(key,code,60, TimeUnit.SECONDS);
            return new CaptchaVo(img,key);
        }
    
        @Override
        public String login(LoginVo loginVo) {
            //判断是否输入验证码
            if (!StringUtils.hasText(loginVo.getCaptchaCode())) {
                throw new LovehouseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
            }
            //校验验证码
            String code = stringRedisTemplate.opsForValue().get(loginVo.getCaptchaKey());
            if (code == null){
                throw new LovehouseException(ResultCodeEnum.APP_LOGIN_CODE_EXPIRED);
            }
            if (!code.equals(loginVo.getCaptchaCode())){
                throw new LovehouseException(ResultCodeEnum.APP_LOGIN_CODE_ERROR);
            }
            //校验用户是否存在
            SystemUser systemUser = systemUserMapper.selectOneByUsername(loginVo.getUsername());
            if (systemUser == null) {
                throw new LovehouseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST);
            }
            if (systemUser.getStatus() == BaseStatus.DISABLE) {
                throw new LovehouseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
            }
            // 鏍¢獙鐢ㄦ埛瀵嗙爜
            if (!systemUser.getPassword().equals(DigestUtils.md5DigestAsHex(loginVo.getPassword().getBytes()))) {
                throw new LovehouseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);
            }
            // 鍒涘缓骞惰繑鍥瀟oken
            return JwtUtil.createToken(systemUser.getId(),systemUser.getUsername());
        }
    
        @Override
        public SystemUserInfoVo getLoginUserInfo(Long userId) {
            SystemUser systemUser = systemUserMapper.selectById(userId);
            SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
            systemUserInfoVo.setName(systemUser.getName());
            systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());
            return systemUserInfoVo;
        }
    
    
    }
    
    

    编写mapper逻辑

    SystemUser selectOneByUsername(String username);
    

    mapper.xml

    写入对应的sql到xml文件
    

    编写HandlerInterceptor
    保护所有受保护的接口增加jwt合法性逻辑 custom.interceptor.AuthenticationInterceptor

    package com.orchids.lovehouse.web.admin.custom.interceptor;
    
    import com.orchids.lovehouse.common.login.LoginUser;
    import com.orchids.lovehouse.common.login.LoginUserHolder;
    import io.jsonwebtoken.Claims;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import com.orchids.lovehouse.common.utils.JwtUtil;
    
    /**
     * @Author qwh
     * @Date 2024/6/2 21:55
     */
    @Component
    public class AuthenticationInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = request.getHeader("access-token");
    
            Claims claims = JwtUtil.parsToken(token);
            Long userId = claims.get("userId", Long.class);
            String username = claims.get("username", String.class);
            LoginUserHolder.setLoginUser(new LoginUser(userId,username));
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            LoginUserHolder.clear();
        }
    }
    
    

    我们约定,前端登录后,后续请求都将JWT,放置于HTTP请求的Header中,其Header的key为access-token
    注册HanderInterceptor config.WebMvcConfiguration

    package com.orchids.lovehouse.web.admin.custom.config;
    
    import com.orchids.lovehouse.web.admin.custom.converter.StringToBaseEnumConverterFactory;
    import com.orchids.lovehouse.web.admin.custom.converter.StringToItemTypeConverter;
    import com.orchids.lovehouse.web.admin.custom.interceptor.AuthenticationInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebMvcConfiguration implements WebMvcConfigurer {
        
        @Autowired
        private AuthenticationInterceptor authenticationInterceptor;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(this.authenticationInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin/login/**");
        }
    }
    

    获取登录个人信息
    查看请求和响应的数据结构

    • 响应的数据结构
    @Schema(description = "员工基本信息")
    @Data
    public class SystemUserInfoVo {
    
        @Schema(description = "用户姓名")
        private String name;
    
        @Schema(description = "用户头像")
        private String avatarUrl;
    }
    

    common.login.LoginUserHolder

    package com.orchids.lovehouse.common.login;
    
    /**
     * @Author qwh
     * @Date 2024/6/2 22:15
     */
    public class LoginUserHolder {
        public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
    
        public static void setLoginUser(LoginUser loginUser) {
            threadLocal.set(loginUser);
        }
    
        public static LoginUser getLoginUser() {
            return threadLocal.get();
        }
    
        public static void clear() {
            threadLocal.remove();
        }
    }
    
    

    common.login.LoginUser

    package com.orchids.lovehouse.common.login;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    
    /**
     * @Author qwh
     * @Date 2024/6/2 22:16
     */
    @Data
    @AllArgsConstructor
    public class LoginUser {
    
        private Long userId;
        private String username;
    }
    
    
  • 相关阅读:
    vue3 学习笔记09 -- 自适应实现(postcss-pxtorem)
    斐波那契数列的递推与递归求法
    【Java】泛型 之 泛型和反射
    java计算机毕业设计台球收费管理系统设计与实现MyBatis+系统+LW文档+源码+调试部署
    四大类好用的科研工具分享
    联想电脑使用“联想电脑管家”之后电脑频繁蓝屏
    前端学习笔记
    (粗糙的笔记)动态规划
    【Linux-达梦】A1.数据库部署
    邦芒支招:求职自荐的五条技巧
  • 原文地址:https://blog.csdn.net/qq_62383709/article/details/139399267