【框架学习(6.3)-- SpringBoot登录验证之学习JWT】

JWT

全称:JSON WEB TOKEN

JWT特点

1、JWT 组成

JWT 由三部分组成,并且各自都是 JSON 格式。

JWT 的样式举例:

xxx.yyy.zzz
如:abcdefg . hijklmn . opqrstuvwxyz

中间以点号分隔开来,三部分任一部分都是一个JSON对象编码得到的。

(1)header(头部)

举例中的xxx

header 中,制定了两个部分,其中alg指定了签名算法(比如HS256),typ指定了令牌类型(一般为JWT)。

举例:
原内容为:

{
  "alg": "HS256",
  "typ": "JWT"
}

Base64 对此部分编码得到xxx,成为第二部分内容。

(2)payload(有效载荷)

举例中的yyy

payload 中,包含了希望传递的有关用户的一些数据和其它的。例如用户名、权限等级等。

举例:

{
  "id": "1234567890",
  "name": "John Doe",
  "something": "isToken"
}

Base64 对此部分编码得到yyy,成为第二部分内容。

注:
因为这部分仅仅是由Base64编码得出,可以轻易地被客户端解码出内容,所以不能存放密码等隐私数据。

至于用户id、用户名等被获取也无关紧要。

(3)verify signature(验证签名)

举例中的zzz

也叫signature(签名),是JWT 真正实现验证机制的部分。

第三部分的zzz,是根据:

xxxyyymyownkey(秘钥)

使用这三者,通过header中指定的签名算法进行加密,再将最终得到的zzz,作为第三部分。

其中,秘钥 是由我们写的程序内部自己定义的,绝对不能泄露。

怎么使用秘钥验证?

Token验证的过程

1、浏览器请求登录。

2、服务器接收请求,判断是否携带有token

  • token,再重复一次生成signature的过程。

    也就是根据 headerpayload加上秘钥通过 header中指定的签名算法生成signature

    然后比较当前计算出的signature和浏览器提供的signature是不是一致的,就知道这个token是不是由自己颁发的。

因为秘钥只有服务器自己知道,如果是伪造的、胡拼乱凑的token,基本上无法命中。

当然,不排除弱密码被暴力破解的可能。

  • token,生成一个token

3、返回结果。

JWT 并没那么安全

实际上,JWT 只是 Token 的一种格式,并且提供了一种生成和校验数据的办法。

Token中携带的数据,一个是不能是隐私数据,再一个就是私钥虽然是由我们自己提供定义,但现在仍存在很多供给破解的手段。

比如私钥比较弱、特殊header组成、修改playload中的固定字段值等等。

JWT 的本质应该是校验payload数据的正确性与真实性,只是我们认为可以通过这种方式来验证登录、做权限管理等功能。

并不能因为使用了加密而掉以轻心,根据系统安全性需求应该考虑到多种方案,例如使用多种秘钥,建立秘钥文件等。

JWT 网址

JWT 官网:

https://jwt.io/

JWT类官方github地址:

https://github.com/auth0/java-jwt

README.md中有介绍:
在这里插入图片描述
并且提供了 JWT 的Java文档地址:

https://javadoc.io/doc/com.auth0/java-jwt/latest/index.html
在这里插入图片描述

使用SpringBoot创建Token

先引入JWT依赖到SpringBoot 项目中:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.2.1</version>
</dependency>

SpringBoot使用的配置文件:

# Spirng
server:
  port: 7788

# 配置MySQL数据源
spring:
  datasource:
    url: jdbc:mysql://192.168.253.10:3306/cat-im?useUnicode=true&characterEncoding=utf8&autoReconnect=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

# mybatis-plus
mybatis-plus:
  mapperPackage: com.cat.auth.mapper # Mapper 所在包路径
  mapperLocations: classpath*:mapper/*Mapper.xml # XML 文件位置
  typeAliasesPackage: com.cat.auth.entity # 实体类路径,多个包之间用逗号或者分号分隔
  configuration:
    mapUnderscoreToCamelCase: true # 驼峰处理
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

使用JWT 进行登录验证

先上项目结构:
在这里插入图片描述

JWTUtils

定义一个JWT工具,将私钥藏在内部,仅对外提供创建解码 Token 的方法即可。

/**JWT工具类**/
public class JWTUtils {


