认证的技术栈(持续更新中)

  1. 什么是 Token(令牌)?
  2. 数字签名signature
  3. jwt 的基本原理介绍
  4. 自定义一个jwt 工具类,并用接口测试
  5. Springboot + jwt 实现带权限的认证(不使用spring security)

前言

前几天,diy 了一个jwt工具类,今天使用成熟的 jjwt-api 编写一个不使用spring security 的轻量级 带权限的认证。

我们在带权限认证中,将 认证放在 过滤器中, 将权限放在 拦截器 中,为什么如此安排,可以看《过滤器和拦截器的区别》。

开发环境: spring boot 3.5.4 、 java 17 。

项目结构

在这里插入图片描述


第一步:maven的 pom.xml 中添加 jwt 依赖包

 		<dependency>
        	<groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!-- 实现模块(必须包含,否则无具体逻辑) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <!-- 可选:JSON 处理模块(若用默认序列化需添加) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>


第二步:新建配置文件,并新建配置文件属性对应的类

1. 新建 application.yml 配置文件

文件中只有两大类配置

  • login.jwt :jwt 认证的配置,包括令牌前缀、请求头名称、令牌的过期时间、加密算法的公钥和私钥

  • api.permission : 接口的权限配置,一共分为三组:

    – path:"/api/admin/**", 只有 ADMIN 权限可访问的路径
    – path: "/api/user/**" , ADMIN、USER权限 都可访问的路径
    – path: "/api/public/**" ANONYMOUS 权限都可以访问,即所有权限都可以访问的路径,

login:
  ## 认证配置
  jwt:

    # 过期时间(单位:秒)
    expiration: 3600
    # 令牌前缀
    token-prefix: Bearer
    # 请求头名称
    header: Authorization

    # RSA公钥, PEM格式
    public-key: |
      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq0n+iYbJx+F9CQ5pUCvq
      DqULv1kB5n4EfRi8HTDtRbsggQWG7UzXMHKuCNgE3SV9ysDaQKgOJEXPcfSrIJzZ
      fe7dp8+LH3wRxIT7FKc7yegLe6VtAVSSx3gdP5Zs/J0QrrgHq/Gd/iIOglxUjrxt
      j4AxgDCBkHIKWnxVYXDEcGIiuN9h013ocyU0+i/g5+nkMaQsFotlvy/+Xb1p+VjB
      Sf2lVB3gC9HPYWepCGwxnp2wbACZzrQvvUdwqEjUnzUmjRUjAZlLBtsOQC8uYT3u
      7UL2oiNakS9blxJTc20fGM2YueBhxw7IMpX7NSO4yBQpAMTYjXvTW7iNFgpaWzC1
      xQIDAQAB
      -----END PUBLIC KEY-----

