你们要的SpringBoot+JWT来了
什么是JWT
Json Web Token (JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)
该token被设计为紧凑且安全的 特别适用于分布式站点的单点登录(SSO)场景
随着JWT的出现 使得校验方式更加简单便捷化
JWT实际上就是一个字符串 它由三部分组成:头部 载荷和签名
用[.]分隔这三个部分 最终的格式类似于:xxxx.xxxx.xxxx
该服务器直接根据token取出保存的用户信息 即可对token的可用性进行校验 使得单点登录更为简单
JWT校验的过程
JWT的使用
项目使用:springboot+mysql+mybatisplus+lombok
不懂的可以先去学习一下,或者可以使用其他方法大同小异
第一步:创建SpringBoot项目我这里用的Maven构建
第二步:导包修改pom.xml下面的dependencies节点加入如下
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jwt依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> <version>5.1.47</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!--数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency>
第三步:修改application.yml
server: port: 8089 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false type: com.alibaba.druid.pool.DruidDataSource username: root password: root mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true type-aliases-package: com.kexun.springbootjwt.entity
第四步:创建数据库(jwt)和表user结构如下
CREATE DATABASE `jwt` CREATE TABLE `user` ( `id` varchar(32) DEFAULT NULL, `username` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8
第五步:配置数据源Druid配置类
@Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DruidDataSource druidDataSource() { return new DruidDataSource(); } }
第六步:创建实体
@Data public class User { @TableId(value = "id", type = IdType.UUID) private String id; private String username; private String password; }
第七步:创建mapper
public interface UserMapper extends BaseMapper<User> { }
第八步:创建自定义注解标记的这个注解的
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckToken { boolean required() default true; }
第九步:创建拦截器
public class LoginInterceptor implements HandlerInterceptor { private UserService userService; public LoginInterceptor(UserService userService) { this.userService = userService; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从 http 请求头中取出 token String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { String name = cookie.getName(); if ("x-token".equals(name)) { token = cookie.getValue(); } } } } // 如果不是映射到方法直接通过 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //检查有没有需要用户权限的注解 if (method.isAnnotationPresent(CheckToken.class)) { CheckToken checkToken = method.getAnnotation(CheckToken.class); if (checkToken.required()) { // 执行认证 if (StringUtils.isEmpty(token)) { throw new RuntimeException("无token,请重新登录"); } // 获取 token 中的 user id String userId; try { userId = JWT.decode(token).getClaim("id").asString(); } catch (JWTDecodeException j) { throw new RuntimeException("访问异常!"); } User user = userService.getById(userId); if (user == null) { throw new RuntimeException("用户不存在,请重新登录"); } Boolean verify = JwtUtils.isVerify(token, user); if (!verify) { throw new RuntimeException("非法访问!"); } return true; } } return true; } }
第十步:注册拦截器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserService userService; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor(userService)).addPathPatterns("/**"); } }
异常处理类
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result onRuntime(Exception e) { e.printStackTrace(); return Result.error(e.getMessage()); } }
返回包装类
package com.kexun.springbootjwt.utils; public class Result { private static final long serialVersionUID = 1L; private int code; private String msg; private Object data; public static long getSerialVersionUID() { return serialVersionUID; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Result(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } public Result(int code, String msg) { this.code = code; this.msg = msg; } public Result() { } public static Result success() { return new Result(0, "ok", null); } public static Result success(String msg) { return new Result(0, msg, null); } public static Result success(String msg, Object data) { return new Result(0, msg, data); } public static Result error() { return new Result(500, "error", null); } public static Result error(String msg) { return new Result(500, msg, null); } public static Result error(String msg, Object data) { return new Result(500, msg, data); } }
JwtUtils
public class JwtUtils { /** * 用户登录成功后生成Jwt * 使用Hs256算法 私匙使用用户密码 * * @param ttlMillis jwt过期时间 * @param user 登录成功的user对象 * @return */ public static String createJWT(long ttlMillis, User user) { //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的) Map<String, Object> claims = new HashMap<String, Object>(); claims.put("id", user.getId()); claims.put("username", user.getUsername()); claims.put("password", user.getPassword()); //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 String key = user.getPassword(); //生成签发人 String subject = user.getUsername(); //下面就是在为payload添加各种标准声明和私有声明了 //这里其实就是new一个JwtBuilder,设置jwt的body JwtBuilder builder = Jwts.builder() //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(UUID.randomUUID().toString()) //iat: jwt的签发时间 .setIssuedAt(now) //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .setSubject(subject) //设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, key); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); //设置过期时间 builder.setExpiration(exp); } return builder.compact(); } /** * Token的解密 * * @param token 加密后的token * @param user 用户的对象 * @return */ public static Claims parseJWT(String token, User user) { //签名秘钥,和生成的签名的秘钥一模一样 String key = user.getPassword(); //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } /** * 校验token * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过 * * @param token * @param user * @return */ public static Boolean isVerify(String token, User user) { //签名秘钥,和生成的签名的秘钥一模一样 String key = user.getPassword(); //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); if (claims.get("password").equals(user.getPassword())) { return true; } return false; } }
启动类:
@SpringBootApplication @MapperScan("com.kexun.springbootjwt.mapper") public class SpringbootJwtApplication { public static void main(String[] args) { SpringApplication.run(SpringbootJwtApplication.class, args); } }
具体演示:
登录:http://127.0.0.1:8089/login?username=lidong&password=123456
鉴权:http://127.0.0.1:8089/checkToken
清空cookie在鉴权
授权失败
都写在注释里面了不明白的可以在评论区留言我看到会及时回复的
附gitee源码地址:https://gitee.com/gdianqimeng/springboot-jwt.git