利用Springboot实现Jwt认证的示例代码

JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证

关于Shiro整合JWT,可以看这里:Springboot实现Shiro+JWT认证

概述

由于概念性内容网上多的是,所以就不详细介绍了

具体可以看这里:阮一峰大佬的博客

我总结几个重点:

JWT,全称Json Web Token,是一种令牌认证的方式

长相:

  • 头部:放有签名算法和令牌类型(这个就是JWT)
  • 载荷:你在令牌上附带的信息:比如用户的id,用户的电话号码,这样以后验证了令牌之后就可以直接从这里获取信息而不用再查数据库了
  • 签名:用来加令牌的

安全性:由于载荷里的内容都是用BASE64处理的,所以是没有保密性的(因为BASE64是对称的),但是由于签名认证的原因,其他人很难伪造数据。不过这也意味着,你不能把敏感信息比如密码放入载荷中,毕竟这种可以被别人直接看到的,但是像用户id这种就无所谓了

工作流程

登录阶段

用户首次登录,通过账号密码比对,判定是否登录成功,如果登录成功的话,就生成一个jwt字符串,然后放入一些附带信息,返回给客户端。

这个jwt字符串里包含了有用户的相关信息,比如这个用户是谁,他的id是多少,这个令牌的有效时间是多久等等。下次用户登录的时候,必须把这个令牌也一起带上。

认证阶段

这里需要和前端统一约定好,在发起请求的时候,会把上次的token放在请求头里的某个位置一起发送过来,后端接受到请求之后,会解析jwt,验证jwt是否合法,有没有被伪造,是否过期,到这里,验证过程就完成了。

不过服务器同样可以从验证后的jwt里获取用户的相关信息,从而减少对数据库的查询。

比如我们有这样一个业务:“通过用户电话号码查询用户余额”

如果我们在jwt的载荷里事先就放有电话号码这个属性,那么我们就可以避免先去数据库根据用户id查询用户电话号码,而直接拿到电话号码,然后执行接下里的业务逻辑。

关于有效期

由于jwt是直接给用户的,只要能验证成功的jwt都可以被视作登录成功,所以,如果不给jwt设置一个过期时间的话,用户只要存着这个jwt,就相当于永远登录了,而这是不安全的,因为如果这个令牌泄露了,那么服务器是没有任何办法阻止该令牌的持有者访问的(因为拿到这个令牌就等于随便冒充你身份访问了),所以往往jwt都会有一个有效期,通常存在于载荷部分,下面是一段生成jwt的java代码:

 return JWT.create().withAudience(userId)
  .withIssuedAt(new Date()) <---- 发行时间
  .withExpiresAt(expiresDate) <---- 有效期
  .withClaim("sessionId", sessionId)
  .withClaim("userName", userName)
  .withClaim("realName", realName)
  .sign(Algorithm.HMAC256(userId+"HelloLehr"));

在实际的开发中,令牌的有效期往往是越短越安全,因为令牌会频繁变化,即使有某个令牌被别人盗用,也会很快失效。但是有效期短也会导致用户体验不好(总是需要重新登录),所以这时候就会出现另外一种令牌—refresh token刷新令牌。刷新令牌的有效期会很长,只要刷新令牌没有过期,就可以再申请另外一个jwt而无需登录(且这个过程是在用户访问某个接口时自动完成的,用户不会感觉到令牌替换),对于刷新令牌的具体实现这里就不详细讲啦(其实因为我也没深入研究过XD…)

对比Session

在传统的session会话机制中,服务器识别用户是通过用户首次访问服务器的时候,给用户一个sessionId,然后把用户对应的会话记录放在服务器这里,以后每次通过sessionId来找到对应的会话记录。这样虽然所有的数据都存在服务器上是安全的,但是对于分布式的应用来说,就需要考虑session共享的问题了,不然同一个用户的sessionId的请求被自动分配到另外一个服务器上就等于失效了

