我是在春季反应性环境(webflux)中关注有关JWT的tutorial。
令牌生成工作正常,但是当我将Authorization
与bearer
一起使用时,授权不起作用
这是我所做的:
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig{
@Autowired private JWTReactiveAuthenticationManager authenticationManager;
@Autowired private SecurityContextRepository securityContext;
@Bean public SecurityWebFilterChain configure(ServerHttpSecurity http){
return http.exceptionHandling()
.authenticationEntryPoint((swe , e) -> {
return Mono.fromRunnable(()->{
System.out.println( "authenticationEntryPoint user trying to access unauthorized api end points : "+
swe.getRequest().getRemoteAddress()+
" in "+swe.getRequest().getPath());
swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
});
}).accessDeniedHandler((swe, e) -> {
return Mono.fromRunnable(()->{
System.out.println( "accessDeniedHandler user trying to access unauthorized api end points : "+
swe.getPrincipal().block().getName()+
" in "+swe.getRequest().getPath());
swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
});
})
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContext)
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.pathMatchers("/auth/login").permitAll()
.anyExchange().authenticated()
.and()
.build();
}
如您所见,我只想拒绝除登录或基于选项的请求以外的所有未授权请求。
登录正常,我正在获得令牌。
但是尝试注销(由于我只是在学习,所以我实现了自我调整,使其处于全状态)。
这是我的注销控制器:
@RestController
@RequestMapping(AuthController.AUTH)
public class AuthController {
static final String AUTH = "/auth";
@Autowired
private AuthenticationService authService;
@PostMapping("/login")
public Mono<ResponseEntity<?>> login(@RequestBody AuthRequestParam arp) {
String username = arp.getUsername();
String password = arp.getPassword();
return authService.authenticate(username, password);
}
@PostMapping("/logout")
public Mono<ResponseEntity<?>> logout(@RequestBody LogoutRequestParam lrp) {
String token = lrp.getToken();
return authService.logout(token);
}
}
注销请求如下:
如上图所示,我相信我做的不错,但是我收到了错误日志消息:
authenticationEntryPoint用户尝试访问未经授权的api端点:/ auth / logout中的/127.0.0.1:45776
这是我的安全上下文内容:
/**
* we use this class to handle the bearer token extraction
* and pass it to the JWTReactiveAuthentication manager so in the end
* we produce
*
* simply said we extract the authorization we authenticate and
* depending on our implementation we produce a security context
*/
@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
@Autowired
private JWTReactiveAuthenticationManager authenticationManager;
@Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
String authorizationHeaderContent = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if( authorizationHeaderContent !=null && !authorizationHeaderContent.isEmpty() && authorizationHeaderContent.startsWith("Bearer ")){
String token = authorizationHeaderContent.substring(7);
Authentication authentication = new UsernamePasswordAuthenticationToken(token, token);
return this.authenticationManager.authenticate(authentication).map((auth) -> {
return new SecurityContextImpl(auth);
});
}
return Mono.empty();
}
@Override
public Mono<Void> save(ServerWebExchange arg0, SecurityContext arg1) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
我看不到我发现的任何问题或错误。错误在哪里?
答案 0 :(得分:2)
写作上有区别
//Wrong
Jwts.builder()
.setSubject(username)
.setClaims(claims)
和
//Correct
Jwts.builder()
.setClaims(claims)
.setSubject(username)
实际上,请查看setSubject
类中的DefaultJwtBuilder
方法:
@Override
public JwtBuilder setSubject(String sub) {
if (Strings.hasText(sub)) {
ensureClaims().setSubject(sub);
} else {
if (this.claims != null) {
claims.setSubject(sub);
}
}
return this;
}
首先调用setSubject(username)
时,ensureClaims()
将创建一个DefaultClaims
,而没有您的setClaims(claims)
,并且如果您调用JWTReactiveAuthenticationManager
,则先前的主题会丢失!这个JWT生成器是伪造的。
否则,您在import org.springframework.context.support.BeanDefinitionDsl.Role;
中导入了错误的Role类,则必须替换:
import com.bridjitlearning.www.jwt.tutorial.domain.Role;
作者
validateToken()
最后但并非最不重要的是,由于false
,check(token)
将始终返回put
。 put
呼叫来不及了,您必须意识到这一点。在调用check方法之前,要么删除此检查,要么移动resignTokenMemory
执行。
我不确定您要使用public Boolean validateToken(String token) {
return !isTokenExpired(token) && resignTokenMemory.check(token);
}
做什么,所以我将让您自行修复:
expiraiton * 1000
另一件事,您的令牌仅在28.8秒内有效,我建议您[routerLink]
来测试存在性。