java - 用于基于 JWT 的身份验证、验证和授权方案的 Spring Security 过滤器,例如
问题描述
Java + Spring(和 Spring Security)在这里,有兴趣使用不记名令牌为我的 Web 服务实现基于 JWT 的身份验证机制。我对使用 Spring Security 进行身份验证和授权的正确方法的理解是通过使用提供的(或自定义)过滤器,如下所示:
- 您指定应用程序中的哪些 URL 已通过身份验证(因此需要经过身份验证的请求才能访问)
- 这通常在一个带
@EnableWebSecurity
注释的 web 安全类中完成,该类扩展WebSecurityConfigurerAdapter
- 这通常在一个带
- 对于任何未经身份验证的 URL,任何过滤器都不应阻止对所请求资源的访问
- 身份验证过滤器有效地提供“登录”端点
- 请求客户端最初应点击此登录端点(身份验证过滤器)以获得可用于进行后续 API 调用的身份验证令牌
- 此过滤器应接收包含主体(例如用户名)和凭据(例如密码)的“登录请求”对象类型
- 此身份验证过滤器应使用登录请求中包含的主体/凭据来确定它们是否代表系统中的有效用户
- 如果是这样,则会生成一个身份验证令牌(JWT 等)并以某种方式在响应中发送回请求者
- 否则,如果主体/凭据与系统中的有效用户不匹配,则返回错误响应并且身份验证失败
- 对于经过身份验证的 URL,验证过滤器会验证请求是否包含身份验证令牌以及身份验证令牌是否有效(已正确签名、包含 JWT 声明等用户信息、未过期等)
- 如果身份验证令牌有效,则请求继续到授权过滤器(见下文)
- 否则,如果身份验证令牌无效,则验证失败并且过滤器将错误响应发送回客户端
- 最后,授权过滤器验证与有效身份验证令牌关联的用户是否有能力/权限来发出此类请求
- 如果他们这样做,那么请求被允许继续到任何资源/控制器被写入来处理它,并且该资源/控制器将响应提供回请求者
- 如果他们不这样做,则会向客户端返回错误响应
- 理想情况下,此 authz 过滤器中的逻辑(代码)将有权访问添加到资源方法的权限注释,这样我就可以添加端点并为其指定权限,而无需修改 authz 过滤器的代码
因此,首先,如果我上面所说的任何内容是 Spring Security(或一般的 Web 安全)反模式或被误导,请首先提供课程纠正并引导我朝着正确的方向前进!
假设我或多或少正确理解了上面的“身份验证流程”......
是否有任何特定的 Spring Security 过滤器已经为我处理了所有这些问题,或者可以扩展并覆盖一些方法以实现这种行为?或者任何非常接近的东西?查看特定于身份验证的 Spring Security 过滤器列表,我看到:
UsernamePasswordAuthenticationFilter
-> 看起来像是 authn 过滤器的合适候选者,但期望查询字符串上有一个username
andpassword
参数,这对我来说很奇怪,最重要的是,它不会生成 JWTCasAuthenticationFilter
-> 看起来像是用于基于 CAS 的 SSO,不适合在非 SSO 上下文中使用BasicAuthenticationFilter
-> 用于基于 HTTP 基本身份验证的身份验证,不适用于更复杂的设置
至于令牌验证和授权,我(令我惊讶的是)在 Spring Security 领域看不到任何符合条件的东西。
除非有人知道我可以轻松使用或子类化的特定于 JWT 的过滤器,否则我认为我需要实现自己的自定义过滤器,在这种情况下,我想知道如何配置 Spring Security 以使用它们而不使用任何其他身份验证过滤器(例如UsernamePasswordAuthenticationFilter
)作为过滤器链的一部分。
解决方案
据我了解,您想要:
- 通过用户名和密码对用户进行身份验证并使用 JWT 进行响应
- 在后续请求中,使用该 JWT 对用户进行身份验证
username/password -> JWT
它本身并不是一个既定的身份验证机制,这就是 Spring Security 还没有直接支持的原因。
不过,您可以很容易地自己获得它。
首先,创建一个/token
生成 JWT 的端点:
@RestController
public class TokenController {
@Value("${jwt.private.key}")
RSAPrivateKey key;
@PostMapping("/token")
public String token(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
// @formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// @formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
}
SignedJWT sign(SignedJWT jwt) {
try {
jwt.sign(new RSASSASigner(this.key));
return jwt;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
其次,配置 Spring Security 以允许 HTTP Basic(用于/token
端点)和 JWT(用于其余部分):
@Configuration
public class RestConfig extends WebSecurityConfigurerAdapter {
@Value("${jwt.public.key}")
RSAPublicKey key;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// @formatter:on
}
@Bean
UserDetailsService users() {
// @formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.authorities("app")
.build());
// @formatter:on
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
}
如果您有兴趣贡献自己的努力,我认为有兴趣添加对此类内容的支持spring-authorization-server
以减少样板文件!/token
推荐阅读
- computer-science - 关键节点,因此一个节点和一组节点之间没有路径
- web - 从 Progressive Web APP 捕获桌面屏幕
- reactjs - 如何将默认值传递给 redux 表单
- linux - 仅查找/列出目录中递归的文件类型
- jira - 当我离开组织时,我在个人空间中创建的 Confluence 内容会被删除吗?
- webots - 尝试将 ImageTexture 添加到 IndexedFaceSet 的形状中,如何控制 ImageTexture 的外观?
- file - 随机文件顺序
- bash - 双参数替换以获取变量值
- git - 如何在 GitHub 流程中合并和标记先前版本的错误修复
- unity3d - 如何将unity项目上传到github