而Jwt不但可以用于登录认证,也把相应的数据返回给了用户(就是载荷里的内容),通过签名来保证数据的真实性,该应用的各个服务器上都有统一的验证方法,只要能通过验证,就说明你的令牌是可信的,我就可以从你的令牌上获取你的信息,知道你是谁了,从而减轻了服务器的压力,而且也对分布式应用更为友好。(毕竟就不用担心服务器session的分布式存储问题了)

整合Springboot

导入java-jwt包

导入java-jwt包:

这个包里实现了一系列jwt操作的api(包括上面讲到的怎么校验,怎么生成jwt等等)

如果你是Maven玩家:

pom.xml里写入

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
 <groupId>com.auth0</groupId>
 <artifactId>java-jwt</artifactId>
 <version>3.8.3</version>
</dependency>

如果你是Gradle玩家:

build.gradle里写入

compile group: 'com.auth0', name: 'java-jwt', version: '3.8.3'

如果你是其他玩家:

maven中央仓库地址点这里

工具类的编写

代码如下:

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

/**
 * @author Lehr
 * @create: 2020-02-04
 */
public class JwtUtils {

 /**
 签发对象:这个用户的id
 签发时间:现在
 有效时间:30分钟
 载荷内容:暂时设计为:这个人的名字,这个人的昵称
 加密密钥:这个人的id加上一串字符串
 */
 public static String createToken(String userId,String realName, String userName) {

 Calendar nowTime = Calendar.getInstance();
 nowTime.add(Calendar.MINUTE,30);
 Date expiresDate = nowTime.getTime();

 return JWT.create().withAudience(userId) //签发对象
  .withIssuedAt(new Date()) //发行时间
  .withExpiresAt(expiresDate) //有效时间
  .withClaim("userName", userName) //载荷,随便写几个都可以
  .withClaim("realName", realName)
  .sign(Algorithm.HMAC256(userId+"HelloLehr")); //加密
 }

 /**
 * 检验合法性,其中secret参数就应该传入的是用户的id
 * @param token
 * @throws TokenUnavailable
 */
 public static void verifyToken(String token, String secret) throws TokenUnavailable {
 DecodedJWT jwt = null;
 try {
  JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();
  jwt = verifier.verify(token);
 } catch (Exception e) {
  //效验失败
  //这里抛出的异常是我自定义的一个异常,你也可以写成别的
  throw new TokenUnavailable();
 }
 }

 /**
 * 获取签发对象
 */
 public static String getAudience(String token) throws TokenUnavailable {
 String audience = null;
 try {
  audience = JWT.decode(token).getAudience().get(0);
 } catch (JWTDecodeException j) {
  //这里是token解析失败
  throw new TokenUnavailable();
 }
 return audience;
 }

 /**
 * 通过载荷名字获取载荷的值
 */
 public static Claim getClaimByName(String token, String name){
 return JWT.decode(token).getClaim(name);
 }
}

一点小说明:

关于jwt生成时的加密和验证方法:

jwt的验证其实就是验证jwt最后那一部分(签名部分)。这里在指定签名的加密方式的时候,还传入了一个字符串来加密,所以验证的时候不但需要知道加密算法,还需要获得这个字符串才能成功解密,提高了安全性。我这里用的是id来,比较简单,如果你想更安全一点,可以把用户密码作为这个加密字符串,这样就算是这段业务代码泄露了,也不会引发太大的安全问题(毕竟我的id是谁都知道的,这样令牌就可以被伪造,但是如果换成密码,只要数据库没事那就没人知道)

关于获得载荷的方法:

可能有人会觉得奇怪,为什么不需要解密不需要verify就能够获取到载荷里的内容呢?原因是,本来载荷就只是用Base64处理了,就没有加密性,所以能直接获取到它的值,但是至于可不可以相信这个值的真实性,就是要看能不能通过验证了,因为最后的签名部分是和前面头部和载荷的内容有关联的,所以一旦签名验证过了,那就说明前面的载荷是没有被改过的。

注解类的编写

在controller层上的每个方法上,可以使用这些注解,来决定访问这个方法是否需要携带token,由于默认是全部检查,所以对于某些特殊接口需要有免验证注解

