SpringBoot使用API KEY保护接口安全

  1. 概述
    安全性在REST API开发中扮演着重要的角色。一个不安全的REST API可以直接访问到后台系统中的敏感数据。因此,企业组织需要关注API安全性。
    Spring Security 提供了各种机制来保护我们的 REST API。其中之一是 API 密钥。API 密钥是客户端在调用 API 调用时提供的令牌。
    在本教程中,我们将讨论如何在Spring Security中实现基于API密钥的身份验证。

  2. REST API Security
    Spring Security可以用来保护REST API的安全性。REST API是无状态的,因此不应该使用会话或cookie。相反,应该使用Basic authentication,API Keys,JWT或OAuth2-based tokens来确保其安全性。

  • Basic Authentication
    Basic authentication是一种简单的认证方案。客户端发送HTTP请求,其中包含Authorization标头的值为Basic base64_url编码的用户名:密码。Basic authentication仅在HTTPS / SSL等其他安全机制下才被认为是安全的。

  • OAuth2
    OAuth2是REST API安全的行业标准。它是一种开放的认证和授权标准,允许资源所有者通过访问令牌将授权委托给客户端,以获得对私有数据的访问权限。

  • API Keys
    一些REST API使用API密钥进行身份验证。API密钥是一个标记,用于向API客户端标识API,而无需引用实际用户。标记可以作为查询字符串或在请求头中发送。

  1. 用API Keys保护REST API
  • 添加Maven 依赖
  • 让我们首先在我们的pom.xml中声明spring-boot-starter-security依赖关系:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 创建自定义过滤器(Filter)

实现思路是从请求头中获取API Key,然后使用我们的配置检查秘钥。在这种情况下,我们需要在Spring Security 配置类中添加一个自定义的Filter。

我们将从实现GenericFilterBean开始。GenericFilterBean是一个基于javax.servlet.Filter接口的简单Spring实现。

让我们创建AuthenticationFilter类:

public class AuthenticationFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws IOException, ServletException {
        try {
            Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception exp) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            PrintWriter writer = httpResponse.getWriter();
            writer.print(exp.getMessage());
            writer.flush();
            writer.close();
        }

        filterChain.doFilter(request, response);
    }
}

我们只需要实现doFilter()方法,在这个方法中我们从请求头中获取API Key,并将生成的Authentication对象设置到当前的SecurityContext实例中。

然后请求被传递给其余的过滤器处理,接着转发给DispatcherServlet最后到达我们的控制器。

在AuthenticationService类中,实现从Header中获取API Key并构造Authentication对象,代码如下:

public class AuthenticationService {
    private static final String AUTH_TOKEN_HEADER_NAME = "X-API-KEY";
    private static final String AUTH_TOKEN = "Baeldung";

    public static Authentication getAuthentication(HttpServletRequest request) {
        String apiKey = request.getHeader(AUTH_TOKEN_HEADER_NAME);

        if ((apiKey == null) || !apiKey.equals(AUTH_TOKEN)) {
            throw new BadCredentialsException("Invalid API Key");
        }

        return new ApiKeyAuthentication(apiKey, AuthorityUtils.NO_AUTHORITIES);
    }
}

在这里,我们检查请求头是否包含 API Key,如果为空 或者Key值不等于密钥,那么就抛出一个 BadCredentialsException。如果请求头包含 API Key,并且验证通过,则将密钥添加到安全上下文中,然后调用下一个安全过滤器。getAuthentication 方法非常简单,我们只是比较 API Key 头部和密钥是否相等。

为了构建 Authentication 对象,我们必须使用 Spring Security 为了标准身份验证而构建对象时使用的相同方法。所以,需要扩展 AbstractAuthenticationToken 类并手动触发身份验证。

  • 扩展AbstractAuthenticationToken
    为了成功地实现我们应用的身份验证功能,我们需要将传入的API Key转换为AbstractAuthenticationToken类型的身份验证对象。AbstractAuthenticationToken类实现了Authentication接口,表示一个认证请求的主体和认证信息。

让我们创建ApiKeyAuthentication类:

public class ApiKeyAuthentication extends AbstractAuthenticationToken {
    private final String apiKey;

    public ApiKeyAuthentication(String apiKey,
        Collection<?extends GrantedAuthority> authorities) {
        super(authorities);
        this.apiKey = apiKey;
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return apiKey;
    }
}

ApiKeyAuthentication 类是类型为 AbstractAuthenticationToken 的对象,其中包含从 HTTP 请求中获取的 apiKey 信息。在构造方法中使用 setAuthenticated(true) 方法。因此,Authentication对象包含 apiKey 和authenticated字段:
在这里插入图片描述

  • Security Config
    通过创建建一个SecurityFilterChain bean,可以通过编程方式把我们上面编写的自定义过滤器(Filter)进行注册。

我们需要在 HttpSecurity 实例上使用 addFilterBefore() 方法在 UsernamePasswordAuthenticationFilter 类之前添加 AuthenticationFilter。

创建SecurityConfig 类:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf()
          .disable()
          .authorizeRequests()
          .antMatchers("/**")
          .authenticated()
          .and()
          .httpBasic()
          .and()
          .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and()
          .addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

}

此外注意代码中我们吧绘画策略(session policy)设置为无状态(STATELESS),因为我们使用的是REST。

  • ResourceController
  • 最后,我们创建ResourceController,实现一个Get请求 /home
