如何使用Spring Security实现具有两级令牌身份验证的Spring Boot微服务?

huangapple go评论110阅读模式
英文:

How to implement Spring Boot Microservice with two levels of token authentication using Spring Security?

问题

你好团队,

我正在一个Spring Boot项目中工作,我想要使用Spring Security + JWT来设置两个级别的令牌身份验证。

我的一些同事已经使用Dropwizard框架构建了一个类似的应用程序。

我想在我的Spring Boot项目中实现相同的架构。我在问题末尾添加了API架构的链接。

我已经能够通过Spring Boot(使用Spring Security + JWT)设置第一个级别的令牌身份验证,但我找不到设置第二个级别的令牌身份验证的正确方法。

我尝试搜索相关文章,但找不到任何信息。

如果您能够分享一个代码片段,以在Spring Boot中实现两个级别的令牌身份验证(使用Spring Security),以便更好地理解,将会很有帮助。

提前感谢您的帮助!

如何使用Spring Security实现具有两级令牌身份验证的Spring Boot微服务?

英文:

Hello Team,

I am working on a Spring Boot project in which I want to setup two levels of token authentication using Spring Security + JWT.

A similar application has already been built by some of my colleagues using Dropwizard framework.

I want to implement the same architecture in my Spring Boot project. I have added link to the architecture of the API at the end of this question.

I am able to setup the first level of token authentication with Spring Boot (using Spring Security + JWT), but I am unable to find the correct way of setting up second level of token authentication.

I tried searching out for related articles but couldn't find any.

It would be helpful if you could share a code snippet implementing both levels of token authentication in Spring Boot (using Spring Security) for better understanding.

Thanking you in anticipation!

如何使用Spring Security实现具有两级令牌身份验证的Spring Boot微服务?

答案1

得分: -1

以下是一个概念验证,以实现您需要的内容。第一个类是安全配置:

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  5. public static final List<String> WHITE_LIST = asList("/authenticate");
  6. public static final List<String> TOKEN1_LIST = asList("/get-access-token");
  7. public static final List<String> TOKEN2_LIST = asList("/add-new-user");
  8. @Autowired
  9. private Token1Filter token1Filter;
  10. @Autowired
  11. private Token2Filter token2Filter;
  12. @Override
  13. protected void configure(HttpSecurity http) throws Exception {
  14. http.csrf().disable()
  15. .formLogin().disable()
  16. .httpBasic().disable()
  17. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  18. .and()
  19. .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
  20. .and()
  21. .authorizeRequests()
  22. .antMatchers(OPTIONS).permitAll()
  23. .antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
  24. .anyRequest().authenticated()
  25. .and()
  26. .addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
  27. .addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
  28. }
  29. }