免验证注解

@PassToken:跳过验证,通常是入口方法上用这个,比如登录接口

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Lehr
 * @create: 2020-02-03
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
 boolean required() default true;
}

拦截器的编写

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @author lehr
*/
@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {
 @Override
 public void addInterceptors(InterceptorRegistry registry) {

 //默认拦截所有路径
 registry.addInterceptor(authenticationInterceptor())
  .addPathPatterns("/**");
 }
 @Bean
 public JwtAuthenticationInterceptor authenticationInterceptor() {
 return new JwtAuthenticationInterceptor();
 }
} 

拦截器

import com.auth0.jwt.interfaces.Claim;
import com.imlehr.internship.annotation.PassToken;
import com.imlehr.internship.dto.AccountDTO;
import com.imlehr.internship.exception.NeedToLogin;
import com.imlehr.internship.exception.UserNotExist;
import com.imlehr.internship.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author Lehr
 * @create: 2020-02-03
 */
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
 @Autowired
 AccountService accountService;

 @Override
 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
 // 从请求头中取出 token 这里需要和前端约定好把jwt放到请求头一个叫token的地方
 String token = httpServletRequest.getHeader("token");
 // 如果不是映射到方法直接通过
 if (!(object instanceof HandlerMethod)) {
  return true;
 }
 HandlerMethod handlerMethod = (HandlerMethod) object;
 Method method = handlerMethod.getMethod();
 //检查是否有passtoken注释,有则跳过认证
 if (method.isAnnotationPresent(PassToken.class)) {
  PassToken passToken = method.getAnnotation(PassToken.class);
  if (passToken.required()) {
  return true;
  }
 }
 //默认全部检查
 else {
  System.out.println("被jwt拦截需要验证");
  // 执行认证
  if (token == null) {
  //这里其实是登录失效,没token了 这个错误也是我自定义的,读者需要自己修改
  throw new NeedToLogin();
  }

  // 获取 token 中的 user Name
  String userId = JwtUtils.getAudience(token);

  //找找看是否有这个user 因为我们需要检查用户是否存在,读者可以自行修改逻辑
  AccountDTO user = accountService.getByUserName(userId);

  if (user == null) {
  //这个错误也是我自定义的
  throw new UserNotExist();
  }

  // 验证 token
  JwtUtils.verifyToken(token, userId)

  //获取载荷内容
 	String userName = JwtUtils.getClaimByName(token, "userName").asString();
 	String realName = JwtUtils.getClaimByName(token, "realName").asString();

  //放入attribute以便后面调用
  request.setAttribute("userName", userName);
 	request.setAttribute("realName", realName);

  return true;

 }
 return true;
 }

 @Override
 public void postHandle(HttpServletRequest httpServletRequest,
    HttpServletResponse httpServletResponse,
    Object o, ModelAndView modelAndView) throws Exception {

 }

 @Override
 public void afterCompletion(HttpServletRequest httpServletRequest,
    HttpServletResponse httpServletResponse,
    Object o, Exception e) throws Exception {
 }
}

这段代码的执行逻辑大概是这样的:

  • 目标方法是否有注解?如果有PassToken的话就不用执行后面的验证直接放行,不然全部需要验证
  • 开始验证:有没有token?没有?那么返回错误
  • 从token的audience中获取签发对象,查看是否有这个用户(有可能客户端造假,有可能这个用户的账户被冻结了),查看用户的逻辑就是调用Service方法直接比对即可
  • 检验Jwt的有效性,如果无效或者过期了就返回错误
  • Jwt有效性检验成功:把Jwt的载荷内容获取到,可以在接下来的controller层中直接使用了(具体使用方法看后面的代码)

接口的编写

这里设计了两个接口:登录和查询名字,来模拟一个迷你业务,其中后者需要登录之后才能使用,大致流程如下:

登录代码