@RestController
public class ResourceController {
    @GetMapping("/home")
    public String homeEndpoint() {
        return "Baeldung !";
    }
}
  • 禁用 Auto-Configuration
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class})
public class ApiKeySecretAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiKeySecretAuthApplication.class, args);
    }
}
  • 测试
    我们先不提供API Key进行测试
curl --location --request GET 'http://localhost:8080/home'

返回 401 未经授权错误。

请求头中加上API Key后,再次请求

curl --location --request GET 'http://localhost:8080/home' \
--header 'X-API-KEY: Baeldung'

请求返回状态200

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
# 基于springBoot编写的RESTFul API 本项目可用于快速搭建基于springBoot的RESTFul API服务,同时集成了swagger作为接口的在线文档与调试工具,数据交互格式建议是JSON格式。 ## 增强理解 [Spring Boot集成swagger2生成接口文档](https://www.jianshu.com/p/a115c9367a59) [自定义RESTful API服务规范](https://www.jianshu.com/p/bdea0385a77e) ## RESTFul API 首先本项目是一个RESTFul API服务的demo,与此同时再集成了一些做API常用的工具。 对于RESTFul API服务各有各的见解,网上大多是自己封装了controller层统一格式返回,通常情况下,不管你怎么请求,它总是响应你的http状态码为200。 而本项目中充分结合了HTTP状态码规范,使用ResponseEntity + HttpStatus的方式完成我们的API。当然,你想做一个完全具有RESTFul风格的API,你需要具有良好的RESTFul风格的资源设计能力。 ## 全局异常处理 采用@RestControllerAdvice + @ExceptionHandler的方式对全局异常进行处理,同时加入了常见的一些自定义异常类。 ## 参数验证器 采用spring提供的@Validated注解结合hibernate的validator进行验证,你只需要在你的验证实体对象中使用验证注解,如@NotNull、@NotBlank等,同时在你的controller方法中加入@Validated注解即可,验证结果信息已经由全局异常处理器帮你做好了。 ## TOKEN验证 当我们的API需要登录后才能访问时,简单做法是登录验证成功后给客户端生成一个token,客户端后续的请求都需要带上这个token参数,服务端对这个token进行验证,验证通过即可访问API。本项目中也集成了token的生成,同时通过拦截器统一验证了token的有效性,这依赖于redis来存储token,但这也是比较流行的做法。 你只需要在controller中需要的地方加入@AccessToken注解即可,同时如果你需要当前登录的用户信息,只需要在方法参数中加入@UserPrincipal注解修饰参数UserPrincipalVO即可。 代码示例: ``` // 在登录业务类中注入用户TOKEN组件 @Autowired private UserTokenComponent userTokenComponent; // 登录 public UserPrincipalVO login(String account, String pwd) { // 登录逻辑验证 ~~~~~ // 验证成功后,可得到用户信息 // 根据用户信息创建token, 可以把用户其它信息填充进UserPrincipalVO中,提供了全参的构造方法 UserPrincipalVO userPrincipalVO = new UserPrincipalVO(account); return userTokenComponent.createToken(userPrincipalVO); } ``` ``` @ApiOperation(value = "需要登录后才能访问的API") @GetMapping("/token") @AccessToken public ResponseEntity<UserPrincipalVO> testToken(@ApiIgnore @UserPrincipal UserPrincipalVO user) { return ResponseEntity.ok(user); } ``` ## 参数签名验证 当我们的API需要作为开放接口时,一般会为接入方分配对应的accessKey和secret,接入方每次请求我们的API时,需要把accessKey和secret与其他参数进行统一的方式签名得到签名串sign,同时把sign作 ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
Spring Boot项目中生成接口的密钥key可以使用Java原生类似的方式,主要是通过Java加密解密相关的API来实现。以下是一种基于Spring Boot的生成AES加密算法密钥的方式: 1. 在Spring Boot项目的配置文件application.properties中配置密钥长度,例如: ``` encryption.key.length=128 ``` 2. 创建一个用于生成密钥的工具类EncryptionUtils,包含一个用于生成密钥的静态方法getSecretKey,如下: ```java import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @Component public class EncryptionUtils { @Value("${encryption.key.length}") private int keyLength; public SecretKey getSecretKey() throws Exception { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(keyLength); SecretKey secretKey = keyGen.generateKey(); byte[] keyBytes = secretKey.getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } } ``` 3. 在需要使用密钥的地方,注入EncryptionUtils并调用getSecretKey方法获取密钥,例如: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.crypto.Cipher; import javax.crypto.SecretKey; @Service public class EncryptionService { @Autowired private EncryptionUtils encryptionUtils; public String encrypt(String strToEncrypt) throws Exception { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKey secretKey = encryptionUtils.getSecretKey(); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedBytes = cipher.doFinal(strToEncrypt.getBytes()); return Base64.getEncoder().encodeToString(encryptedBytes); } public String decrypt(String strToDecrypt) throws Exception { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); SecretKey secretKey = encryptionUtils.getSecretKey(); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)); return new String(decryptedBytes); } } ``` 需要注意的是,密钥的生成方式应该根据具体的使用场景和安全需求进行选择。如果需要更高的安全性,可以使用更复杂的生成方式,比如使用RSA算法生成密钥对等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值