Авторизация Webflux JWT не работает нормально - PullRequest
2 голосов
/ 30 июня 2019

Я следую учебнику о JWT в контексте пружинного реагирования (webflux).

Генерация токенов работает нормально, однако авторизация не работает, когда я использую 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();


    }

Как видите, я хочу просто отклонить все неавторизованные запросы, кроме запросов входа в систему или опций.

Логин работает нормально, и я получаю токен.

enter image description here

Но попытка выхода из системы (твик, который я реализовал, чтобы сделать его полным состоянием, так как я только учусь) не работает.

Вот мой контроллер выхода из системы:


@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);
    }

}

Запрос на выход из системы следующий:

enter image description here enter image description here

Как указано на изображениях выше, я считаю, что у меня все хорошо, но я получаю сообщение об ошибке:

authenticationEntryPoint пользователь пытается получить доступ к неавторизованным конечным точкам API: /127.0.0.1:45776 в / auth / logout

Вот мой контекст безопасности:


/**
 * 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.");
    }

}

Я не вижу ни одной обнаруженной проблемы или ошибки. Где ошибка?

1 Ответ

2 голосов
/ 05 июля 2019

Существует разница в написании

//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), прецедент теряется!Этот JWT-строитель - фальшивка.

В противном случае вы импортируете неправильный класс ролей в JWTReactiveAuthenticationManager, вам необходимо заменить:

import org.springframework.context.support.BeanDefinitionDsl.Role;

на

import com.bridjitlearning.www.jwt.tutorial.domain.Role;

И последнее, что не менее важно,validateToken() всегда будет возвращать false из-за check(token).put звонок приходит слишком поздно, вы должны знать об этом.Либо вы удаляете эту проверку, либо перемещаете выполнение put перед вызовом метода проверки.

Я не уверен, что вы хотите сделать с resignTokenMemory, поэтому я позволю вам исправить это самостоятельно:

public Boolean validateToken(String token) {
    return !isTokenExpired(token) && resignTokenMemory.check(token);
}

Еще одна вещь, ваш токен действителентолько 28,8 секунды, для тестирования raison я рекомендую вам expiraiton * 1000.

...