    /**
     * 秘钥作为私有的静态变量,只能暴露给工具类内部的方法用。
     **/
    private static String SECRET = "token@#¥%……¥@cat!!!";

    /**
     * 创建Token
     */
    public static String getToken(Map<String, String> map) {
        //新建一个 JWT 生成器
        JWTCreator.Builder builder = JWT.create();
        // 添加 payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        //默认7天过期
        Calendar outDate = Calendar.getInstance();
        outDate.add(Calendar.DATE, 7);// 当前时间加7天,作为过期时间点
        builder.withExpiresAt(outDate.getTime()); //设置过期时间
        //只需要告知生成器应该使用什么算法、私钥是多少,header中的 typ会自动添加设置为JWT,
        String token = builder.sign(Algorithm.HMAC256(SECRET));//签名
        return token;
    }

    /**
     * 验证token
     */
    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
    }
}

因为是JSON 对象,所以添加的时候就跟map一样,使用的是key-value 的形式。

JWT 的Builder 中:

  • withClaim(k,v)是添加一个名称为k,值为v的数据到payload中。

    v可以是以下几种数据类型:

    • Boolean
    • Integer
    • Long
    • String
    • Double
    • Instant
    • List <?>
    • Map <String,?>
  • 如果要添加数组,有单独的方法:
    withArrayClaim
    添加一个名称为k,值则是支持存入 Integer、Long、String三种数组。

  • JWT 的token里定义了几种固定的属性名,并且提供了相应的方法设置:

    • kid (秘钥的id。因为Java程序中只使用一个秘钥加密可能不太安全,可以制造一个秘钥文件,根据秘钥的id用来标识应该使用哪一个秘钥进行生成令牌和验证令牌的操作。)

    添加方法:withKeyId(String kid)

    • issur(令牌的颁发者是谁?)
      ……
      有时间再写……
JWTInterceptor:

添加一个JWTToken拦截器,定义拦截规则。
目的就是让请求在到达控制层(Controller)之前,没有携带正确 Token 的请求都回绝掉,返回false及拒绝原因等一些其它信息给前端。

前端收到拒绝的信息后,再根据拒绝原因可以跳转到对应的登录页面。
重定向等操作交由前端。
(我没有写页面……)

/**
 * 自定义拦截器
 **/
@Slf4j
public class JWTInterceptor implements HandlerInterceptor {


    /**
     * 在业务处理器(Controller层)处理请求之前被调用
     **/
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        //获取请求头中的令牌
        String token = request.getHeader("token");
        log.info("当前token为:{}", token);
        Map<String, Object> map = new HashMap<>();
        try {
            if (token != null) {
                JWTUtils.verify(token);
                return true;
            }else {
                map.put("msg", "未携带token");
            }
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "签名不一致");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "令牌过期");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "算法不匹配");
        } catch (InvalidClaimException e) {
            e.printStackTrace();
            map.put("msg", "失效的payload");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效");
        }

        map.put("state", false);

        //响应到前台: 将map转为json
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

InterceptorConfig

制定好拦截器与令牌的生成与验证工具,还需要将拦截器添加到web中去,才能工作。

/**
 * 自定义Web配置
 * 可添加自定义拦截器、路由等……
 **/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器
     **/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor()) // 添加自定义的JWT拦截器
                .addPathPatterns("/user/needJWTToken") //规定拦截器对指定的路径过滤
                .excludePathPatterns("/user/login") // 设置不需要拦截的过滤规则
        ;
    }
}
UserController

UserController提供两个路径接口:

  • jwtLogin 提供登录验证、颁发令牌。
  • needJWTToken 则是一个需要正确令牌才能访问的路径接口。
