Spring security 使用JWT和基本身份验证保护REST应用程序有意义吗?

Spring security 使用JWT和基本身份验证保护REST应用程序有意义吗?,spring-security,basic-authentication,jwt,Spring Security,Basic Authentication,Jwt,我有一个SpringREST应用程序,它最初是由基本身份验证保护的 然后,我添加了一个登录控制器,该控制器创建一个JWTJSON Web令牌,用于后续请求 我是否可以将以下代码移出登录控制器并放入安全过滤器?那么我就不再需要登录控制器了 tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail()); 或者我可以删除基本身份验证吗 将基本身份验证与JWT混合

我有一个SpringREST应用程序,它最初是由基本身份验证保护的

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

我是否可以将以下代码移出登录控制器并放入安全过滤器?那么我就不再需要登录控制器了

tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail());
或者我可以删除基本身份验证吗

将基本身份验证与JWT混合使用是一种好的设计吗


虽然一切都很好,但我对如何最好地设计这种安全性还不太清楚。

假设所有通信都使用100%TLS-无论是在登录期间还是登录后的任何时候-通过基本身份验证使用用户名/密码进行身份验证,并在exchange中接收JWT是一个有效的用例。这几乎就是OAuth 2的一个流(“密码授予”)的工作原理

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

您也不需要
X-Auth-Token
头:HTTP身份验证
Bearer
方案就是为这个确切的用例创建的:基本上,跟踪
Bearer
方案名称的任何信息位都是应该验证的“Bearer”信息。您只需设置
授权
标题:

Authorization: Bearer <JWT value here>
授权:持票人
但是,也就是说,如果您的REST客户端“不受信任”(例如启用JavaScript的浏览器),我甚至不会这样做:HTTP响应中可以通过JavaScript访问的任何值——基本上是任何头值或响应体值——都可以通过MITM XSS攻击进行嗅探和拦截

最好将JWT值存储在一个仅安全、仅http的cookie(cookie配置:setSecure(true)、setHttpOnly(true))中。这保证了浏览器将:

  • 仅通过TLS连接传输cookie,并且
  • 永远不要让cookie值对JavaScript代码可用
  • 这种方法几乎是实现最佳安全实践所需的一切。最后一件事是确保您在每个HTTP请求上都有CSRF保护,以确保向您的站点发起请求的外部域无法正常工作

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

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

    据我们所知,这是目前针对web上不受信任的JavaScript客户端提供的最佳安全性。如果你好奇的话,Stormpath也写了一篇文章


    最后,软件已经为您完成了所有这些(还有很多更酷的东西,包括额外的自动安全检查),因此您不必自己编写或更糟糕地维护它。查看表单和表单/Ajax示例,了解如何使用它。嗯

    以下是一些代码,用于备份关于如何在Spring中执行此操作的公认答案……只需扩展
    UsernamePasswordAuthenticationFilter
    并将其添加到Spring安全性中即可……这与HTTP基本身份验证+Spring安全性配合使用

    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);
    
        }
    
    }
    

    如何在后续请求中将令牌发送到服务器?(HTTP头?Cookie?)。另外,您是否正在使用TLS(SSL)?嗨,莱斯,很高兴看到您再次出现!是,令牌作为X-Auth-token头发送。我也在使用TLS。使用JWT时必须使用TLS吗?嗨,Stephane!:)如果JWT代表一个已验证的身份,是的,我会考虑TLS强制,否则它(很容易)对MITM攻击。最后的后台问题在我试图回答:你的休息客户端是JavaScript(jQuery,角度等)还是移动客户端?好点。我认为在jwt设置中不需要基本的身份验证。回答得很好,谢谢。我记得我后来在应用程序开发中添加了登录控制器,当时我了解并在应用程序中实现了JWT令牌身份验证。实际上,我不知道如何从基本的auth-Spring安全过滤器中创建令牌。阅读您的解决方案,我发现我可以也应该这样做,并在不必要时完全删除登录控制器……嗨,莱斯,我正要用使用承载前缀的标准授权头替换X-Auth-Token头。这时我偶然发现了一个答案,说我不应该使用标准标题,而应该使用自定义标题。他的观点是,这个标准头应该留给基本身份验证。“看,莫名其妙的……”斯蒂芬妮·艾伯特我给那条线索加了一个答案。
    授权
    头支持多种方案。你可以使用任何你想要的方案<代码>基本表示一种算法
    Bearer
    就是它后面的任何文本(没有算法)。其他方案(如
    摘要
    )使用不同的算法。你甚至可以发明自己的计划。要点是标题相同,但方案名称及其尾部文本值反映了确切的行为。我在上面的回答中对此@Stephaneyelbert进行了扩展,我建议您不应出于身份验证的原因使用自定义头-只需使用
    授权
    头(在您的情况下使用
    承载
    方案)。您需要为C语言使用自定义头
    <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    
    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();
    
                }
        }