Spring Security的API Key实现SpringBoot 接口安全
Spring Security 提供了各种机制来保护我们的 REST API。其中之一是 API 密钥。API 密钥是客户端在调用 API 调用时提供的令牌。
在本教程中,我们将讨论如何在Spring Security中实现基于API密钥的身份验证。
API Keys
一些REST API使用API密钥进行身份验证。API密钥是一个标记,用于向API客户端标识API,而无需引用实际用户。标记可以作为查询字符串或在请求头中发送。
一 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
二 验证请求头的API KEY
publicclassAuthenticationService{privatestaticfinalStringAUTH_TOKEN_HEADER_NAME="X-API-KEY";privatestaticfinalStringAUTH_TOKEN="Baeldung";publicstaticAuthenticationgetAuthentication(HttpServletRequest request){String apiKey = request.getHeader(AUTH_TOKEN_HEADER_NAME);if((apiKey ==null)||!apiKey.equals(AUTH_TOKEN)){thrownewBadCredentialsException("Invalid API Key");}returnnewApiKeyAuthentication(apiKey,AuthorityUtils.NO_AUTHORITIES);}}
在这里,我们检查请求头是否包含 API Key,如果为空 或者Key值不等于密钥,那么就抛出一个 BadCredentialsException。如果请求头包含 API Key,并且验证通过,则将密钥添加到安全上下文中,然后调用下一个安全过滤器。getAuthentication 方法非常简单,我们只是比较 API Key 头部和密钥是否相等。
为了构建 Authentication 对象,我们必须使用 Spring Security 为了标准身份验证而构建对象时使用的相同方法。所以,需要扩展 AbstractAuthenticationToken 类并手动触发身份验证。
三 扩展AbstractAuthenticationToken
为了成功地实现我们应用的身份验证功能,我们需要将传入的API Key转换为AbstractAuthenticationToken类型的身份验证对象。AbstractAuthenticationToken类实现了Authentication接口,表示一个认证请求的主体和认证信息。
publicclassApiKeyAuthenticationextendsAbstractAuthenticationToken{privatefinalString apiKey;publicApiKeyAuthentication(String apiKey,Collection<?extendsGrantedAuthority> authorities){super(authorities);this.apiKey = apiKey;setAuthenticated(true);}@OverridepublicObjectgetCredentials(){returnnull;}@OverridepublicObjectgetPrincipal(){return apiKey;}}
ApiKeyAuthentication 类是类型为 AbstractAuthenticationToken 的对象,其中包含从 HTTP 请求中获取的 apiKey 信息。在构造方法中使用 setAuthenticated(true) 方法。因此,Authentication对象包含 apiKey 和authenticated字段
四 创建自定义过滤器
实现思路是从请求头中获取API Key,然后使用我们的配置检查秘钥。在这种情况下,我们需要在Spring Security 配置类中添加一个自定义的Filter。
我们将从实现GenericFilterBean开始。GenericFilterBean是一个基于javax.servlet.Filter接口的简单Spring实现。
publicclassAuthenticationFilterextendsGenericFilterBean{@OverridepublicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain filterChain)throwsIOException,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最后到达我们的控制器。
五 配置类
通过创建建一个SecurityFilterChain bean,可以通过编程方式把我们上面编写的自定义过滤器(Filter)进行注册。
我们需要在 HttpSecurity 实例上使用 addFilterBefore() 方法在 UsernamePasswordAuthenticationFilter 类之前添加 AuthenticationFilter。
@Configuration@EnableWebSecuritypublicclassSecurityConfig{@BeanpublicSecurityFilterChainfilterChain(HttpSecurity http)throwsException{
http.csrf().disable().authorizeRequests().antMatchers("/**").authenticated().and().httpBasic().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().addFilterBefore(newAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);return http.build();}}
六 测试
- controller
@RestControllerpublicclassResourceController{@GetMapping("/home")publicStringhomeEndpoint(){return"Baeldung !";}}
- 启动类 禁用 Auto-Configuration
@SpringBootApplication(exclude ={SecurityAutoConfiguration.class,UserDetailsServiceAutoConfiguration.class})publicclassApiKeySecretAuthApplication{publicstaticvoidmain(String[] args){SpringApplication.run(ApiKeySecretAuthApplication.class, args);}}
- 测试
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
版权归原作者 勉之~ 所有, 如有侵权,请联系我们删除。