# RSA私钥
    private-key: |
      -----BEGIN PRIVATE KEY-----
      MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrSf6JhsnH4X0J
      DmlQK+oOpQu/WQHmfgR9GLwdMO1FuyCBBYbtTNcwcq4I2ATdJX3KwNpAqA4kRc9x
      9KsgnNl97t2nz4sffBHEhPsUpzvJ6At7pW0BVJLHeB0/lmz8nRCuuAer8Z3+Ig6C
      XFSOvG2PgDGAMIGQcgpafFVhcMRwYiK432HTXehzJTT6L+Dn6eQxpCwWi2W/L/5d
      vWn5WMFJ/aVUHeAL0c9hZ6kIbDGenbBsAJnOtC+9R3CoSNSfNSaNFSMBmUsG2w5A
      Ly5hPe7tQvaiI1qRL1uXElNzbR8YzZi54GHHDsgylfs1I7jIFCkAxNiNe9NbuI0W
      ClpbMLXFAgMBAAECggEAdD5XG4X50khYzpiVJ+9lfGLNwiHphD7pVOxAXZ8ipzwD
      JwxVMR4Au8RYD3BqfJpA8CRI+bATSNKHodJlgFBx2WEN1CCpTcipVjDZJH8JuFBd
      2p+9hjWbFvuQ/eJcPGNrOudxbWMf0aPTLOz0BZoi0EwzS8efZiO+mwHd/67D4W/b
      BsZtpG6sk1+9nVlJMPiQSHwBN+fAbBAc74/ut38tBUV6Vus2Oz+9Z0pCTWgI6vwW
      DvoNXZoEhiU4iCf3nltkfuhr++boYabsZdMcAg5AxdNPa1A40Kys8Uu44ta7xCmH
      Jflj3AItYUTtzEWCkkiR0AiGkG7VDzUdtXanTRiRAQKBgQDiMUiu/IILHKNeQXpJ
      EMHgslTcj4ktfoAYJbCHETA1LBoRl7Wh7UGEp+X8k8Urlg9DoEYapQCTLcCCJpFR
      RmrI9meqaeidcm+OByi6dqUPLjcI2kEGeN3SDehqT/y1269XAdWIxB9n3k3qmpNK
      pigK4XXU6ThGWWfOHg8jxCxb1QKBgQDB3IO5hdBS7lLF0N4TvaLQijirKXsjWSh9
      zv0IcE8zeR4JUMM/n/H6PVGjHCi5tzU/lS9RGDUJzpQtiZxwRRaC63IHxleqvqBU
      Dx//ngHqJwtBBOv/aKmX6gsdklK922X6t4J9tvH+NKI7GTilzg1XGjT7iCTaCNGO
      23+7pSGaMQKBgQCc/hXzlkDwU3U0Y3/rnJzkRwJNf7Z3hBiGKgqnBJAQJ8cENOcV
      N6ZE23/uWeX9BY8coe5maeXHQXcfHoO4BIbzasqy5gVU2muBCYkEOnr/2VTLaSi+
      xD85nvuUZmYBFzRQ0G4UGgOuRQaRca4PntmmC6yMFT9A3KcG0agya+NZ7QKBgAk2
      GyQzkYR/QnuXwxWZCyVxi+PGIs25LM1dinReMcU2WhEDP2PEi9DiHQDtE5pPdD5D
      Lm5iAhMnN686PK6w+bLa0R5BdGTujduwpEqXDWw80rISM8x9AqHvQvvyue+/fwb8
      tTyX9sOnaJX6StDGC6g53I6fF3EsvhhVoaoe3IvhAoGBANatqF2y2E49cSqxKoC5
      FDGCLOte7H3lzokjjdbnL49fz6IXjzgI1/A57fQve6yyl9Cm028Vxamfb/FAabv0
      2yrjmEBEH8sBPqP4t/Gr8Dm49EZXsRoqe90++SBVtCcAGgXubUZkm2KFhBleZZCn
      E5/QOqCAtHOkHykLnlBLIT69
      -----END PRIVATE KEY-----

# 接口权限配置
api:
  permissions:
    # 功能:不同的角色,可以访问的路径,如 ADMIN 可以访问 /api/admin/**  和  /api/user/** 两种路径
    - path: "/api/admin/**"
      roles: ADMIN
    - path: "/api/user/**"
      roles: USER,ADMIN
    - path: "/api/public/**"
      roles: ANONYMOUS

## 上面的格式会被解析成:
#  {
#    "api": {
#      "permissions": [
#        { "path": "/api/admin/**", "roles": "ADMIN" },
#        { "path": "/api/user/**", "roles": "USER,ADMIN" },
#        { "path": "/api/public/**", "roles": "ANONYMOUS" }
#      ]
#    }
#  }


2. 新建 jwtProperties 类

这个类用于保存application.yml中的 login.jwt 中的各个属性,主要包括令牌前缀、请求头名称、令牌的过期时间、加密算法的公钥和私钥各个属性

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "login.jwt")
public class JwtProperties {

    //公钥
    private String publicKey;

    //私钥
    private String privateKey;

    // 过期时间
    private long expiration;

    // token前缀,默认 Bearer
    private String tokenPrefix;

    // token 的key名,默认为Authorization
    private String header;


    public String getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }

    public String getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    public long getExpiration() {
        return expiration;
    }

    public void setExpiration(long expiration) {
        this.expiration = expiration;
    }

    public String getTokenPrefix() {
        return tokenPrefix;
    }

    public void setTokenPrefix(String tokenPrefix) {
        this.tokenPrefix = tokenPrefix;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

}

3. 新建 ApiPermssionProperties 类

这个类主要是获取 application.yml 中权限的属性,包括不同的角色所能访问的路径:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * API的权限属性,从application.yaml中读取
 */
@Component
@ConfigurationProperties(prefix = "api")
public class ApiPermissionProperties {

    private List<Permission> permissions;

    // 静态内部类:存储单个路径-角色映射
    public static class Permission {
        private String path;
        private String roles;

        // getter和setter
        public String getPath() {
            return path;
        }
        public void setPath(String path) {
            this.path = path;
        }
        public String getRoles() {
            return roles;
        }
        public void setRoles(String roles) {
            this.roles = roles;
        }
    }

