Spring Boot实战之Filter实现使用JWT进行接口认证

jwt(json web token)

用户发送按照约定,向服务端发送 Header、Payload 和 Signature,并包含认证信息(密码),验证通过后服务端返回一个token,之后用户使用该token作为登录凭证,适合于移动端和api

jwt使用流程

JWT_workflow

代码实现

1. 依赖

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.johnfnash.learn.springboot</groupId>
    <artifactId>jwt-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jwt-demo</name>
    <description>Demo project for JWT</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. JWT token生成及解析工具类

package com.johnfnash.learn.jwt.util;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.util.encoders.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtUtils {

    /**
     * 签发 JWT
     * 
     * @param id
     * @param subject   可以是 JSON 数据,尽可能少
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String userId, String subject, long ttlMillis) {
        SignatureAlgorithm algorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // header Map
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        SecretKey secretKey = generalKey();

        Claims claims = Jwts.claims();
        claims.setId(UUID.randomUUID().toString()) // jti (JWT ID),防止jwt被重新发送
                .setSubject(subject) // 主题
                .setIssuedAt(now) // 签发时间
                .setIssuer("user"); // 签发者

        // payload 中 放入自定义信息
        Map<String, Object> selfMap = new HashMap<>();
        selfMap.put("userId", userId);
        claims.putAll(selfMap);

        JwtBuilder builder = Jwts.builder().setHeader(map) // header
                .setClaims(claims) // 使用 JSON 实例设置 payload
                .signWith(algorithm, secretKey); // 签名算法以及密钥
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }

    private static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECRET);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 
     * 解析JWT字符串
     * 
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) {
        Claims claims = null;
        try {
            SecretKey secretKey = generalKey();
            claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
        } catch (Exception e) {
            System.err.println("token 校验失败");
        }
        return claims;
    }


    public static void main(String[] args) {
        String token = createJWT("aaaa", "同步", 60000L);
        System.out.println("token: " + token);
        System.out.println(parseJWT(token));
    }

}

3. JWT 校验 filter

package com.johnfnash.learn.jwt.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.johnfnash.learn.jwt.exception.ServiceException;
import com.johnfnash.learn.jwt.util.JwtUtils;

import io.jsonwebtoken.Claims;

/**
 * JWT 校验 filter
 */
@WebFilter(filterName = "jwtFilter", urlPatterns = "/hello/*")
public class HTTPBearerAuthorizeAttribute implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if("options".equals(httpRequest.getMethod())) {
            chain.doFilter(request, response);
            return;
        }
        
        String auth = httpRequest.getHeader("Authorization");
        if(auth != null && auth.length() > 7) {
            String headStr = auth.substring(0, 6).toLowerCase();
            if (headStr.compareTo("bearer") == 0)
            {
                auth = auth.substring(7, auth.length());
                Claims claims = JwtUtils.parseJWT(auth);
                if(claims != null) {
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
        
        // token 校验失败,抛出异常
        throw new ServiceException("token校验失败");
    }

    @Override
    public void destroy() {
    }

}

注:

  • 从 请求 的header 从获取传入的 token 并进行校验,校验通过则请求走下去;否则,抛出异常,在异常统一处理类中处理。
  • 需要在启动类中添加 @ServletComponentScan 注解扫描定义的 filter
@SpringBootApplication
@ServletComponentScan
public class JwtDemoApplication {

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

}

其中,异常处理类代码如下:

package com.johnfnash.learn.jwt.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.johnfnash.learn.jwt.dto.JsonResult;
import com.johnfnash.learn.jwt.enums.StatusCodeEnum;
import com.johnfnash.learn.jwt.exception.ServiceException;

/**
 * 统一异常处理
 *
 */
@ControllerAdvice
@ResponseBody
public class UnifiedExceptionHandler    {

    @ExceptionHandler(value = ServiceException.class)
    public JsonResult<?> handleAuthorizationException(HttpServletRequest request,
            HttpServletResponse response, ServiceException ex) {
        
        JsonResult<?> jr = JsonResult.of(StatusCodeEnum.AUTH_ERR.getCode(), null);
        if (ex instanceof ServiceException) {
            jr.setMsg(ex.getMessage() != null ? ex.getMessage() : "授权校验失败");
        } else {
            jr.setMsg("授权校验失败");
        }

        return jr;
    }

}

注:其中 ServiceException 为自定义的业务异常类,JsonResult 为对响应结果进行的一个封装。

4. token 的获取

登录成功之后获取JWT,大致流程如下:

package com.johnfnash.learn.jwt.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.johnfnash.learn.jwt.util.JwtUtils;

@RestController
@RequestMapping("/auth")
public class AuthController {

     @RequestMapping(value="/login",method = RequestMethod.POST)
     public String login(String loginName, String password) {
         // 1. 进行账号、密码校验
         
         // 2. 校验通过之后
         String userId = "adadsad";
         String jwt = JwtUtils.createJWT(userId, loginName, 1800000);
         return jwt;
     }
    
}

5. API 的 token 校验

package com.johnfnash.learn.jwt.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.johnfnash.learn.jwt.dto.JsonResult;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("detail")
    public JsonResult<String> hello() {
        return JsonResult.of("hello world!");
    }
    
}

filter 中定义了需要进行过滤的请求的 url pattern,这个 controller 下的接口就是要被过滤,进行token校验的。

6. 测试

1) 登录,获取token

token_request

2) 使用上面获取的token进行接口调用

未使用token,获取token错误,或者token过期时

token_auth_fail

使用正确的token时

token_auth_success

注意:token 放在 请求的 Authorization 头中。

本文参考:Spring Boot实战之Filter实现使用JWT进行接口认证

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值