关注

【Java项目-企悦抽】10-用户与认证模块-实现用户登录02-完成鉴权+验证码、密码登录逻辑

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥 弹简特 个人主页

❄️ 个人专栏直通车:


靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:


在这里插入图片描述



前言

友友们,上一期我们完成了登录模块的基础功能——验证码获取与缓存逻辑开发,本期我们将推进登录核心业务开发,重点实现登录鉴权、验证码登录与密码登录整套逻辑。那么话不多说,咱们直接开始吧!


一、登录鉴权方式

最简单的登录流程:
1、web登录页填写登录信息,前端发送请求到后端
2、后端接收登录信息,并校验,校验成功,返回成功结果

1、Cookie-Session认证方式

在这里插入图片描述

在这里插入图片描述

缺点:
1、仅用于web
2、不能跨域
3、集群环境下不适用

2、Token认证方式

在这里插入图片描述

缺点:用户数量大,对于内存来说,压力巨大,因为Redis是内存中的

3、基于Token的JWT令牌认证

此时登录之后,我不会单独生成 SessionId 或者是 token,而是我单独拎出来一个服务叫做 jwt,唯一和上述的区别就是 对于生成出来的 jwt 令牌,我们是不用存储在后端的,他生成这个 jwt 令牌之后,就直接返回给前端。

而对于前端来说,他是存储这个 jwt 的,此时如果 web 进行后续的操作的时候,会将这个 jwt 带上,然后对于后端来说我们对 jwt 令牌进行校验。

那么此时有人会问:你 jwt 都没有存储在后端,我们怎么去校验呢?
此时这个是对于 jwt 的原理有关的,jwt 是一个加解密的过程,也就是说我们会把我们关心的内容生成一个 jwt 令牌,这个其实就是一个加密的过程,那么这个令牌不被后端存储,而是交由前端存储,这个加解密的过程,不用我们手动编写逻辑完成,而是靠独立拆分出来的 JWT 工具包来实现。

完整的 JWT 由三部分组成:头部、负载、签名。
我们将用户相关信息放入负载进行加密处理,加密完成后整合生成完整的 JWT 令牌字符串,后端直接将该字符串返回给前端;后续前端发起各类业务请求时会携带该令牌字符串,后端接收后对其执行解密校验操作,若解密流程能够正常执行无报错,就代表令牌校验通过。

在这里插入图片描述

好处:不像Cookie-session一样仅仅限于web,因为jwt不是存在Cookie里面的,所以不被web页面限制,又由于他不是存在Cookie里面的,所以他能够接受跨域资源的访问,同时你记住,由于我们jwt也不受分布式的限制,因为他只是涉及加解密的机制。

我们本项目使用的是jwt,那么登录的方式就是
1、web登录页填写登录信息,前端发送请求到后端
2、后端接收登录信息,并校验,校验成功,返回成功结果
3、生成jwt令牌并返回


4、一图解释上述完整的原理

在这里插入图片描述
注意:方式3我们的密钥字符串是写到配置文件中的或者写到工具类中的,一致不会动,不是存到缓存中,而是写在代码里,他不占内存,且只生成一次,后续直接一直使用


JWT相关内容请参考👉详讲JWT


二、封装JWTUtil工具类

那么上述咱们讲了jwt的原理之后,我们就将这个东西的使用封装成一个工具类,然后咱使用的时候就直接使用就行了。

咱这些工具类都是统一的,直接复制粘贴别人写好的就行了,知道自己怎么使用就OK了。

在这里插入图片描述

注意:这里如果看起来很吃力的铁铁们,可以参考上述给的jwt博客,不然后续代码看起来很吃力,此处咱没有过多去解释JWT原理和逻辑,因为我在JWT详解博客中总结过一篇很详细,很通俗易懂的内容,需要的铁铁们可以参考哦。

首先,生成秘钥字符串(随机生成,然后放起来(放到配置文件中),后续一直使用),注意:不是存起来,而是写到我们的配置文件中,如期一来,集群也使用,而且如此一来,我们也不占用内存
原理还是看咱上述给的博客

生成秘钥字符串的代码

/**⽣成密钥字符串*/
@Test
public void genKey(){
 // 创建了⼀个密钥对象,使⽤HS256签名算法。 
 Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
 // 将密钥编码为Base64字符串。
 String secretString = Encoders.BASE64.encode(key.getEncoded());
 System.out.println(secretString);
}