    // 外部类的getter和setter
    public List<Permission> getPermissions() {
        return permissions;
    }
    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

第三步:新建工具类

1. 新建RsaUtils工具类

在本项目中,jwt 签名的加密算法使用的是 RSA 算法,所以RsaUtils的作用,就是将从configuation.yml中获取的 私钥和公钥 的字符串形式,转换成 具体的实例,以供 JwtUtils 类调用

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RsaUtils {

    /**
     * 从字符串中加载公钥
     */
    public static PublicKey getPublicKey(String publicKeyStr) throws Exception {
        // 去除换行和BEGIN/END标记
        publicKeyStr = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", "");

        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 从字符串中加载私钥
     */
    public static PrivateKey getPrivateKey(String privateKeyStr) throws Exception {

            // 去除换行和BEGIN/END标记
            privateKeyStr = privateKeyStr.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);


    }
}

2. 新建JwtUtils工具类

JwtUtils 用于 生成 jwt 令牌、验证 jwt 令牌、从令牌中获取用户信息,和从来令牌中获取角色。

import com.example.spring_jwt2.config.JwtProperties;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtils {


  final  private JwtProperties jwtProperties;

  private PrivateKey privateKey;
  private PublicKey publicKey;

  @Autowired // 这个@Autowired,就是提醒JwtUtils再执行方法之前,先将构造函数的JwtProperties jwtProperties注入
  // 初始化密钥
  public  JwtUtils(JwtProperties jwtProperties)  {

        this.jwtProperties = jwtProperties;
        try {
            this.privateKey = RsaUtils.getPrivateKey(jwtProperties.getPrivateKey());
            this.publicKey = RsaUtils.getPublicKey(jwtProperties.getPublicKey());
        }catch (Exception ex){
            String s =ex.toString();
        }
  }

  /**
   * 生成JWT令牌
   * @param username 用户名
   * @param roles 角色列表
   * @return JWT令牌
   */
  public String generateToken(String username, String... roles) {
      // 设置过期时间
      Date expirationDate = new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 1000);

      // 设置自定义声明
      Map<String, Object> claims = new HashMap<>();
      claims.put("roles", roles);

      // 生成令牌
      return Jwts.builder()
              .setClaims(claims)
              .setSubject(username)
              .setIssuedAt(new Date())
              .setExpiration(expirationDate)
              .signWith(privateKey, SignatureAlgorithm.RS256)
              .compact();
  }

  /**
   * 解析JWT令牌,获取声明
   */
  public Claims parseClaims(String token) {
      return Jwts.parserBuilder()
              .setSigningKey(publicKey)
              .build()
              .parseClaimsJws(token)
              .getBody();
  }

  /**
   * 验证JWT令牌是否有效
   */
  public boolean validateToken(String token) {
      try {
          Jwts.parserBuilder()
                  .setSigningKey(publicKey)
                  .build()
                  .parseClaimsJws(token);
          return true;
      } catch (JwtException | IllegalArgumentException e) {
          // 令牌无效(过期、签名错误等)
          return false;
      }
  }

  /**
   * 从令牌中获取用户名
   */
  public String getUsernameFromToken(String token) {
      return parseClaims(token).getSubject();
  }

  /**
   * 从令牌中获取角色列表
   */
  @SuppressWarnings("unchecked")
  public String[] getRolesFromToken(String token) {
      Claims claims = parseClaims(token);
      return ((java.util.List<String>) claims.get("roles"))
              .toArray(new String[0]);
  }
}

第四步:过滤器和拦截器的编写和配置

1. 新建 JwtAuthFilter类, jwt认证的过滤器

这个是一个过滤器,jwt的认证就是在这个过滤器中,当认证成功,就进入下一步的拦截器;认证失败,则立即返回客户端失败信息。

import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintWriter;

/**
 *  JWT认证的过滤器
 * 负责验证令牌有效性并解析用户信息
 */
@Component
@Order(1)
public class JwtAuthFilter implements Filter {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private JwtProperties jwtProperties;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=UTF-8");

        // 1. 从请求头获取令牌,从httpRequest 中获取 完整的token
        String token = httpRequest.getHeader(jwtProperties.getHeader());

        // 2. 处理令牌前缀,即将获取的令牌的前缀去掉
        if (token != null && token.startsWith(jwtProperties.getTokenPrefix())) {
            token = token.substring(jwtProperties.getTokenPrefix().length()).trim();
        }

