Spring Boot 中如何实现 HTTP 认证?

.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 认证的流程:

  1. 浏览器发出请求,说要访问 /hello 接口。

  2. 服务端返回 401,表示未认证。同时在响应头中携带 WWW-Authenticate 字段来描述认证形式。

  3. 浏览器收到 401 响应之后,弹出对话框,要求用户输入用户名/密码,用户输入完用户名/密码之后,浏览器会将之进行 Base64 编码,编码完成后,发送到服务端。

  4. 服务端对浏览器传来的信息进行解码,并校验,当没问题的时候,给客户端作出响应。

大致的流程就是这样。

3.Http 摘要认证


Http 摘要认证与 HttpBasic 认证基本兼容,但是要复杂很多,这个复杂不仅体现在代码上,也体现在请求过程中。

Http 摘要认证最重要的改进是他不会在网络上发送明文密码。它的整个认证流程是这样的:

  1. 浏览器发出请求,说要访问 /hello 接口。

  2. 服务端返回 401,表示未认证,同时在响应头中携带 WWW-Authenticate 字段来描述认证形式。不同的是,这次服务端会计算出一个随机字符串,一同返回前端,这样可以防止重放攻击(所谓重放攻击就是别人嗅探到你的摘要信息,把摘要当成密码一次次发送服务端,加一个会变化的随机字符串,生成的摘要信息就会变化,就可以防止重放攻击),如下:

同时,服务端返回的字段还有一个 qop,表示保护级别,auth 表示只进行身份验证;auth-int 表示还要校验内容。

nonce 是服务端生成的随机字符串,这是一个经过 Base64 编码的字符串,经过解码我们发现,它是由过期时间和密钥组成的。在以后的请求中 nonce 会原封不动的再发回给服务端。

  1. 客户端选择一个算法,根据该算法计算出密码以及其他数据的摘要,如下:

可以看到,客户端发送到服务端的数据比较多。

  • nonce 就是服务端发来的随机字符串。

  • response 是生成的摘要信息。

  • nc 表示请求此时,可以防止重放攻击。

  • cnonce 表示客户端发送给服务端的随机字符串。

  1. 服务端根据客户端发送来的用户名,可以查询出用户密码,再根据用户密码可以计算出摘要信息,再将摘要信息和客户端发送来的摘要信息进行对比,就能确认用户身份。

这就是整个流程。

一言以蔽之,原本的用户密码被摘要信息代替了,为了安全,摘要信息会根据服务端返回的随机字符串而发生变化,服务端根据用户密码,同样算出密码的摘要信息,再和客户端传来的摘要信息进行对比,没问题的话,用户就算认证成功了。当然,在此基础上还加了一些过期限制、重放攻击防范机制等。

好了,那这个在 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();

}

}

配置无非就是两方面,一方面是服务端随机字符串的生成,另一方面就是客户端摘要信息的校验。

  1. 首先提供 DigestAuthenticationEntryPoint 的实例,配置服务端随机数生成的一写参数,例如 nonce 有效期(多长时间会变),realm 的名字,以及生成 nonce 时所需要的 key。nonce 的具体生成逻辑在 DigestAuthenticationEntryPoint#commence 方法中:

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 编码字符,再将该编码字符写回到前端。

  1. 配置 DigestAuthenticationFilter 过滤器,主要用来处理前端请求。过滤器的源码比较长,我这里就不贴出来了,一个核心的思路就是从前端拿到用户请求的摘要信息,服务端也根据一直的信息算出来一个摘要,再根据传过来的摘要信息进行比对,进而确认用户身份。

配置完成后,重启服务端进行测试。

测试效果其实和 HttpBasic 认证是一样的,所有的变化,只是背后的实现有所变化而已,用户体验是一样的。

4.小结


Http 摘要认证的效果虽然比 HttpBasic 安全,但是其实大家看到,整个流程下来解决的安全问题其实还是非常有限。而且代码也麻烦了很多,因此这种认证方式并未广泛流行开来。
Http 认证小伙伴们作为一个了解即可,里边的有一些思想还是挺有意思的,可以激发我们解决其他问题的思路,例如对于重放攻击的的解决办法,我们如果想自己防御重放攻击,就可以参考这里的实现思路。
好啦,小伙伴们如果有收获,记得点个在看鼓励下松哥哦~

Spring Boot 的拦截器主要用于增强或修改请求处理的过程,通常用于认证、日志记录、性能统计等场景。实现 Spring Boot 拦截器的基本步骤如下: 1. 配置全局拦截器:首先,在 `WebMvcConfigurer` 接口定义一个 `addInterceptors` 方法。这个接口是自定义配置 Web MVC 的地方。 ```java @Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyGlobalInterceptor()) .addPathPatterns("/**"); // 匹配所有路径 } private static class MyGlobalInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 执行拦截逻辑,如验证、记录日志等 return true; // 如果拦截成功返回true,继续处理;如果需要阻止请求则返回false } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 请求处理完后的操作 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 异常处理完成后执行 } } } ``` 在这个例子,`MyGlobalInterceptor` 类实现了 `HandlerInterceptor` 接口,`preHandle` 方法会在每次请求开始前被调用,`postHandle` 和 `afterCompletion` 则分别在处理结束前后执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值