此时你生成完这个字符串之后,记住 存起来,我们使用这个密钥字符串结合api生成一个密钥对象,这个密钥对象用于生成签名,以及这个密钥对象用于解密token,记住老铁,咱之前在博客系统中也有详细的说过,可以参考👉JWT使用案例

代码

/**
 * @ClassName JWTUtil
 * @Description TODO JWT工具类
 * @Author zhongge
 * @Version 1.0
 */
public class JWTUtil {

    /**
     * 日志对象
     */
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);

    /**
     * 密钥:Base64编码的密钥
     */
    private static final String SECRET = "XDdowsTOMYxvxDjV2dAKdjez6UZJzy44IpsEbDSG0Q0=";

    /**
     * 生成安全密钥:将一个Base64编码的密钥解码并创建一个HMAC SHA密钥。
     */
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
            Decoders.BASE64.decode(SECRET));

    /**
     * 过期时间(单位: 毫秒)
     */
    private static final long EXPIRATION = 60*60*1000;

    /**
     * 生成token
     *
     * @param claim  {"id": 12, "name":"lisi"}
     * @return token
     */
    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;
    }

    /**
     * 验证token(解密)
     */
    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 (Exception e){
            // 签名验证失败
            logger.error("解析令牌错误,jwt:{}", jwt, e);
        }
        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;
    }
}

三、登录逻辑

1、流程如下所示:

  1. 用户在登录页选择电话+密码或电话+验证码方式,将登录信息提交至 User Server。
  2. 服务端先做基础校验(登录信息完整性 / 手机号),再从 MySQL 获取用户信息,并校验用户身份(含是否强制身份登录)。
  3. 密码登录时由服务端校验密码是否一致;验证码登录时由 verificationCode Service 向 redis 查询并校验验证码是否准确。
  4. 校验通过后,服务端使用 JWT 生成 Token 并返回给客户端;客户端保存 Token,后续请求携带该令牌,由服务端校验合法性后再执行业务逻辑。

2、流程图如下所示:

在这里插入图片描述

3、时序图

在这里插入图片描述

四、实现控制层密码、验证码登录接口

1、约定前后端交互接口

密码登录
[请求方式] POST
[请求地址] /password/login
[请求参数]

{
 "loginName":"13199999999",
 "password":"123456",
 "mandatoryIdentity":"ADMIN"
}

[响应数据]

{
 "code": 200,
 "data": {
 "token": "eyJhbGciOiJIUzI1NiJ9.eyJpZGVudGl0eSI6Ik5PUk1BTCIsInVzZXJJZCI6MjEsImlhdCI6MTcxNjI2MjI5OCwiZXhwIjoxNzE2MjY0MDk4fQ.QfiZmZcfzd5ls_t8lg7bsTF7kA0daK-psjUt1QRj9d4",
 "identity": "ADMIN"
 },
 "msg": ""
}

二、验证码登录
[请求方式] POST
[请求地址] /message/login
[请求参数]

{
 "loginMobile":"13199999999",
 "verificationCode":"0475",
 "mandatoryIdentity":"ADMIN"
}

[响应数据]

{
 "code": 200,
 "data": {
 "token": "eyJhbGciOiJIUzI1NiJ9.eyJpZGVudGl0eSI6Ik5PUk1BTCIsInVzZXJJZCI6MjEsImlhdCI6MTcxNjI2MjUyMywiZXhwIjoxNzE2MjY0MzIzfQ.XEuwO8AvNcqstbOrkI9kWaMhbN-HN2DfnUYGhJthA3I",
 "identity": "ADMIN"
 },
 "msg": ""
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


2、实体类的封装

在这里插入图片描述
返回实体类
在这里插入图片描述
请求参数实体类
在这里插入图片描述
但是你看,我们请求实体类中有公共的字段,所以此时就可以提取出来
在这里插入图片描述
代码

/**
 * @ClassName UserLoginResult
 * @Description TODO 用户登录结果实体类
 * @Author zhongge
 * @Version 1.0
 */
@Data
public class UserLoginResult {
    /**
     * JWT令牌
     */
    private String token;

    /**
     * 登录人员身份
     */
    private String identity;
}


/**
 * @ClassName UserLoginParam
 * @Description TODO 登录请求实体父类
 * @Author zhongge
 * @Version 1.0
 */
@Data
public class UserLoginParam implements Serializable {
    /**
     * 强制某身份登录,不填写就不限制身份
     *
     * 这个身份从枚举类中来获取
     * @see com.zhongge.lotterysystem.service.enums.UserIdentityEnum #name()
     */
    private String mandatoryIdentity;

}

/**
 * @ClassName UserPasswordLoginParam
 * @Description TODO 手机号密码登录的请求实体类
 * @Author zhongge
 * @Version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class UserPasswordLoginParam extends UserLoginParam{
    /**
     * 手机号或者邮箱
     */
    private String loginName;

    /**
     * 密码
     */
    private String password;
}