/**
 * 用户登录:获取账号密码并登录,如果不对就报错,对了就返回用户的登录信息
 * 同时生成jwt返回给用户
 *
 * @return
 * @throws LoginFailed 这个LoginFailed也是我自定义的
 */
 @PassToken
 @GetMapping(value = "/login")
 public AccountVO login(String userName, String password) throws LoginFailed{

 try{
  service.login(userName,password);
 }
 catch (AuthenticationException e)
 {
  throw new LoginFailed();
 }

 //如果成功了,聚合需要返回的信息
 AccountVO account = accountService.getAccountByUserName(userName);

 //给分配一个token 然后返回
 String jwtToken = JwtUtils.createToken(account);

 //我的处理方式是把token放到accountVO里去了
 account.setToken(jwtToken);

 return account;

 }

业务代码

这里列举一个需要登录,用来测试用户名字的接口(其中用户的名字来源于jwt的载荷部分)

 @GetMapping(value = "/username")
 public String checkName(HttpServletRequest req) {
 //之前在拦截器里设置好的名字现在可以取出来直接用了
 String name = (String) req.getAttribute("userName");
 return name;
 }

到此这篇关于利用Springboot实现Jwt认证的示例代码的文章就介绍到这了,更多相关Springboot Jwt认证内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot使用Jwt处理跨域认证问题的教程详解

    在前后端开发时为什么需要用户认证呢?原因是由于HTTP协定是不存储状态的,这意味着当我们透过账号密码验证一个使用者时,当下一个request请求时他就把刚刚的资料忘记了.于是我们的程序就不知道谁是谁了. 所以为了保证系统的安全,就需要验证用户是否处于登陆状态. 一.JWT的组成 JWT由Header.Payload.Signature三部分组成,分别用.分隔. 下面就是一个jwt真实的样子,说白了就是一个字符串,但是里面却存储了很重要的信息. eyJhbGciOiJIUzI1NiJ9.eyJzd

  • Springboot+SpringSecurity+JWT实现用户登录和权限认证示例

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的后端框架是Spring-Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进行了很好的无缝连接,在移动互联网的授权认证方面有很强的优势,具体的使用大家可以结合自己的业务场景进行选

  • springboot+jwt实现token登陆权限认证的实现

    一 前言 此篇文章的内容也是学习不久,终于到周末有时间码一篇文章分享知识追寻者的粉丝们,学完本篇文章,读者将对token类的登陆认证流程有个全面的了解,可以动态搭建自己的登陆认证过程:对小项目而已是个轻量级的认证机制,符合开发需求: 二 jwt实现登陆认证流程 用户使用账号和面发出post请求 服务器接受到请求后使用私钥创建一个jwt,这边会生成token 服务器返回这个jwt给浏览器 浏览器需要将带有token的jwt放入请求头 每次手到客户端请求,服务器验证该jwt的token 验证成功返回

  • Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码

    本人野生程序员一名,了解了一些微服务架构.前后端分离.SPA的知识后就想试试做点什么东西.之前一直做后端,前端只是有基础知识.之前学习过angularjs,但当时就是一脸懵逼(完全看不懂是啥)就放弃了.最近又学了Vue,这次感觉总算明白了一些,但其中也跳过很多坑(应该还会更多),在这里写下来记录一下吧. 说回主题,之前传统登录认证的方法基本是由服务器端提供一个登录页面,页面中的一个form输入username和password后POST给服务器,服务器将这些信息与DB或Ldap中的用户信息对比,

  • 利用Springboot实现Jwt认证的示例代码

    JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证 关于Shiro整合JWT,可以看这里:Springboot实现Shiro+JWT认证 概述 由于概念性内容网上多的是,所以就不详细介绍了 具体可以看这里:阮一峰大佬的博客 我总结几个重点: JWT,全称Json Web Token,是一种令牌认证的方式 长相: 头部:放有签名算法和令牌类型(这个就是JWT) 载荷:你在令牌上附带的信息:比如用户的id,用户的电话号

  • 基于Springboot实现JWT认证的示例代码

    目录 一.了解JWT 概念 作用 1.1 为什么授权要使用jwt 二.JWT结构 2.1 header 2.2 payload 2.3 signature 三.使用JWT 3.1 上手 3.2 封装工具类 3.3 整合springboot 最近一直想写一个类似于待办的东西,由于不想用传统的session,就卡住了,后来在各种群里扯皮,发现除了用缓存之外,还可以通过 JWT 来实现. 一.了解JWT 概念 json web token 用于在各方之间以 json 对象安全地传输信息,比如在前端和后

  • Springboot WebFlux集成Spring Security实现JWT认证的示例

    1 简介 在之前的文章<Springboot集成Spring Security实现JWT认证>讲解了如何在传统的Web项目中整合Spring Security和JWT,今天我们讲解如何在响应式WebFlux项目中整合.二者大体是相同的,主要区别在于Reactive WebFlux与传统Web的区别. 2 项目整合 引入必要的依赖: <dependency> <groupId>org.springframework.boot</groupId> <art

  • Spring Boot 集成JWT实现前后端认证的示例代码

    目录 前言 JWT简介 为什么要用JWT 传统session认证存在那些弊端? JWT认证的优势 JWT的数据结构 Header Payload Signature Spring Boot集成JWT 引入Jwt包 编写jwt工具类 Token认证拦截器 配置拦击器 登录验证流程 示例代码 总结 前言 小程序.H5应用的快速发展,使得前后端分离已经成为了趋势,然而系统认证却是系统的重要一部分,本文将讲解JWT如何实现前后端认证. JWT简介 JWT(全称:Json Web Token)是一个开放标

  • SpringBoot整合token实现登录认证的示例代码

    1.pom.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</group

  • SpringBoot整合Sa-Token实现登录认证的示例代码

    目录 依赖 登录 退出登录 前后端分离 今天分享的是 Spring Boot 整合 Sa-Token 实现登录认证. 依赖 首先,我们需要添加依赖: 关键依赖: <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.28.0</version> </depend

  • 在Angular中使用JWT认证方法示例

    本文介绍了在Angular中使用JWT认证方法示例,分享给大家,具体如下: 项目地址: grading-system 基于session的认证和基于token的认证的方式已经被广泛使用.在session认证中,服务端会存储一份用户登录信息,这份登录信息会在响应时传递给浏览器并保存为Cookie,在下次请求时,会带上这份登录信息,这样就能识别请求来自哪个用户. 在基于session的认证中,每个用户都要生成一份session,这份session通常保存在内存中,随着用户量的增加,服务端的开销会增大

  • golang之JWT实现的示例代码

    什么是JSON Web Token? JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON方式安全地传输信息.由于此信息是经过数字签名的,因此可以被验证和信任.可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名. 直白的讲jwt就是一种用户认证(区别于session.cookie)的解决方案. 出现的背景 众所周知,在jwt出现之前,我们已经有session.cookie来解决用户登

  • ASP.NET Core 实现基本认证的示例代码

    HTTP基本认证 在HTTP中,HTTP基本认证(Basic Authentication)是一种允许网页浏览器或其他客户端程序以(用户名:口令) 请求资源的身份验证方式,不要求cookie,session identifier.login page等标记或载体. - 所有浏览器据支持HTTP基本认证方式 - 基本身证原理不保证传输凭证的安全性,仅被based64编码,并没有encrypted或者hashed,一般部署在客户端和服务端互信的网络,在公网中应用BA认证通常与https结合 http

  • SpringBoot实现api加密的示例代码

    目录 SpringBoot的API加密对接 项目介绍 什么是RSA加密 加密实战 实战准备 真刀真枪 解密实战 实战准备 真刀真枪 总结 项目坑点 SpringBoot的API加密对接 在项目中,为了保证数据的安全,我们常常会对传递的数据进行加密.常用的加密算法包括对称加密(AES)和非对称加密(RSA),博主选取码云上最简单的API加密项目进行下面的讲解. 下面请出我们的最亮的项目 rsa-encrypt-body-spring-boot 项目介绍 该项目使用RSA加密方式对API接口返回的数

随机推荐