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

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

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

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

@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()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
            .authorizeRequests()
            .antMatchers(OPTIONS).permitAll()
            .antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
  }
}

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

@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 -> {
                /**
                 *    在这里,您可以使用功能来检查提供的Jwt令牌1,
                 * 将包含的数据添加到Spring SecurityContextHolder中。
                 */

                // 用于测试目的
                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 -> {
                /**
                 *    在这里,您可以使用功能来检查提供的Jwt令牌2,
                 * 将包含的数据添加到Spring SecurityContextHolder中。
                 */

                // 用于测试目的
                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);
  }
}

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

英文:

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

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final List&lt;String&gt; WHITE_LIST = asList(&quot;/authenticate&quot;);
public static final List&lt;String&gt; TOKEN1_LIST = asList(&quot;/get-access-token&quot;);
public static final List&lt;String&gt; TOKEN2_LIST = asList(&quot;/add-new-user&quot;);
@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&#39;t be used to store user&#39;s state
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -&gt; 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);
}
}

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:

@AllArgsConstructor
@Component
public class Token1Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = &quot;Bearer &quot;;
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt1 -&gt; {
/**
*    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(&quot;testUserToken1&quot;, null, new ArrayList&lt;&gt;())
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
}
private Optional&lt;String&gt; getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -&gt; r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -&gt; t.replace(TOKEN_PREFIX, &quot;&quot;))
.filter(Predicate.not(String::isEmpty));
}
}
@AllArgsConstructor
@Component
public class Token2Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = &quot;Bearer &quot;;
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt2 -&gt; {
/**
*    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(&quot;testUserToken2&quot;, null,
asList(new SimpleGrantedAuthority(&quot;ADMIN&quot;)))
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
}
private Optional&lt;String&gt; getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -&gt; r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -&gt; t.replace(TOKEN_PREFIX, &quot;&quot;))
.filter(Predicate.not(String::isEmpty));
}
}

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:

@AllArgsConstructor
@RestController
public class TestController {
@GetMapping(&quot;/authenticate&quot;)
public ResponseEntity&lt;String&gt; authenticate(@RequestParam String username, @RequestParam String password) {
return new ResponseEntity(&quot;[authenticate] Testing purpose&quot;, OK);
}
@GetMapping(&quot;/get-access-token&quot;)
public ResponseEntity&lt;String&gt; getAccessToken() {
return new ResponseEntity(&quot;[get-access-token] Testing purpose&quot;, OK);
}
@GetMapping(&quot;/add-new-user&quot;)
@PreAuthorize(&quot;hasAuthority(&#39;ADMIN&#39;)&quot;)
public ResponseEntity&lt;String&gt; addNewUser() {
return new ResponseEntity(&quot;[add-new-user] Testing purpose&quot;, OK);
}
}

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:

确定