阅读 67

Spring Cloud Gateway 使用JWT工具类做用户登录校验

1. JWT测试

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/19:29
 * @Description: JWT测试
 */
public class JwtTest {

    /**
     * 创建Jwt令牌:
     *
     * JWT = 头部Header + 载荷playload + 签名signature
     */
    @Test
    public void testCreateJwt() {
        // 构建jwt令牌
        // 1.头部Header: 描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等
        JwtBuilder builder = Jwts.builder()
                .setId("8989")                      // 设置令牌唯一编号
                .setIssuer("csp1999")               // 设置令牌颁发者
                .setSubject("JWT加密测试")           //  设置令牌主题  可以是JSON数据
                .setIssuedAt(new Date())            // 设置令牌签发日期
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 3));// 设置令牌过期时间 3分钟

        // 2.自定义载荷playload: 存放有效信息的地方
        Map<String,Object> userInfo = new HashMap<>();
        userInfo.put("username","csp");
        userInfo.put("password","123456");
        userInfo.put("school","河南科技大学");
        userInfo.put("age","22");
        // 将载荷添加到JWT令牌中
        builder.addClaims(userInfo);

        // 3.为令牌设置 签名signature
        builder.signWith(SignatureAlgorithm.HS256, "haust");// 设置令牌的签名 使用HS256算法,并设置SecretKey密钥(字符串)

        // 构建 并返回一个字符串
        String jwtStr = builder.compact();
        System.out.println(jwtStr);
    }

    /**
     * 解析Jwt令牌数据
     */
    @Test
    public void testParseJwt() {
        // jwt字符串
        String jwtStr = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4OTg5IiwiaXNzIjoiY3NwMTk5OSIsInN1YiI6IkpXVOWKoOWvhua1i-ivlSIsImlhdCI6MTYxMTQ4ODc1MSwiZXhwIjoxNjExNDg4OTMxLCJwYXNzd29yZCI6IjEyMzQ1NiIsInNjaG9vbCI6Iuays-WNl-enkeaKgOWkp-WtpiIsImFnZSI6IjIyIiwidXNlcm5hbWUiOiJjc3AifQ.uH28G9MSHfzaKBAOyr8AdksYLVvy8O5P8g7TORZIUFY";

        // 解析jwt字符串
        Claims claims = Jwts.parser().
                setSigningKey("haust").     // 密钥(盐)
                parseClaimsJws(jwtStr).     // 要解析的令牌对象
                getBody();                  // 获取解析后的结果

        // {jti=8989, iss=csp1999, sub=JWT加密测试, iat=1611488751, exp=1611488931, password=123456, school=河南科技大学, age=22, username=csp}
        System.out.println(claims);
    }
}
复制代码

2. JWT工具类

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/19:29
 * @Description: JWT工具类
 */
public class JwtUtil {
    // 有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 * 1000  一个小时

    // Jwt令牌信息
    public static final String JWT_KEY = "itcast";

    /**
     * 生成令牌
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        // 指定算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 当前系统时间
        long nowMillis = System.currentTimeMillis();
        // 令牌签发时间
        Date now = new Date(nowMillis);

        // 如果令牌有效期为null,则默认设置有效期1小时
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        // 令牌过期时间设置
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);

        // 生成秘钥
        SecretKey secretKey = generalKey();

        // 封装Jwt令牌信息
        JwtBuilder builder = Jwts.builder()
                .setId(id)                                  //唯一的ID
                .setSubject(subject)                        // 主题  可以是JSON数据
                .setIssuer("admin")                         // 签发者
                .setIssuedAt(now)                           // 签发时间
                .signWith(signatureAlgorithm, secretKey)    // 签名算法以及密匙
                .setExpiration(expDate);                    // 设置过期时间

        return builder.compact();
    }

    /**
     * 生成加密 secretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * 解析令牌数据
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

    public static void main(String[] args) {
        String jwt = JwtUtil.createJWT("weiyibiaoshi", "aaaaaa", null);

        System.out.println(jwt);
        try {
            Claims claims = JwtUtil.parseJWT(jwt);
            System.out.println(claims);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

3. 用户登录校验

3.1 网关过滤器

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/20:17
 * @Description: 授权过滤器
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    // 令牌头名字
    private static final String AUTHORIZE_TOKEN = "Authorization";

    /**
     * 全局过滤器
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取Request、Response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 获取请求的URI
        String path = request.getURI().getPath();

        // 如果是登录、goods等开放的微服务[这里的goods部分开放],则直接放行,这里不做完整演示,完整演示需要设计一套权限系统
        // 未登录下只放行登录和搜索
        if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {
            // 放行
            Mono<Void> filter = chain.filter(exchange);

            return filter;
        }

        // 从头文件中获取的令牌信息
        String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
        // 如果为true:说明令牌在头文件中, false:令牌不在头文件中,将令牌封装入头文件,再传递给其他微服务
        boolean hasToken = true;

        // 如果头文件中没有令牌信息,则从请求参数中获取
        if (StringUtils.isEmpty(token)) {
            token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            hasToken = false;
        }

        // 如果为空,则输出错误代码
        if (StringUtils.isEmpty(token)) {
            // 设置方法不允许被访问,405错误代码
            response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
            return response.setComplete();
        }

        // 如果不为空,则解析令牌数据
        try {
            Claims claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            // 解析失败,响应401错误
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 放行之前,将令牌封装到头文件中(这一步是为了方便AUTH2校验令牌)
        request.mutate().header(AUTHORIZE_TOKEN,token);

        // 放行
        return chain.filter(exchange);
    }


    /**
     * 过滤器执行顺序
     *
     * @return
     */
    @Override
    public int getOrder() {
        // 首位
        return 0;
    }
}
复制代码

