Spring security 使用JWT和基本身份验证保护REST应用程序有意义吗?
我有一个SpringREST应用程序,它最初是由基本身份验证保护的 然后,我添加了一个登录控制器,该控制器创建一个JWTJSON Web令牌,用于后续请求 我是否可以将以下代码移出登录控制器并放入安全过滤器?那么我就不再需要登录控制器了Spring security 使用JWT和基本身份验证保护REST应用程序有意义吗?,spring-security,basic-authentication,jwt,Spring Security,Basic Authentication,Jwt,我有一个SpringREST应用程序,它最初是由基本身份验证保护的 然后,我添加了一个登录控制器,该控制器创建一个JWTJSON Web令牌,用于后续请求 我是否可以将以下代码移出登录控制器并放入安全过滤器?那么我就不再需要登录控制器了 tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail()); 或者我可以删除基本身份验证吗 将基本身份验证与JWT混合
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))中。这保证了浏览器将:
最后,软件已经为您完成了所有这些(还有很多更酷的东西,包括额外的自动安全检查),因此您不必自己编写或更糟糕地维护它。查看表单和表单/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();
}
}