        // 3. 验证令牌
        if (token == null || !jwtUtils.validateToken(token)) { // 如果令牌为空,或者验证失败
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = httpResponse.getWriter();
            out.write("{\"code\":401,\"message\":\"认证失败:无效的令牌\"}");
            out.flush();
            out.close();
            return;
        }

        // 4. 解析令牌,获取用户信息 (用户名和角色),如果没有权限的需求,可以省略 第4、5 两个步骤,也不用写拦截器
        Claims claims = jwtUtils.parseClaims(token);
        String username = claims.getSubject();
        String[] roles = jwtUtils.getRolesFromToken(token);

        // 5. 将用户信息存入请求属性,供后面的拦截器使用
        httpRequest.setAttribute("currentUsername", username);
        httpRequest.setAttribute("currentRoles", roles);

        // 6. 放行请求
        chain.doFilter(request, response);

    }

}

2. 新建 PermissionInterceptor 类, 权限拦截器

权限拦截器,根据在认证过滤器中 获取的 角色,检查角色是否有权限 访问 请求的路径。在这里是否有一个疑问,凭什么认为在 认证过滤器中的角色 就是正确的? 因为我们的 jwt 认证 本来就有防篡改的功能。所以这里放心的使用从过滤器中获取的角色,它如果被篡改了, 就过不了前面的认证了。

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.List;

/**
 * 权限拦截器
 * 负责验证用户是否有权限访问特定接口
 */
@Component
public class PermissionInterceptor implements HandlerInterceptor {

    @Autowired
    private ApiPermissionProperties apiPermissionProperties;

    /**
     * 在处理前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        response.setContentType("application/json;charset=UTF-8");

        // 1. 获取当前请求路径
        String requestUri = request.getRequestURI();

        // 2. 获取用户角色
        String[] currentRoles = (String[]) request.getAttribute("currentRoles");
        if (currentRoles == null) { // 没有获取到角色
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            out.write("{\"code\":403,\"message\":\"权限不足:未获取用户角色信息\"}");
            out.flush();
            out.close();
            return false;
        }

        // 3. 检查权限配置
       // Map<String, String> permissions = apiPermissionProperties.getPermissions();

        List<ApiPermissionProperties.Permission> permissions = apiPermissionProperties.getPermissions();
//        for (ApiPermissionProperties.Permission perm : permissions) {
//            String path = perm.getPath(); // 此处能正确获取带斜杠的路径,如"/api/admin/**"
//            String roles = perm.getRoles();
//            // 业务逻辑处理...
//        }

        boolean hasPermission = false;

        // 4. 匹配接口路径并检查权限
        for (ApiPermissionProperties.Permission perm : permissions) {
            String pattern = perm.getPath();
            String allowedRolesStr = perm.getRoles();

            // 将Ant风格的路径模式转换为正则表达式
            String regex = pattern.replace("/**", "/.*")
                    .replace("*", "[^/]*")
                    .replace("?", ".");

            if (Pattern.matches(regex, requestUri)) { // 判断 请求的 uri 是否能在 application.yml 文件中找到已经配置的路径
                // 检查用户角色是否在允许的角色列表中
                String[] allowedRoles = allowedRolesStr.split(",");
                for (String role : currentRoles) { // 循环请求中的角色,
                    if (Arrays.asList(allowedRoles).contains(role)) { // 如果在当前路径中可以找到此角色,则说明有权限
                        hasPermission = true;
                        break;
                    }
                }
                break;
            }
        }

        // 5. 权限校验结果处理
        if (!hasPermission) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            out.write("{\"code\":403,\"message\":\"权限不足:没有访问该接口的权限\"}");
            out.flush();
            out.close();
            return false;
        }

        // 6. 权限校验通过,放行
        return true;
    }
}

3. 新建 WebConfig, 配置过滤器和拦截器

将刚刚新建的过滤器和 拦截器配置到程序中,这样每次请求,就必须要经过滤器和拦截器了。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
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 WebConfig implements WebMvcConfigurer {

    @Autowired
    private PermissionInterceptor permissionInterceptor;

    /**
     * 过滤器
     * @param myFilter
     * @return
     */
    @Bean
    public FilterRegistrationBean<JwtAuthFilter> myFilterRegistration(JwtAuthFilter myFilter) {
        FilterRegistrationBean<JwtAuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(myFilter);
        // 明确指定需要拦截的路径
        registration.addUrlPatterns("/api/admin/*", "/api/user/*");
        // 可选:指定需要排除的路径(如果有)
         registration.addInitParameter("exclusions", "/api/public/*");
        return registration;
    }

