一、什么是jwt
来自官方的回答:
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然 JWT 可以加密以在各方之间提供保密,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。
通俗的说,jwt就是将个人信息加密存储在一个签名令牌中,通过这个令牌验证个人信息。
二、什么时候要使用到jwt呢?
- 授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
- 信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。
要是仍然觉得还不是特别理解,那举一个很简单的例子。你想去一个游乐场,买个一张门票,这个门票就相当于你的jwt令牌。游乐场里的每个项目都要你出示门票才能玩,避免有人没买票翻墙混入游乐场(这里每个项目就相当于令牌允许的路由、服务和资源)。
三、jwt的组成
在其紧凑形式中,JSON Web Tokens 由用点 ( .
)分隔的三个部分组成,它们是:
- 标题
- 有效载荷
- 签名
四、jwt的第一个案例
因为jwt就是做登录验证的,所以我们首先要准备登录最基本的环境。
-
数据库建立user表
最基本的三个字段即可,包含用户名密码即可。
-
pom依赖配置
准备最基本的环境,以及jwt的环境依赖
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-test</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>lombok</artifactId> <artifactId>mysql-connector-java</artifactId> <artifactId>druid</artifactId> <artifactId>jjwt</artifactId>
-
准备好jwt的工具类
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Date; public class JwtUtil { public static final long EXPIRE = 1000 * 60 * 60 * 24; public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; public static String getJwtToken(String id, String nickname){ String JwtToken = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") .setSubject("chenhang") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .claim("id", id) .claim("nickname", nickname) .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } /** * 判断token是否存在与有效 * @param jwtToken * @return */ public static boolean checkToken(String jwtToken) { if(StringUtils.isEmpty(jwtToken)) return false; try { Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 判断token是否存在与有效 * @param request * @return */ public static boolean checkToken(HttpServletRequest request) { try { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) return false; Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 根据token获取会员id * @param request * @return */ public static String getMemberIdByJwtToken(HttpServletRequest request) { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) return ""; Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); Claims claims = claimsJws.getBody(); return (String)claims.get("id"); } }
-
搭建最基本的包环境,包括entity,mapper,service,controller
mapper: <select id="login" parameterType="User" resultType="User"> select * from user where username = #{username} and password = #{password} </select> controller: //用户登录生成token令牌 @PostMapping("/user/login") public Map<String,Object> login(@RequestBody User user){ log.info("用户名:[{}]",user.getUsername()); log.info("密码:[{}]",user.getPassword()); Map<String,Object> map = new HashMap<>(); try { User login = userService.login(user); //生成jwt令牌 String token = JwtUtil.getJwtToken(user.getId(), user.getUsername()); map.put("token",token); map.put("state",true); map.put("msg","登陆成功"); } catch (Exception e) { map.put("state",false); map.put("msg","登陆失败"); } return map; } //通过token令牌验证是否合法 @PostMapping("/user/getInfo") public Map<String,Object> getInfo(String token){ log.info("当前token为:[{}]",token); Map<String,Object> map = new HashMap<>(); try { boolean b = JwtUtil.checkToken(token); if (b){ map.put("state",true); map.put("msg","验证通过"); return map; } } catch (Exception e) { e.printStackTrace(); } map.put("state",false); map.put("msg","验证失败"); return map; }
-
以上只是在当个接口中去验证token的合法性,若按照以上方法,则每个接口、方法都要去验证,造成代码冗余。所以在单体架构中,我们可以去写一个拦截器。在访问接口之前就通过拦截器去验证token的合法性,让代码更简洁,逻辑更清晰。
拦截器的编写 import com.chen.utils.JwtUtil; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; public class JWTInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); Map<String,Object> map = new HashMap<>(); try { boolean b = JwtUtil.checkToken(token); if (b){ return true; } } catch (Exception e) { e.printStackTrace(); } map.put("state",false); map.put("msg","请先登录!"); String json = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); return false; } }
-
最后将拦截器配置到spring当中。
import com.chen.interceptors.JWTInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JWTInterceptor()) .addPathPatterns("/user/getInfo") .excludePathPatterns("/user/login"); } }