HttpBasic 认证有必定的局限性与安全隐患,所以在实际项目中使用并很少,可是,有的时候为了测试方便,开启 HttpBasic 认证能方便不少。前端
所以松哥今天仍是来和你们简单聊一聊 Spring Security 中的 HttpBasic 认证。java
本文是 Spring Security 系列第 29 篇,阅读前面文章有助于更好理解本文:算法
Http Basic 认证是 Web 服务器和客户端之间进行认证的一种方式,最初是在 HTTP1.0 规范(RFC 1945)中定义,后续的有关安全的信息能够在 HTTP 1.1 规范(RFC 2616)和 HTTP 认证规范(RFC 2617)中找到。spring
HttpBasic 最大的优点在于使用很是简单,没有复杂的页面交互,只须要在请求头中携带相应的信息就能够认证成功,并且它是一种无状态登陆,也就是 session 中并不会记录用户的登陆信息。数据库
HttpBasic 最大的问题在于安全性,由于用户名/密码只是简单的经过 Base64 编码以后就开始传送了,很容易被工具嗅探到,进而暴露用户信息。后端
Spring Security 中既支持基本的 HttpBasic 认证,也支持 Http 摘要认证,Http 摘要认证是在 HttpBasic 认证的基础上,提升了信息安全管理,可是代码复杂度也提升了很多,因此 Http 摘要认证使用并很少。跨域
这里,松哥将和你们分享 Spring Security 中的这两种认证方式。浏览器
咱们先来看实现,再来分析它的认证流程。安全
首先建立一个 Spring Boot 项目,引入 Web 和 Spring Security 依赖,以下:服务器
接下来建立一个测试接口:
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }
再开启 HttpBasic 认证:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } }
最后再在 application.properties 中配置基本的用户信息,以下:
spring.security.user.password=123 spring.security.user.name=javaboy
配置完成后,启动项目,访问 /hello
接口,此时浏览器中会有弹出框,让咱们输入用户名/密码信息:
此时咱们查看请求响应头,以下:
能够看到,浏览器响应了 401,同时还携带了一个 WWW-Authenticate 响应头,这个是用来描述认证形式的,若是咱们使用的是 HttpBasic 认证,默认响应头格式如图所示。
接下来咱们输入用户名密码,点击 Sign In 进行登陆,登陆成功后,就能够成功访问到 /hello
接口了。
咱们查看第二次的请求,以下:
你们能够看到,在请求头中,多了一个 Authorization 字段,该字段的值为 Basic amF2YWJveToxMjM=
,
amF2YWJveToxMjM=
是一个通过 Base64 编码以后的字符串,咱们将该字符串解码以后发现,结果以下:
String x = new String(Base64.getDecoder().decode("amF2YWJveToxMjM="), "UTF-8");
解码结果以下:
能够看到,这就是咱们的用户名密码信息。用户名/密码只是通过简单的 Base64 编码以后就开始传递了,因此说,这种认证方式比较危险⚠️。
咱们再来稍微总结一下 HttpBasic 认证的流程:
/hello
接口。大体的流程就是这样。
Http 摘要认证与 HttpBasic 认证基本兼容,可是要复杂不少,这个复杂不只体如今代码上,也体如今请求过程当中。
Http 摘要认证最重要的改进是他不会在网络上发送明文密码。它的整个认证流程是这样的:
/hello
接口。同时,服务端返回的字段还有一个 qop,表示保护级别,auth 表示只进行身份验证;auth-int 表示还要校验内容。
nonce 是服务端生成的随机字符串,这是一个通过 Base64 编码的字符串,通过解码咱们发现,它是由过时时间和密钥组成的。在之后的请求中 nonce 会原封不动的再发回给服务端。
能够看到,客户端发送到服务端的数据比较多。
这就是整个流程。
一言以蔽之,本来的用户密码被摘要信息代替了,为了安全,摘要信息会根据服务端返回的随机字符串而发生变化,服务端根据用户密码,一样算出密码的摘要信息,再和客户端传来的摘要信息进行对比,没问题的话,用户就算认证成功了。固然,在此基础上还加了一些过时限制、重放攻击防范机制等。
好了,那这个在 Spring Security 代码中该怎么实现呢?
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf() .disable() .exceptionHandling() .authenticationEntryPoint(digestAuthenticationEntryPoint()) .and() .addFilter(digestAuthenticationFilter()); } @Bean DigestAuthenticationEntryPoint digestAuthenticationEntryPoint() { DigestAuthenticationEntryPoint entryPoint = new DigestAuthenticationEntryPoint(); entryPoint.setKey("javaboy"); entryPoint.setRealmName("myrealm"); entryPoint.setNonceValiditySeconds(1000); return entryPoint; } @Bean DigestAuthenticationFilter digestAuthenticationFilter() { DigestAuthenticationFilter filter = new DigestAuthenticationFilter(); filter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint()); filter.setUserDetailsService(userDetailsService()); return filter; } @Override @Bean protected UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("javaboy").password("123").roles("admin").build()); return manager; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
配置无非就是两方面,一方面是服务端随机字符串的生成,另外一方面就是客户端摘要信息的校验。
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { HttpServletResponse httpResponse = response; long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000); String signatureValue = DigestAuthUtils.md5Hex(expiryTime + ":" + key); String nonceValue = expiryTime + ":" + signatureValue; String nonceValueBase64 = new String(Base64.getEncoder().encode(nonceValue.getBytes())); String authenticateHeader = "Digest realm=\"" + realmName + "\", " + "qop=\"auth\", nonce=\"" + nonceValueBase64 + "\""; if (authException instanceof NonceExpiredException) { authenticateHeader = authenticateHeader + ", stale=\"true\""; } if (logger.isDebugEnabled()) { logger.debug("WWW-Authenticate header sent to user agent: " + authenticateHeader); } httpResponse.addHeader("WWW-Authenticate", authenticateHeader); httpResponse.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); }
在这段代码中,首先获取到过时时间,而后给过时时间和 key 一块儿计算出消息摘要,再将 nonce 和消息摘要共同做为 value,计算出一个 Base64 编码字符,再将该编码字符写回到前端。
配置完成后,重启服务端进行测试。
测试效果其实和 HttpBasic 认证是同样的,全部的变化,只是背后的实现有所变化而已,用户体验是同样的。
Http 摘要认证的效果虽然比 HttpBasic 安全,可是其实你们看到,整个流程下来解决的安全问题其实仍是很是有限。并且代码也麻烦了不少,所以这种认证方式并未普遍流行开来。
Http 认证小伙伴们做为一个了解便可,里边的有一些思想仍是挺有意思的,能够激发咱们解决其余问题的思路,例如对于重放攻击的的解决办法,咱们若是想本身防护重放攻击,就能够参考这里的实现思路。
好啦,小伙伴们若是有收获,记得点个在看鼓励下松哥哦~