不同的URL包含在不同的属性中,与它们相关的每个List都在HttpSecurity类中进行了配置。用于管理每个子集的过滤器,即使用Jwt 1和Jwt 2保护的URL如下:

  1. @AllArgsConstructor
  2. @Component
  3. public class Token1Filter extends OncePerRequestFilter {
  4. private static final String TOKEN_PREFIX = "Bearer ";
  5. @Override
  6. protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
  7. FilterChain filterChain) throws ServletException, IOException {
  8. getJwt(request)
  9. .ifPresent(jwt1 -> {
  10. /**
  11. * 在这里,您可以使用功能来检查提供的Jwt令牌1,
  12. * 将包含的数据添加到Spring SecurityContextHolder中。
  13. */
  14. // 用于测试目的
  15. SecurityContextHolder.getContext().setAuthentication(
  16. new UsernamePasswordAuthenticationToken("testUserToken1", null, new ArrayList<>())
  17. );
  18. });
  19. filterChain.doFilter(request, response);
  20. }
  21. @Override
  22. protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
  23. return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
  24. }
  25. private Optional<String> getJwt(HttpServletRequest request) {
  26. return ofNullable(request)
  27. .map(r -> r.getHeader(AUTHORIZATION))
  28. .filter(Predicate.not(String::isEmpty))
  29. .map(t -> t.replace(TOKEN_PREFIX, ""))
  30. .filter(Predicate.not(String::isEmpty));
  31. }
  32. }
  33. @AllArgsConstructor
  34. @Component
  35. public class Token2Filter extends OncePerRequestFilter {
  36. private static final String TOKEN_PREFIX = "Bearer ";
  37. @Override
  38. protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
  39. FilterChain filterChain) throws ServletException, IOException {
  40. getJwt(request)
  41. .ifPresent(jwt2 -> {
  42. /**
  43. * 在这里,您可以使用功能来检查提供的Jwt令牌2,
  44. * 将包含的数据添加到Spring SecurityContextHolder中。
  45. */
  46. // 用于测试目的
  47. SecurityContextHolder.getContext().setAuthentication(
  48. new UsernamePasswordAuthenticationToken("testUserToken2", null,
  49. asList(new SimpleGrantedAuthority("ADMIN")))
  50. );
  51. });
  52. filterChain.doFilter(request, response);
  53. }
  54. @Override
  55. protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
  56. return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
  57. }
  58. private Optional<String> getJwt(HttpServletRequest request) {
  59. return ofNullable(request)
  60. .map(r -> r.getHeader(AUTHORIZATION))
  61. .filter(Predicate.not(String::isEmpty))
  62. .map(t -> t.replace(TOKEN_PREFIX, ""))
  63. .filter(Predicate.not(String::isEmpty));
  64. }
  65. }

每个过滤器都包含shouldNotFilter方法的实现,以确定是否需要处理当前请求。并且包含了在提取和验证提供的Jwt令牌时需要添加功能的位置的注释。

最后,只有一个虚拟控制器来测试每个用例:

  1. @AllArgsConstructor
  2. @RestController
  3. public class TestController {
  4. @GetMapping("/authenticate")
  5. public ResponseEntity<String> authenticate(@RequestParam String username, @RequestParam String password) {
  6. return new ResponseEntity("[authenticate] Testing purpose", OK);
  7. }
  8. @GetMapping("/get-access-token")
  9. public ResponseEntity<String> getAccessToken() {
  10. return new ResponseEntity("[get-access-token] Testing purpose", OK);
  11. }
  12. @GetMapping("/add-new-user")
  13. @PreAuthorize("hasAuthority('ADMIN')")
  14. public ResponseEntity<String> addNewUser() {
  15. return new ResponseEntity("[add-new-user] Testing purpose", OK);
  16. }
  17. }

可以添加一些改进,但这是一个很好的起始点,可以正常工作。

英文:

The following is a proof of concept to achieve what you need. The first class is the security configuration:

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  5. public static final List&lt;String&gt; WHITE_LIST = asList(&quot;/authenticate&quot;);
  6. public static final List&lt;String&gt; TOKEN1_LIST = asList(&quot;/get-access-token&quot;);
  7. public static final List&lt;String&gt; TOKEN2_LIST = asList(&quot;/add-new-user&quot;);
  8. @Autowired
  9. private Token1Filter token1Filter;
  10. @Autowired
  11. private Token2Filter token2Filter;
  12. @Override
  13. protected void configure(HttpSecurity http) throws Exception {
  14. http.csrf().disable()
  15. .formLogin().disable()
  16. .httpBasic().disable()
  17. // Make sure we use stateless session; session won&#39;t be used to store user&#39;s state
  18. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  19. .and()
  20. // Handle an authorized attempts
  21. .exceptionHandling().authenticationEntryPoint((req, rsp, e) -&gt; rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
  22. .and()
  23. .authorizeRequests()
  24. // List of services do not require authentication
  25. .antMatchers(OPTIONS).permitAll()
  26. .antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
  27. // Any other request must be authenticated
  28. .anyRequest().authenticated()
  29. .and()
  30. .addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
  31. .addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
  32. }
  33. }

