Spring Security Sping Boot 安全-使用Cookie中的令牌而不是授权头

mnowg1ta  于 2023-08-05  发布在  Spring
关注(0)|答案(1)|浏览(165)

您好,我正在尝试通过JWT从Spring Security Session身份验证和授权迁移到auth。我有一个关于我遇到的具体情况的问题。我不使用Authorization头进行身份验证,而是对使用cookie来避免将令牌存储在本地存储中感兴趣。然而,我的集成测试一直失败,因为没有一个“承载令牌”。我想知道是否有人遇到过类似的情况,他们需要发送JWT令牌作为cookie,而不是使用授权头。如果是,您如何解决错误消息look below?任何见解或解决方案将不胜感激。- 谢谢你-谢谢
错误main] .s.r.w.a.BearerTokenAuthenticationFilter : Did not process request since did not find bearer token
集成测试

@Test
    @Order(3)
    void login() throws Exception {
        MvcResult login = this.MOCK_MVC
                .perform(post("******")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(new LoginDTO(ADMIN_EMAIL, ADMIN_PASSWORD).convertToJSON().toString())
                )
                .andExpect(status().isOk())
                .andReturn();

        Cookie cookie = login.getResponse().getCookie(COOKIE_NAME);

        // Test route
        this.MOCK_MVC
                .perform(get("****").cookie(cookie))
                .andExpect(status().isOk());
    }

字符串
登录方式

/**
     * Note Transactional annotation is used because Entity class has properties with fetch type LAZY
     * @param dto consist of principal(username or email) and password.
     * @param req of type HttpServletRequest
     * @param res of type HttpServletResponse
     * @throws AuthenticationException is thrown when credentials do not exist or bad credentials
     * @return ResponseEntity of type HttpStatus
     * */
    @Transactional
    public ResponseEntity<?> login(LoginDTO dto, HttpServletRequest req, HttpServletResponse res) {
        Authentication authentication = this.authManager.authenticate(
                UsernamePasswordAuthenticationToken.unauthenticated(dto.getPrincipal(), dto.getPassword())
        );

        // Jwt Token
        String token = this.jwtTokenService.generateToken(authentication);

        // Add Jwt Cookie to Header
        Cookie jwtCookie = new Cookie(COOKIENAME, token);
        jwtCookie.setDomain(DOMAIN);
        jwtCookie.setPath(COOKIE_PATH);
        jwtCookie.setSecure(COOKIE_SECURE);
        jwtCookie.setHttpOnly(HTTPONLY);
        jwtCookie.setMaxAge(COOKIEMAXAGE);

        // Add custom cookie to response
        res.addCookie(jwtCookie);

        // Second cookie where UI can access to validate if user is logged in
        Cookie cookie = new Cookie(LOGGEDSESSION, UUID.randomUUID().toString());
        cookie.setDomain(DOMAIN);
        cookie.setPath(COOKIE_PATH);
        cookie.setSecure(COOKIE_SECURE);
        cookie.setHttpOnly(false);
        cookie.setMaxAge(COOKIEMAXAGE);

        // Add custom cookie to response
        res.addCookie(cookie);

        return new ResponseEntity<>(OK);
    }


过滤链

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers(publicRoutes()).permitAll();
                    auth.anyRequest().authenticated();
                })
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
                .exceptionHandling((ex) -> ex.authenticationEntryPoint(this.authEntryPoint))
//                .addFilterBefore(new JwtFilter(), BearerTokenAuthenticationFilter.class)
                .logout(out -> out
                        .logoutUrl("****")
                        .deleteCookies(COOKIE_NAME, LOGGEDSESSION)
                        .logoutSuccessHandler((request, response, authentication) ->
                                SecurityContextHolder.clearContext()
                        )
                )
                .build();
    }


最后,我想提请您注意前面提到的SecurityFilterChain,您会注意到我已经注解掉了addFilterBefore方法。最初,我的方法是通过提取包含JWT令牌的所需cookie并将其添加到请求头来处理每个传入请求。这种方法在cookie存在时工作良好,但在cookie为空时(例如,在用户登录过程中)工作不佳。注意HeaderMapRequestWrapper的实现类似于link

@Component @Slf4j
    public class JwtFilter extends OncePerRequestFilter {

    @Value(value = "${server.servlet.session.cookie.name}")
    private String COOKIENAME;

    @Override
    protected void doFilterInternal(
            @NotNull HttpServletRequest request,
            @NotNull HttpServletResponse response,
            @NotNull FilterChain filterChain
    ) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        log.info("Cookies Array " + Arrays.toString(cookies)); // Null on login requests

        HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(request);

        if (cookies != null) {
            Optional<String> cookie = Arrays.stream(cookies)
                    .map(Cookie::getName)
                    .filter(name -> name.equals(COOKIENAME))
                    .findFirst();

            cookie.ifPresent(s -> requestWrapper.addHeader(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(s)));
        }

        filterChain.doFilter(requestWrapper, response);
    }

}

ct3nt3jp

ct3nt3jp1#

我可以通过查看spring docs来解决这个问题。由于默认情况下,资源服务器在Authorization头中查找承载令牌,并且在我的情况下,我作为cookie发送,因此我必须定义BearerTokenResolver的自定义实现。
豆子

@Bean
    public BearerTokenResolver bearerTokenResolver(JwtDecoder jwtDecoder) {
        return new CustomBearerTokenResolver(jwtDecoder);
    }

字符串
自定义实施

@Slf4j
public class CustomBearerTokenResolver implements BearerTokenResolver {

    @Value(value = "${server.servlet.session.cookie.name}")
    private String COOKIENAME;
    private final JwtDecoder jwtDecoder;

    public CustomBearerTokenResolver(JwtDecoder jwtDecoder) {
        this.jwtDecoder = jwtDecoder;
    }

    @Override
    public String resolve(final HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();

        if (cookies != null) {
            Optional<Cookie> cookie = Arrays.stream(cookies)
                    .filter(name -> name.getName().equals(COOKIENAME))
                    .findFirst();

            if (cookie.isPresent()) {
                try { // Note this is an expensive compute
                    String token = cookie.get().getValue();
                    this.jwtDecoder.decode(token);
                    return token;
                } catch (JwtException e) {
                    log.error("Jwt Exception {}", e.getMessage());
                    return null;
                }
            }
        }

        return null;
    }

}

相关问题