SpringBoot整合JWT的简单应用

基于传统session的认证

http协议本身是一种无状态的协议,如果用户向服务器提供用户名和密码进行认证,那么下一次请求时,用户还需要再进行用户认证。根据http协议,服务器无法确定是哪个用户发出的请求,所以只能在服务器端存储一份用户认证的信息。这份认证信息会在响应时传递到客户端,保存为cookie,以便下次请求时服务器能识别来自哪个用户,无需再进行用户认证。

传统session认证

传统session的弊端

  1. session通常保存在服务端内存中,随着认证用户的增多,服务端的开销会明显增大。
  2. session保存在某一台服务器的内存中,这意味着用户下次请求还必须是这台服务器,这在分布式应用上限制了负载均衡器的能力。
  3. 客户端需要携带cookie,如果cookie被截取,很容易收到跨站请求伪造共计。

JWT是什么?

JWT(Json Web Token)通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输前还能完成数据加密、签名等处理。

JWT能做什么?

授权:JWT的最常见方案。一旦用户登录,后续的请求将包括JWT,允许用户访问令牌允许的路由、服务器、资源。

JWT的优势

  1. 简洁,传输速度快。
  2. 自包含:负载中包含用户所需的信息,避免多次查询数据库。
  3. 令牌是以JSON加密保存在客户端,所以JWT是跨语言的,原则上任何Web形式都支持。
  4. 无需在服务端保存用户信息,适用于分布式微服务。

JWT代码演示

  • Pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.itcast</groupId> <artifactId>Jwt</artifactId> <version>1.0-SNAPSHOT</version> <name>Jwt</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.2.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.0.RELEASE</version> </plugin> </plugins> </build> </project>

  • resource/application.yml
server:  port: 8090 spring:  datasource:  type: com.alibaba.druid.pool.DruidDataSource  driver-class-name: com.mysql.cj.jdbc.Driver  url: jdbc:mysql://localhost:3306/exercise?serverTimezone=UTC  username: root  password: Wy19971124 mybatis:  mapper-locations: classpath:/mapper/*.xml logging:  level:  cn:  itcast:  dao:  debug

  • resource/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.dao.UserDao">
    <select id="login" resultType="cn.itcast.domain.User">
    select * from user where name=#{name} and password=#{password}
    </select>
</mapper>

  • src/main/java/cn.itcast/application.java
package cn.itcast; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("cn.itcast.dao") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

  • src/main/java/cn.itcast/domain/User.java
package cn.itcast.domain;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String password;
}

  • src/main/java/cn.itcast/dao/UserDao.interface
package cn.itcast.dao;

import cn.itcast.domain.User;

public interface UserDao {
    User login(User user);
}

  • src/main/java/cn.itcast/service/UserService.interface
package cn.itcast.service;

import cn.itcast.domain.User;

public interface UserService {
    User login(User user);
}

  • src/main/java/cn.itcast/service/impl/UserServiceImpl.java
package cn.itcast.service.impl;

import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
import cn.itcast.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public User login(User user) {
        User loginUser = userDao.login(user);
        if(loginUser != null) {
            return loginUser;
        }
        else {
            throw new RuntimeException("登录失败");
        }
    }
}

  • src/main/java/cn.itcast/interceptors/JwtInterceptor.java(编写拦截器
package cn.itcast.interceptors;

import cn.itcast.utils.JwtUtils;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
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 {
            DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);  // 获取令牌
            return true;
        }
        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 (Exception e) {
            e.printStackTrace();  // 总异常
            map.put("msg", "令牌无效");
        }
        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;
    }
}

  • src/main/java/cn.itcast/config/InterceptorConfig.java(配置拦截器
package cn.itcast.config;

import cn.itcast.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("/UserController/verify")  // 拦截/UserController/verify
                .excludePathPatterns("/UserController/login");  //放行/UserController/login
    }
}

  • src/main/java/cn.itcast/controller/UserController.java
package cn.itcast.controller;

import cn.itcast.domain.User;
import cn.itcast.service.UserService;
import cn.itcast.utils.JwtUtils;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/UserController")
@ResponseBody
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Map<String, Object> login(User user) {
        log.info("用户名:[{}]", user.getName());
        log.info("密码:[{}]", user.getPassword());
        Map<String, Object> map = new HashMap<>();
        try {
            User loginUser = userService.login(user);
            Map<String, String> payload = new HashMap<>();
            payload.put("id", loginUser.getId().toString());
            payload.put("name", loginUser.getName());
            String token = JwtUtils.getToken(payload);  // 生成JWT令牌
            map.put("state", true);
            map.put("msg", "登录成功");
            map.put("token", token);
        }
        catch (Exception e) {
            map.put("state", false);
            map.put("msg", e.getMessage());
        }
        return map;
    }

    @PostMapping("/verify")
    public Map<String, Object> verify(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        String token = request.getHeader("token");
        DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
        log.info("用户id:[{}]", tokenInfo.getClaim("id").asString());
        log.info("用户名:[{}]", tokenInfo.getClaim("name").asString());
        map.put("state", true);
        map.put("msg", "请求成功");
        return map;
    }
}

编写和配置拦截器是难点

实验

首次登录访问:

请求头中携带令牌认证:

转载自:https://zhuanlan.zhihu.com/p/352351265