如何使用SpringSecurity保护你的REST(2)

「这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战

确保Web应用程序的安全是一个固有的复杂命题。Spring Security为Java开发者提供了一个强大的框架来满足这一需求,但这一强大的工具伴随着一个陡峭的学习曲线。

本文简要介绍了用Spring Security保护REST API背后的基本组件。我们将建立一个简单的应用,使用JSON Web Token(JWT)来存储用户的信息。

JWT由于其简单性和紧凑性,正在迅速成为持有授权信息的标准方法。

具有简单登录能力的前端

Spring Web默认会在resources/static文件夹中提供文件。这就是客户端以一个小的index.html文件形式存在的地方。这将使你了解到JavaScript前端是如何与服务器安全互动的。

这个简单的index.html文件允许用户点击一个按钮,看到从受保护的端点返回的信息。它还提供了一个简单的登录功能。你可以在清单1中看到处理这些交互的JS。

清单 1.受保护的API和登录调用(index.html)。

<script>
  let token = null;
  async function protectedAPI(){
    let headers = {};
    if (token) headers["Authorization"] = "Bearer " + token;
    let response = await fetch("/protected", {
      headers
    });
    let text = await response.text();
    if (response.ok){
      document.getElementById("protectedMessage").innerHTML = text;
    } else {
      document.getElementById("protectedMessage").innerHTML = "You must log in first";
    }
  }
  async function login(){
    let response = await fetch("/open/login", {
      method: 'post',
      body: 
JSON.stringify({username:document.getElementById("username").value,password:document.getElementById("password").value}),
      headers: {
        "Content-Type": "application/json"
      }
    });
    if (response.ok){
      let text = await response.text();
      token = text;
      document.getElementById("login-msg").innerHTML = "Login success";
    } else {
      document.getElementById("login-msg").innerHTML = "Failed to login: " + response.status;
    }
  }
</script>
复制代码

清单1依赖于两个API端点。/open/login/protected。它使用登录调用的结果来设置token变量的值,如果token存在,受保护的调用会在授权头中发送token。当用户访问安全端点时,服务器将使用该token来验证用户的授权。

受保护的端点MyController.java

MyController是一个简单明了的Spring Web REST映射,如清单2所示。`

清单2.MyController.java

@GetMapping({ "/protected" })
public String protectedEndpoint() {
  return "Protected Endpoint Response";
}
复制代码

请注意,在映射的路由级别上没有安全布线存在。

SecurityConfig.java

SecurityConfig.java文件是安全设置的中心。让我们从这里开始并向外移动。

该类用@configuration@EnableWebSecurity做了注解,提醒Spring注意安全问题已经激活,该类将对其应用设置。

大部分的工作是在列表4中看到的configure()方法中完成的。

清单3.SecurityConfig.configure()

private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
    new AntPathRequestMatcher("/"), new AntPathRequestMatcher("/open/**")
  );
  TokenAuthenticationProvider provider;
  private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
  protected void configure(final HttpSecurity http) throws Exception {
    http
      .sessionManagement()
      .sessionCreationPolicy(STATELESS)
      .and()
      .exceptionHandling()
      .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
      .and()
      .authenticationProvider(provider)
      .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
      .authorizeRequests()
      .requestMatchers(PROTECTED_URLS)
      .authenticated()
      .and()
      .csrf().disable()
      .formLogin().disable()
      .httpBasic().disable()
      .logout().disable();
  }
  TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
    final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(successHandler());
    return filter;
  }
复制代码

对清单3的一些评论。配置方法使用Ant模式匹配器(PROTECTED_URLS)来允许对静态目录("/")和("/open/")路径之后的任何请求通过,而不需要进行认证检查。这意味着你仍然可以点击/static/index.html文件,而登录端点可以托管在/open/login

请注意,该配置还添加了提供者,即TokenAuthenticationProvider,以及过滤器,由TokenAuthenticationFilter处理。注意过滤器在AnonymousAuthenticationFilter之前,它是Spring Security的一部分。

感谢观看,如果您有兴趣,可以关注一下我,方便查看后续文章,一起学习,共同进步,不胜感激!