-
JWT (JSON Web Token)介绍, 官网 , github
-
定义: JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWTS可以使用秘密(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。
-
- 身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。
- 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
-
JWT由Header,Payload及Signature 3个子字符串组成
格式为:Header.Payload.Signature 例如:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtyp...oLL62SY Header : {"alg": "HS256"} Payload : {"sub": "Joe"} Signature : 对eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ 进行加密,形成一个新的字符串
-
Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)
-
Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。
{ "iss":"Issuer —— 用于说明该JWT是由谁签发的", "sub":"Subject —— 用于说明该JWT面向的对象", "aud":"Audience —— 用于说明该JWT发送给的用户", "exp":"Expiration Time —— 数字类型,说明该JWT过期的时间", "nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理", "iat":"Issued At —— 数字类型,说明该JWT何时被签发", "jti":"JWT ID —— 说明标明JWT的唯一ID", "user-definde1":"自定义属性举例", "user-definde2":"自定义属性举例" }
-
Signature : 是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature
-
-
JWT实现认证的原理
服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败
-
-
登录和认证流程
-
集成 JWT
-
从官网上找到 Java 语言的 JWT 使用教程,我们这里使用标注有:maven: com.auth0 / java-jwt / 3.3.0 的版本 , maven: io.jsonwebtoken / jjwt / 0.9.0 这个版本比较老了
-
添加 pom 配置 , github
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.9.0</version> </dependency>
-
定义注解
// 在Controller的方法上使用此注解,该方法在映射时会检查用户是否登录,未登录返回401错误 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Authorization { boolean required() default true; }
-
定义 JwtUtils
public class JwtUtils { // 生产 Token public static String getToken(User user){ String token = ""; try { Algorithm algorithm = Algorithm.HMAC256("secret"); //long expire = 7 * 24 * 60 * 60 * 1000; // 7天后过期 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, 10); // 10分钟后过期 token = JWT.create() .withIssuer("jwtdemo") .withClaim("userId", user.getUserId()) .withAudience(user.getUsername()) .withSubject(user.getUsername()) .withExpiresAt(calendar.getTime()) .sign(algorithm); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. } return token; } // 验证 token public static boolean checkToken(String token){ JWTVerifier jwtVerifier = JWT.require( Algorithm.HMAC256("secret") ).build(); try { jwtVerifier.verify(token); return true; } catch (JWTVerificationException e) { throw new RuntimeException("401 verify error"); } finally { return true; } } }
-
定义拦截器
@Configuration public class AuthenticationConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 拦截所有请求,通过判断是否有 @Authorization 注解, 决定是否需要登录; // 排除 swagger,alipay , wxpay registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**") .excludePathPatterns("/alipay") .excludePathPatterns("/wxpay/wxpay_notify"); } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } } public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 如果不是映射到方法直接通过 if(!(handler instanceof HandlerMethod)){ return true; // true表示继续流程 } HandlerMethod handlerMethod=(HandlerMethod)handler; Method method=handlerMethod.getMethod(); //检查是否有需要用户权限的注解 if (method.isAnnotationPresent(Authorization.class)) { Authorization auth = method.getAnnotation(Authorization.class); if (auth.required()) { // 从 http 请求头中取出 token String token = request.getHeader("Authorization"); if (token == null) { throw new RuntimeException("无token,请重新登录"); } // 获取 token 中的 user id , user name, 验证user是否存在 try { Long userId = JWT.decode(token).getClaim("userId").asLong(); String userName = JWT.decode(token).getAudience().get(0); // User user = userService.findUserById(userId); } catch (JWTDecodeException j) { throw new RuntimeException("401 decode error"); } // 验证 token , token 是否过期 JwtUtils.checkToken(token); } } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { } }
-
添加登录接口
@Data @AllArgsConstructor @NoArgsConstructor public class User { Long userId; String username; String password; String token } @Data @AllArgsConstructor @NoArgsConstructor public class ResultModel<T> implements java.io.Serializable { private int status; private String message; private T data; } @RestController @RequestMapping("api") public class UserController { @PostMapping("/login") public Object login(@RequestBody User user){ ResultModel<User> model = new ResultModel<>(); if(user ==null){ model.setStatus(400); model.setMessage("登录失败,用户不存在"); return model; } else { if (!user.getPassword().equals("123456")){ model.setStatus(500); model.setMessage("登录失败,密码错误"); return model; } else { String token = JwtUtils.getToken(user); user.setToken(token); model.setData(user); model.setStatus(200); return model; } } } @Authorization @GetMapping("/getMessage") public String getMessage(){ return "你已通过验证"; } }
-
来源:CSDN
作者:Dong滴个Dong
链接:https://blog.csdn.net/justfamily/article/details/104061621