如何使用 Spring Security 实现具有两级令牌认证的 Spring Boot 微服务?
Posted
技术标签:
【中文标题】如何使用 Spring Security 实现具有两级令牌认证的 Spring Boot 微服务?【英文标题】:How to implement Spring Boot Microservice with two levels of token authentication using Spring Security? 【发布时间】:2021-02-07 05:28:40 【问题描述】:团队您好,
我正在开发一个 Spring Boot 项目,我想在其中使用 Spring Security + JWT 设置两个级别的令牌身份验证。
我的一些同事已经使用 Dropwizard 框架构建了一个类似的应用程序。
我想在我的 Spring Boot 项目中实现相同的架构。我在这个问题的末尾添加了指向 API 架构的链接。
我可以使用 Spring Boot 设置第一级令牌认证(使用 Spring Security + JWT),但我无法找到设置第二级令牌认证的正确方法。
我尝试搜索相关文章,但找不到。
如果您可以共享代码 sn-p 在 Spring Boot(使用 Spring Security)中实现两个级别的令牌身份验证以更好地理解,将会很有帮助。
谢谢你的期待!
【问题讨论】:
实施任何类型的自定义安全流程都是不好的做法。有许多实施不当的流程导致安全漏洞的例子。您应该遵循任何标准的 oauth2 流程,或者使用 spring 提供的当前现有的已通过社区审查和测试的身份验证方法。 【参考方案1】:以下是实现您需要的概念证明。第一类是安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter
public static final List<String> WHITE_LIST = asList("/authenticate");
public static final List<String> TOKEN1_LIST = asList("/get-access-token");
public static final List<String> TOKEN2_LIST = asList("/add-new-user");
@Autowired
private Token1Filter token1Filter;
@Autowired
private Token2Filter token2Filter;
@Override
protected void configure(HttpSecurity http) throws Exception
http.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
// Make sure we use stateless session; session won't be used to store user's state
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
// List of services do not require authentication
.antMatchers(OPTIONS).permitAll()
.antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
.and()
.addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
如您所见,不同的 Url 包含在不同的属性中,并且与它们相关的每个 List
都在 HttpSecurity
类中配置。用于管理每个子集的过滤器,我的意思是,使用 Jwt 1 和 Jwt 2 保护的 Urls 如下:
@AllArgsConstructor
@Component
public class Token1Filter extends OncePerRequestFilter
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException
getJwt(request)
.ifPresent(jwt1 ->
/**
* Here you can use functionality to check provided Jwt token 1,
* adding included data into Spring SecurityContextHolder.
*/
// Used for testing purpose
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken1", null, new ArrayList<>())
);
);
filterChain.doFilter(request, response);
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
private Optional<String> getJwt(HttpServletRequest request)
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
@AllArgsConstructor
@Component
public class Token2Filter extends OncePerRequestFilter
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException
getJwt(request)
.ifPresent(jwt2 ->
/**
* Here you can use functionality to check provided Jwt token 2,
* adding included data into Spring SecurityContextHolder.
*/
// Used for testing purpose
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken2", null,
asList(new SimpleGrantedAuthority("ADMIN")))
);
);
filterChain.doFilter(request, response);
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
private Optional<String> getJwt(HttpServletRequest request)
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
每个都包含shouldNotFilter
方法的实现,以了解它是否必须管理当前请求。并包含对您需要添加功能以提取和验证提供的 Jwt 令牌的位置的注释。
最后,只有一个虚拟控制器来测试每个用例:
@AllArgsConstructor
@RestController
public class TestController
@GetMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestParam String username, @RequestParam String password)
return new ResponseEntity("[authenticate] Testing purpose", OK);
@GetMapping("/get-access-token")
public ResponseEntity<String> getAccessToken()
return new ResponseEntity("[get-access-token] Testing purpose", OK);
@GetMapping("/add-new-user")
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<String> addNewUser()
return new ResponseEntity("[add-new-user] Testing purpose", OK);
可以添加一些改进,但这是一个很好的初始点,可以按预期工作。
【讨论】:
如果您提供的令牌不以Bearer
为前缀,但其他任何内容都将通过您的小验证。实施自定义安全过滤器是不好的做法,并且有很多实施不良的自定义弹簧安全过滤器的例子。
另一方面,有几件事情需要考虑:1. Bearer
前缀是一个标准,所以在这里它是有意义的。 2.由于“白名单”中未包含的Url已配置为authenticated()
,您的确认anything else it will pass your little validation
不正确。 3. 在示例中,SecurityContextHolder
中包含的数据仅用作测试目的,如果您阅读上述评论,我会向他解释如何管理每个用例。 4. 如果您需要自定义安全方法但 Spring 本身不提供,则解决方案不能是 don't do it
不应该用概念证明来回答不好的做法。应该回答这是不好的做法。如果您需要自定义安全性,那么首先有人做错了什么。有一些标准是应该遵循的,我们应该教导这些标准。任何其他都是错误的答案。我们不应该回答有不良做法的人。
创建自定义过滤器是框架提供的一个工具,也是一种广泛使用的技术。您仍然坚持称它为bad practice
,好的,请随时与 Spring 团队讨论它。我打电话给proof of concept
,因为我不知道如何处理他的Jwt令牌的细节,所以我不能“填补空白”。出于这个原因,我将 cmets 包含在应该管理这些 cmets 的代码中,并将所需的内容添加到 Spring Authorization 内容中。
@dotore 提供的解决方案完美运行。考虑到最佳实践,这是一个已经在客户现有项目中实现的架构,所以我别无选择,只能遵循相同的架构。但是,我已经按照 Thomas Andolf 分享的文章中的建议进行了必要的更改,以使用 JJWT 库进行令牌解析。感谢医生和 Thomas Andolf 的帮助。以上是关于如何使用 Spring Security 实现具有两级令牌认证的 Spring Boot 微服务?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Security 中的子域之间共享会话
如何使用 Spring Security 处理基于用户权限的授权?
具有 Spring Security 和 Java Config 的自定义身份验证提供程序
具有特定表角色的spring security UserDetailsService
我们如何使用带有 Spring 5.0 的最新 spring-security-oauth2 jar 来实现授权服务器?