    /**
     * 拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册权限拦截器,拦截需要权限验证的接口
        registry.addInterceptor(permissionInterceptor)
                .addPathPatterns("/api/admin/**", "/api/user/**") //添加拦截的路径
                .excludePathPatterns("/api/public/**");  // 放行的路径
    }


}


第五步:编写接口

1. 新建AuthController类, 授权接口

这里就只包含一个登录接口,登录成功,就签发一个token,即通过Authorization(授权)了。这里我们只用于测试,所以默认都是登录成功的,实际开发环境中,要将账号密码和服务器中的账号密码 比对的。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 认证控制器
 * 提供登录接口,用于获取JWT令牌
 */
@RestController
public class AuthController {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录接口,用于获取JWT令牌
     * 实际项目中应该验证用户名密码的正确性
     */
    @PostMapping("/api/public/login")
    public Map<String, Object> login(
            @RequestParam String username,
            @RequestParam String password) {

        // 这里简化处理,实际项目中应该从数据库中读取用户信息, 验证用户名密码,验证成功才能发放令牌。不成功,提示登录失败。
        // 为了测试,假设不同用户有不同角色
        String[] roles;
        if ("admin".equals(username)) {
            roles = new String[]{"ADMIN", "USER"};
        } else if ("user".equals(username)) {
            roles = new String[]{"USER"};
        } else {
            roles = new String[]{"GUEST"};
        }

        // 生成JWT令牌
        String token = jwtUtils.generateToken(username, roles);

        // 构建响应
        Map<String, Object> response = new HashMap<>();
        response.put("code", 200);
        response.put("message", "登录成功");
        response.put("token", jwtProperties.getTokenPrefix() + " " + token);
        response.put("username", username);
        response.put("roles", roles);

        return response;
    }
}

2. 新建TestController类,测试接口(可以愉快的测试了)

定义了三个不同权限的接口。

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 测试控制器
 * 提供不同权限要求的接口用于测试
 */
@RestController
public class TestController {

    /**
     * 公开接口,不需要认证
     */
    @GetMapping("/api/public/info")
    public Map<String, Object> publicInfo() {
        Map<String, Object> response = new HashMap<>();
        response.put("code", 200);
        response.put("message", "这是公开信息,任何人都可以访问");
        return response;
    }

    /**
     * 用户接口,需要USER或ADMIN角色
     */
    @GetMapping("/api/user/profile")
    public Map<String, Object> userProfile(HttpServletRequest request) {
        String username = (String) request.getAttribute("currentUsername");
        String[] roles = (String[]) request.getAttribute("currentRoles");

        Map<String, Object> response = new HashMap<>();
        response.put("code", 200);
        response.put("message", "这是用户个人信息");
        response.put("username", username);
        response.put("roles", roles);
        return response;
    }

    /**
     * 管理员接口,需要ADMIN角色
     */
    @GetMapping("/api/admin/dashboard")
    public Map<String, Object> adminDashboard(HttpServletRequest request) {
        String username = (String) request.getAttribute("currentUsername");
        String[] roles = (String[]) request.getAttribute("currentRoles");

        Map<String, Object> response = new HashMap<>();
        response.put("code", 200);
        response.put("message", "这是管理员控制台");
        response.put("username", username);
        response.put("roles", roles);
        return response;
    }


}

现在就可以愉快的测试了。

测试接口

将程序运行,使用 Thunder client 或postman 工具测试 接口

1. 先测试 /api/public/info ,公共接口,

这是所有人都可以访问,所以应该不需要登录。

在这里插入图片描述
正常返回没问题。

2. 测试 api/user/profile

  1. 我们先直接请求,不带token 试试看
    在这里插入图片描述
    成功的将其过滤了。

  2. 先使用user登录,获取 token ,再使用token 请求一次
    在这里插入图片描述
    登录,成功返回 token,并且此时的 token 拥有 USER 的权限了,带着token再一次请求.
    在这里插入图片描述
    OK ,可以访问。

3. 测试 api/admin/dashboard

我们用刚刚的token 能否 成功请求 api/admin/dashboard 呢?这个可是需要 ADMIN角色的权限的。
在这里插入图片描述
提示权限不足,使用admin 登录,获取ADMIN 的角色
在这里插入图片描述
这一次返回的token 是 ADMIN 的角色了,使用新的 token 请求一次

在这里插入图片描述
这一次没问题了。

源码下载:spring boot + jwt 的带权限认证

后续文章,将学习如何使用 Spring security 实现 带权限的安全认证。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