/**
 * @ClassName UserMessageLoginParam
 * @Description TODO 手机号验证码登录的请求实体类
 * @Author zhongge
 * @Version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = true)//你没有使用继承的话,那么你使用这个就会报错
public class UserMessageLoginParam extends UserLoginParam{

    /**
     * 手机号或者邮箱
     */
    private String loginMobile;

    /**
     * 验证码
     */
    private String verificationCode;
}

3、开始写控制层

说明序列化和反序列工具类的使用场景在打印日志上
在这里插入图片描述

服务层对外打交道的是DTO
在这里插入图片描述
而你控制层调用服务层 你得使用DTO对象来接收,那么你控制层和前端打交道,你前端要求返回什么对象,你服务层就返回是对象,好比,如图:
在这里插入图片描述
为什么大家这么约定?因为我们项目不是一个人做的,而是一个团队做的,所以为了他们之间的交互,我们得约定一些东西。

注意,咱们的DTO是返回实体类,你要向返回哪一些东西看齐


所以创建DTO实体类
在这里插入图片描述

/**
 * @ClassName UserLoginDTO
 * @Description TODO 服务层返回登录的实体类对象
 * @Author zhongge
 * @Version 1.0
 */
@Data
public class UserLoginDTO {
    /**
     * JWT令牌
     */
    private String token;

    /**
     * 登录人员身份
     */
    private String identity;
}


开始类型转换
在这里插入图片描述
如果数据为空
在这里插入图片描述
同时注意,咱的异常状态码变遇到需求 就边变化


那么类型装换的逻辑如下:
在这里插入图片描述


补充一些少些的东西
1、接收JSON对象使用@RequestBody注解
在这里插入图片描述
2、参数校验
在这里插入图片描述

当然,验证码登录也是同理
在这里插入图片描述
OK,那么接下里还有短信验证的控制层代码,和我们密码的方式很相似,所以我们就直接复制就好了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


五、实现服务层密码、验证码登录逻辑

1、登录实现-密码登录业务逻辑

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接下里我们先看密码登录的逻辑实现

在这里插入图片描述

在这里插入图片描述
查库

在这里插入图片描述
注意:手机号需要加密
在这里插入图片描述
查不到就抛异常
在这里插入图片描述
那么接下里我们就校验一下是否需要强制要求这个某个用户登录,什么意思呢?
在这里插入图片描述
在这里插入图片描述
校验密码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后,如果程序走到此处就代表检验通过了,此时就塞入返回值:JWT令牌(token)
在这里插入图片描述
最后构建返回值直接返回即可
在这里插入图片描述

此处补充一点,我们的UerLoginDTO里面的身份我们使用枚举吧,这个枚举之前定义过的,是可管控的管理员和用户,之所以使用枚举 是因为咱想让这个身份信息在我们的管控范围内
在这里插入图片描述
在这里插入图片描述
这一步,现在看不懂就好好沉淀沉淀,后面回头就会了呢~~

此时,还得再控制层嗲用的那部分有一个类型转换的方法,需要做如下的修改:
在这里插入图片描述
OK,此时,咱就把那两个sql给实现一下就行了
在这里插入图片描述
OK,到此密码登录就完成了

2、登录实现-验证码登录业务逻辑

手机号短信验证码登录,第一步咱先验证一下手机号是否合法
在这里插入图片描述
在这里插入图片描述
开始校验验证码:
首先将处理验证码的工具类给注入进来,到时候用它去查询出当时发送的验证码
在这里插入图片描述
通过手机号从缓存中获取验证码
在这里插入图片描述
校验验证码
在这里插入图片描述
OK,到这里咱们的验证码登录也实现完成了~~


最后,各位铁汁,本期我们已经完成登录模块最核心的功能:登录鉴权、密码登录与验证码登录整套逻辑。下一期会先对两套登录接口进行功能测试,接着编写前端对接代码,最后实现全局登录拦截器,内容全是实操干货。如果本篇内容对你有帮助,不妨点赞收藏,也欢迎在评论区留言交流,你的每一份支持都是我持续创作的最大动力~👋

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_81982617/article/details/162972655

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--