As you can see, the different Urls are included in different properties and every List related with them is configured in HttpSecurity class. The filters used to manage every subset, I mean, Urls securized using Jwt 1 and Jwt 2 are the following ones:

  1. @AllArgsConstructor
  2. @Component
  3. public class Token1Filter extends OncePerRequestFilter {
  4. private static final String TOKEN_PREFIX = &quot;Bearer &quot;;
  5. @Override
  6. protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
  7. FilterChain filterChain) throws ServletException, IOException {
  8. getJwt(request)
  9. .ifPresent(jwt1 -&gt; {
  10. /**
  11. * Here you can use functionality to check provided Jwt token 1,
  12. * adding included data into Spring SecurityContextHolder.
  13. */
  14. // Used for testing purpose
  15. SecurityContextHolder.getContext().setAuthentication(
  16. new UsernamePasswordAuthenticationToken(&quot;testUserToken1&quot;, null, new ArrayList&lt;&gt;())
  17. );
  18. });
  19. filterChain.doFilter(request, response);
  20. }
  21. @Override
  22. protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
  23. return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
  24. }
  25. private Optional&lt;String&gt; getJwt(HttpServletRequest request) {
  26. return ofNullable(request)
  27. .map(r -&gt; r.getHeader(AUTHORIZATION))
  28. .filter(Predicate.not(String::isEmpty))
  29. .map(t -&gt; t.replace(TOKEN_PREFIX, &quot;&quot;))
  30. .filter(Predicate.not(String::isEmpty));
  31. }
  32. }
  33. @AllArgsConstructor
  34. @Component
  35. public class Token2Filter extends OncePerRequestFilter {
  36. private static final String TOKEN_PREFIX = &quot;Bearer &quot;;
  37. @Override
  38. protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
  39. FilterChain filterChain) throws ServletException, IOException {
  40. getJwt(request)
  41. .ifPresent(jwt2 -&gt; {
  42. /**
  43. * Here you can use functionality to check provided Jwt token 2,
  44. * adding included data into Spring SecurityContextHolder.
  45. */
  46. // Used for testing purpose
  47. SecurityContextHolder.getContext().setAuthentication(
  48. new UsernamePasswordAuthenticationToken(&quot;testUserToken2&quot;, null,
  49. asList(new SimpleGrantedAuthority(&quot;ADMIN&quot;)))
  50. );
  51. });
  52. filterChain.doFilter(request, response);
  53. }
  54. @Override
  55. protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
  56. return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
  57. }
  58. private Optional&lt;String&gt; getJwt(HttpServletRequest request) {
  59. return ofNullable(request)
  60. .map(r -&gt; r.getHeader(AUTHORIZATION))
  61. .filter(Predicate.not(String::isEmpty))
  62. .map(t -&gt; t.replace(TOKEN_PREFIX, &quot;&quot;))
  63. .filter(Predicate.not(String::isEmpty));
  64. }
  65. }

Every one include an implementation of shouldNotFilter method to know if it have to manage the current request. And contains a comment on location on which you need to add the functionality to extract and verify the provided Jwt token.

And finally, only a dummy controller to test every use case:

  1. @AllArgsConstructor
  2. @RestController
  3. public class TestController {
  4. @GetMapping(&quot;/authenticate&quot;)
  5. public ResponseEntity&lt;String&gt; authenticate(@RequestParam String username, @RequestParam String password) {
  6. return new ResponseEntity(&quot;[authenticate] Testing purpose&quot;, OK);
  7. }
  8. @GetMapping(&quot;/get-access-token&quot;)
  9. public ResponseEntity&lt;String&gt; getAccessToken() {
  10. return new ResponseEntity(&quot;[get-access-token] Testing purpose&quot;, OK);
  11. }
  12. @GetMapping(&quot;/add-new-user&quot;)
  13. @PreAuthorize(&quot;hasAuthority(&#39;ADMIN&#39;)&quot;)
  14. public ResponseEntity&lt;String&gt; addNewUser() {
  15. return new ResponseEntity(&quot;[add-new-user] Testing purpose&quot;, OK);
  16. }
  17. }

Several improvements could be added but this is a good initial point that works as expected.

huangapple
  • 本文由 发表于 2020年10月24日 17:44:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/64512022.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定