使用JWT和Basic身份验证保护REST应用程序是否有意义?

时间:2015-03-07 18:35:12

标签: spring-security basic-authentication jwt

我有一个Spring REST应用程序,最初使用基本身份验证进行保护。

然后我添加了一个登录控制器,它创建了一个JWT JSON Web令牌,用于后续请求。

我可以将以下代码移出登录控制器并进入安全过滤器吗?然后我不再需要登录控制器了。

tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail());

或者我可以删除基本身份验证吗?

将基本身份验证与JWT混合是一个好的设计吗?

虽然一切正常,但我在这方面有点暗,以便最好地设计这种安全性。

2 个答案:

答案 0 :(得分:72)

假设所有通信都有100%TLS - 无论是在登录期间还是在登录后的所有时间 - 通过基本身份验证使用用户名/密码进行身份验证并在交换中接收JWT是一个有效的用例。这几乎确切地说,其中一个OAuth 2的流程(密码授予')是如何工作的。

这个想法是通过一个端点对最终用户进行身份验证,例如/login/token使用您想要的任何机制,响应应该包含要在所有后续请求中发回的JWT。 JWT应该是一个JWS(即加密签名的JWT),具有适当的JWT到期(exp)字段:这可以确保客户端无法操纵JWT或使其活得比它应该更长。

您也不需要X-Auth-Token标头:为这个确切的用例创建了HTTP身份验证Bearer方案:基本上是跟踪Bearer方案的任何信息名字是“持票人”。应该验证的信息。您只需设置Authorization标题:

Authorization: Bearer <JWT value here>

但是,话说回来,如果您的REST客户端不受信任且不受信任。 (例如支持JavaScript的浏览器),我甚至不会这样做:HTTP响应中可通过JavaScript访问的任何值 - 基本上任何标题值或响应正文值 - 都可以通过MITM XSS攻击进行嗅探和截获。 / p>

最好将JWT值存储在仅安全的仅限http的cookie中(cookie config:setSecure(true),setHttpOnly(true))。这可以保证浏览器:

  1. 只能通过TLS连接传输cookie,
  2. 永远不要将cookie值用于JavaScript代码。
  3. 这种方法几乎您需要为最佳实践安全做的所有事情。最后一件事是确保您对每个HTTP请求都有CSRF保护,以确保启动对您站点的请求的外部域无法正常运行。

    最简单的方法是使用随机值设置一个仅安全(但不是仅限http)的cookie,例如一个UUID。

    然后,在每次进入您的服务器的请求中,确保您自己的JavaScript代码读取cookie值并将其设置在自定义标头中,例如X-CSRF-Token并验证服务器中每个请求的值。外部域客户端无法为您的域的请求设置自定义标头,除非外部客户端通过HTTP选项请求获得授权,因此任何CSRF攻击尝试(例如在IFrame中,无论如何)都将失败。

    这是我们所知道的网络上不受信任的JavaScript客户端可用的最佳安全性。 Stormpath在these techniques写了一篇文章,如果你有点好奇的话。

    最后,Stormpath Java Servlet Plugin 已经为您完成了所有这些(以及更多很酷的内容,包括额外的自动安全检查),因此您无需写它 - 或者更糟 - 自己维护它。查看HTTP Request Authentication section和Form / Ajax示例以了解如何使用它。 HTH!

答案 1 :(得分:1)

以下是一些代码,用于备份关于如何在Spring中执行此操作的已接受答案....只需扩展UsernamePasswordAuthenticationFilter并将其添加到Spring Security ...这适用于HTTP基本身份验证+ Spring Security

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {

        this.authenticationManager = authenticationManager;

    }

    @Override

    public Authentication attemptAuthentication(HttpServletRequest req,

                                                HttpServletResponse res) throws AuthenticationException {

        try {

            ApplicationUser creds = new ObjectMapper()

                    .readValue(req.getInputStream(), ApplicationUser.class);

            return authenticationManager.authenticate(

                    new UsernamePasswordAuthenticationToken(

                            creds.getUsername(),

                            creds.getPassword(),

                            new ArrayList<>())

            );

        } catch (IOException e) {

            throw new RuntimeException(e);

        }

    }

    @Override

    protected void successfulAuthentication(HttpServletRequest req,

                                            HttpServletResponse res,

                                            FilterChain chain,

                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()

                .setSubject(((User) auth.getPrincipal()).getUsername())

                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

                .signWith(SignatureAlgorithm.HS512, SECRET)

                .compact();

        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);

    }

}

使用JWT lib。:

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

spring boot配置类

package com.vanitysoft.payit.security.web.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

    import com.vanitysoft.payit.util.SecurityConstants;

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         @Autowired
           private UserDetailsService userDetailsService;

            @Autowired
            private  BCryptPasswordEncoder bCryptPasswordEncoder;

         @Override
           protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userDetailsService)
                      .passwordEncoder(bCryptPasswordEncoder);
           }

         @Override
            protected void configure(HttpSecurity http) throws Exception {
             http.cors().and().csrf().disable()
                    .authorizeRequests()                             
                        .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
                        .antMatchers("/user/**").authenticated()
                        .and()
                        .httpBasic()
                        .and()
                        .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                        .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and()
                        .logout()
                        .permitAll();

            }
    }