@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/user/jwtLogin", method = RequestMethod.POST,consumes = "application/json")
    public Map<String, Object> jwtLogin(@RequestBody User user) {
        log.info("user_id:{}", user.getName());
        log.info("password: {}", user.getPassword());
        Map<String, Object> map = new HashMap<>();
        User userDB = userService.login(user);
        if (userDB != null) {
            Map<String, String> payload = new HashMap<>();
            payload.put("user_id", userDB.getId().toString());
            payload.put("user_name", userDB.getName());
            String token = JWTUtils.getToken(payload);
            map.put("state", true);
            map.put("msg", "登录成功");
            map.put("token", token);
            return map;
        } else {
            map.put("state", false);
            map.put("msg", "登录失败,用户名或密码错误!");
            map.put("token", "");
        }
        return map;
    }


    @RequestMapping(value = "/user/needJWTToken", method = RequestMethod.POST)
    public Map<String, Object> needJWTToken(HttpServletRequest request) {
        String token = request.getHeader("token");
        DecodedJWT verify = JWTUtils.verify(token);
        String id = verify.getClaim("id").asString();
        String name = verify.getClaim("name").asString();
        log.info("user_id:{}", id);
        log.info("user_name: {}", name);

        //TODO 业务逻辑
        Map<String, Object> map = new HashMap<>();
        map.put("state", true);
        map.put("msg", "请求成功");
        return map;
    }


}
UserMapper

UserMapper 继承了BaseMapper,实现基础的crud操作。
为Service层实现具体的业务逻辑提供数据库操作。

@Mapper
public interface UserMapper extends BaseMapper<User> {

    /**查询User**/
    User selectUser(User user) ;

}
    <select id="selectUser" parameterType="User" resultType="User">
        select id,name,password from user where name =#{name} and password=#{password}
    </select>
UserService

在 Service层面,接口中依旧需要自定义方法,由实现类去实现。

因为方法中需要加入自己需要的判断或者业务层面的一些东西。

public interface UserService extends IService<User> {


    User login(User user);

}
UserServiceImpl

service实现类中:
extends ServiceImpl<UserMapper, User> 能将UserMapper注入到内部使用,然后我们在方法里进行判断。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user) {
        return baseMapper.selectUser(user);
    }

}
启动类
@SpringBootApplication
@ComponentScan(basePackages = {"com.cat.auth.*"})
@MapperScan("com.cat.auth.mapper")
public class AuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}
JWT Token 验证效果

数据库中新建用户:

用户名:admin
密码:admin123

(1)登录时密码错误,不颁发令牌

在这里插入图片描述

(2)用户名密码正确,颁发令牌

在这里插入图片描述

(3)访问需要权限的页面,没有携带Token

告知前端请求失败,并告诉失败原因。
在这里插入图片描述
(实际上,前端也需要定义一个拦截器,对于接收response,一旦收到约定好的错误状态及错误原因,知道是没有携带 Token就跳转到登录页面去。)

(4)访问需要权限的页面,携带错误的令牌

在这里插入图片描述

(5)访问需要权限的页面,携带正确的令牌

在这里插入图片描述

学习

学习自B站 尚硅谷、编程不良人的视频。
可以参考 jwt-java相关文档,下载下来用浏览器打开。
英文不好直接一键翻译就好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果您下载了本程序,但是该程序存在问题无法运行,那么您可以选择退款或者寻求我们的帮助(如果找我们帮助的话,是需要追加额外费用的)。另外,您不会使用资源的话(这种情况不支持退款),也可以找我们帮助(需要追加额外费用) 微信小程序是腾讯公司基于微信平台推出的一种轻量级应用形态,它无需用户下载安装即可在微信内直接使用。自2017年正式上线以来,小程序凭借其便捷性、易获取性和出色的用户体验迅速获得市场认可,并成为连接线上线下服务的重要桥梁。 小程序的核心特点包括: 零安装:用户只需通过微信扫一扫或搜索功能,即可打开和使用小程序,大大降低了用户的使用门槛和手机存储空间压力。 速度快:加载速度相较于传统的HTML5网页更快,依托于微信强大的基础设施,能够实现近乎原生应用的流畅体验。 跨平台兼容:开发者一次开发,即可在多种终端设备上运行,免除了复杂的适配工作,大大提高了开发效率。 社交属性强:小程序可以无缝嵌入微信生态,支持分享至聊天窗口、朋友圈等社交场景,有利于用户间的传播和裂变增长。 丰富接口能力:提供丰富的API接口,可调用微信支付、位置服务、用户身份识别等多种功能,方便企业进行商业服务的集成与拓展。 目前,微信小程序已经覆盖了电商购物、生活服务、娱乐休闲、教育学习、工具助手等多个领域,为数以亿计的用户提供便捷的服务入口,也为众多商家和开发者提供了新的商业模式和创业机会。随着技术的不断升级和完善,小程序已成为现代移动互联网生态中不可或缺的一部分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

上岸撒尿的鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值