Spring 基于 JWT 的身份验证、验证和授权方案的安全过滤器,举例
Spring Security filters for JWT-based authentication, verification and authorization scheme, by example
Java + Spring(和 Spring 安全性),有兴趣使用不记名令牌为我的 Web 服务实现基于 JWT 的身份验证机制。我 理解 使用 Spring 身份验证和授权安全性的正确方法是通过使用提供的(或自定义)过滤器,如下所示:
- 您指定应用程序中的哪些网址经过身份验证(因此需要经过身份验证的请求才能访问)
- 这通常在扩展
WebSecurityConfigurerAdapter
的 @EnableWebSecurity
注释网络安全 class 中完成
- 对于任何未经身份验证的 URL,任何过滤器都不应阻止对所请求资源的访问
- 身份验证过滤器有效地提供了一个“登录”端点
- 请求客户端最初应点击此登录端点(身份验证过滤器)以获得可用于进行后续 API 调用的身份验证令牌
- 此过滤器应接收一种包含主体(例如用户名)和凭据(例如密码)的“登录请求”对象
- 此身份验证过滤器应使用登录请求中包含的 principal/credential 来确定它们是否代表系统中的有效用户
- 如果是这样,将生成一个身份验证令牌(JWT 等)并以某种方式在响应中发送回请求者
- 否则,如果 principal/credential 与系统中的有效用户不匹配,则返回错误响应并且身份验证失败
- 对于经过身份验证的 URL,验证过滤器 验证请求是否包含身份验证令牌以及身份验证令牌是否有效(已正确签名,包含 JWT 声明等用户信息,未过期等)
- 如果授权令牌有效,请求将继续到授权过滤器(见下文)
- 否则,如果授权令牌无效,则验证失败并且过滤器将错误响应发送回客户端
- 最后,授权过滤器 验证与有效身份验证令牌相关联的用户是否具有 ability/permission 发出此类请求的权限
- 如果他们这样做,则允许请求继续处理任何 resources/controller 编写的内容,并且 resource/controller 将响应返回给请求者
- 如果他们不这样做,则会向客户端返回错误响应
- 理想情况下,此 authz 过滤器内的逻辑(代码)可以访问添加到资源方法的权限注释,这样我就可以添加端点并在其上指定权限,而无需修改 authz 过滤器的代码
所以首先,如果我上面所说的任何内容是 Spring 安全(或一般的网络安全)反模式或被误导,请首先提供路线更正并引导我正确方向!
假设我或多或少地正确理解了上面的“授权流程”...
是否有任何特定的 Spring 安全过滤器已经为我处理了 所有 的问题,或者可以扩展并重写一些方法来执行这边走?或者任何非常接近的东西?查看特定于身份验证的 Spring 安全过滤器列表,我看到:
UsernamePasswordAuthenticationFilter
-> 看起来像是 authn 过滤器的一个不错的候选者,但期望查询字符串上有一个 username
和 password
参数,这对我来说很奇怪,最重要的是,不生成 JWT
CasAuthenticationFilter
-> 看起来像是用于基于 CAS 的 SSO,不适合在非 SSO 环境中使用
BasicAuthenticationFilter
-> 用于基于 HTTP 基本身份验证的身份验证,不适合更复杂的设置
至于令牌验证和授权,我(令我惊讶的是)在 Spring 安全环境中没有看到 任何东西。
除非有人知道我可以使用或子class 轻松使用的特定于 JWT 的过滤器,否则我想我需要实现自己的自定义过滤器,在这种情况下我想知道如何配置 Spring 安全使用它们而不使用任何这些其他身份验证过滤器(例如 UsernamePasswordAuthenticationFilter
)作为过滤器链的一部分。
据我了解,您想:
- 通过用户名和密码对用户进行身份验证并使用 JWT 响应
- 在后续请求中,使用该 JWT 对用户进行身份验证
username/password -> JWT
本身并不是一个既定的身份验证机制,这就是为什么 Spring 安全性还没有直接支持的原因。
不过你可以得到它 on your own pretty easily。
首先,创建一个生成 JWT 的 /token
端点:
@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 安全性以允许 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
样板文件,如果您有兴趣贡献自己的力量!
Java + Spring(和 Spring 安全性),有兴趣使用不记名令牌为我的 Web 服务实现基于 JWT 的身份验证机制。我 理解 使用 Spring 身份验证和授权安全性的正确方法是通过使用提供的(或自定义)过滤器,如下所示:
- 您指定应用程序中的哪些网址经过身份验证(因此需要经过身份验证的请求才能访问)
- 这通常在扩展
WebSecurityConfigurerAdapter
的
@EnableWebSecurity
注释网络安全 class 中完成 - 这通常在扩展
- 对于任何未经身份验证的 URL,任何过滤器都不应阻止对所请求资源的访问
- 身份验证过滤器有效地提供了一个“登录”端点
- 请求客户端最初应点击此登录端点(身份验证过滤器)以获得可用于进行后续 API 调用的身份验证令牌
- 此过滤器应接收一种包含主体(例如用户名)和凭据(例如密码)的“登录请求”对象
- 此身份验证过滤器应使用登录请求中包含的 principal/credential 来确定它们是否代表系统中的有效用户
- 如果是这样,将生成一个身份验证令牌(JWT 等)并以某种方式在响应中发送回请求者
- 否则,如果 principal/credential 与系统中的有效用户不匹配,则返回错误响应并且身份验证失败
- 对于经过身份验证的 URL,验证过滤器 验证请求是否包含身份验证令牌以及身份验证令牌是否有效(已正确签名,包含 JWT 声明等用户信息,未过期等)
- 如果授权令牌有效,请求将继续到授权过滤器(见下文)
- 否则,如果授权令牌无效,则验证失败并且过滤器将错误响应发送回客户端
- 最后,授权过滤器 验证与有效身份验证令牌相关联的用户是否具有 ability/permission 发出此类请求的权限
- 如果他们这样做,则允许请求继续处理任何 resources/controller 编写的内容,并且 resource/controller 将响应返回给请求者
- 如果他们不这样做,则会向客户端返回错误响应
- 理想情况下,此 authz 过滤器内的逻辑(代码)可以访问添加到资源方法的权限注释,这样我就可以添加端点并在其上指定权限,而无需修改 authz 过滤器的代码
所以首先,如果我上面所说的任何内容是 Spring 安全(或一般的网络安全)反模式或被误导,请首先提供路线更正并引导我正确方向!
假设我或多或少地正确理解了上面的“授权流程”...
是否有任何特定的 Spring 安全过滤器已经为我处理了 所有 的问题,或者可以扩展并重写一些方法来执行这边走?或者任何非常接近的东西?查看特定于身份验证的 Spring 安全过滤器列表,我看到:
UsernamePasswordAuthenticationFilter
-> 看起来像是 authn 过滤器的一个不错的候选者,但期望查询字符串上有一个username
和password
参数,这对我来说很奇怪,最重要的是,不生成 JWTCasAuthenticationFilter
-> 看起来像是用于基于 CAS 的 SSO,不适合在非 SSO 环境中使用BasicAuthenticationFilter
-> 用于基于 HTTP 基本身份验证的身份验证,不适合更复杂的设置
至于令牌验证和授权,我(令我惊讶的是)在 Spring 安全环境中没有看到 任何东西。
除非有人知道我可以使用或子class 轻松使用的特定于 JWT 的过滤器,否则我想我需要实现自己的自定义过滤器,在这种情况下我想知道如何配置 Spring 安全使用它们而不使用任何这些其他身份验证过滤器(例如 UsernamePasswordAuthenticationFilter
)作为过滤器链的一部分。
据我了解,您想:
- 通过用户名和密码对用户进行身份验证并使用 JWT 响应
- 在后续请求中,使用该 JWT 对用户进行身份验证
username/password -> JWT
本身并不是一个既定的身份验证机制,这就是为什么 Spring 安全性还没有直接支持的原因。
不过你可以得到它 on your own pretty easily。
首先,创建一个生成 JWT 的 /token
端点:
@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 安全性以允许 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
样板文件,如果您有兴趣贡献自己的力量!