3.2 网关微服务application.yml

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" # 跨域处理 允许所有的域
            allowedMethods: #支持的请求类型
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        # 对接商品goods微服务路由相关配置
        - id: changgou_goods_route
          uri: lb://changgou-goods
          predicates:
            - Path=/api/brand/**,/api/category/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter # 请求数限流 名字不能随便写 ,使用默认的facatory
              args:
                # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                key-resolver: "#{@ipKeyResolver}"
                # 令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
                # 令牌桶总容量
                redis-rate-limiter.burstCapacity: 1
                # 上面配置,表示1秒内,允许 1个请求通过,令牌桶的填充速率也是1秒钟添加1个令牌。
        # 对接用户user微服务路由相关配置
        - id: changgou_user_route
          uri: lb://changgou-user
          predicates:
            - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
          filters:
            # user微服务真实请求中是没有/api的,所以这里StripPrefix=1
            - StripPrefix=1
  # 微服务名称
  application:
    name: changgou-gateway-web
  # Redis配置
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 8.131.66.136
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: csp19990129

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true
复制代码

3.3 网关微服务主启动类

/**
 * @Auther: csp1999
 * @Date: 2021/01/24/15:16
 * @Description: 用户/前台微服务网关启动类
 */
@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayWebApplication.class, args);
    }

    /**
     * IP限流:由用户请求的IP创建创建用户唯一标识,进而根据IP进行限流操作
     *
     * @return
     */
    @Bean(name = "ipKeyResolver")
    public KeyResolver userKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                // 获取远程客户端IP
                String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
                System.out.println("hostName:" + hostName);
                return Mono.just(hostName);
            }
        };
    }
}
复制代码

3.4 用户微服务编写登录代码

/**
 * @Author: csp1999
 * @Description: User 的Controller
 * @Date 2021/1/14 0:18
 */
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

    @Autowired
    private UserService userService;

	 /***
     * 修改User数据
     * @param user
     * @param id
     * @return
     */
    @PutMapping(value = "/{id}")
    public Result update(@RequestBody User user, @PathVariable String id) {
      	 ...
    }

    /***
     * 新增User数据
     * @param user
     * @return
     */
    @PostMapping
    public Result add(@RequestBody User user) {
        ...
    }

    /***
     * 根据ID查询User数据
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result<User> findById(@PathVariable String id) {
        ...
    }

    /***
     * 查询User全部数据
     * @return
     */
    @GetMapping
    public Result<List<User>> findAll() {
       ...
    }

    /***
     * 用户登录
     * @param username
     * @param password
     * @param response
     * @param request
     * @return
     */
    @RequestMapping("/login")
    public Result<User> login(String username, String password, HttpServletResponse response, HttpServletRequest request) {
        // 1.从数据库中查询用户名对应的用户的对象
        User user = userService.findById(username);
        if (user == null) {
            // 2.判断用户是否为空 为空返回数据
            return new Result<User>(false, StatusCode.LOGINERROR, "用户名或密码错误...");
        }

        // 3.如果不为空 判断密码是否正确 若正确 则登录成功
        if (BCrypt.checkpw(password, user.getPassword())) {
            // 登录成功,讲用户信息存入map
            Map<String, Object> info = new HashMap<String, Object>();
            info.put("role", "USER");
            info.put("success", "SUCCESS");
            info.put("username", username);

            // 3.1生成令牌
            String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(info), null);
            // 3.2设置jwt存入 cookie 中
            Cookie cookie = new Cookie("Authorization", jwt);
            response.addCookie(cookie);
            // 3.3设置jwt存入头文件中
            response.setHeader("Authorization", jwt);

            return new Result<User>(true, StatusCode.OK, "登录成功", jwt);
        } else {
            // 登录失败
            return new Result<User>(false, StatusCode.LOGINERROR, "用户名或密码错误");
        }
    }
}
复制代码
文章分类
后端
文章标签