如何解决这个spring安全相关的循环依赖关系?

li9yvcax  于 2021-07-14  发布在  Java
关注(0)|答案(2)|浏览(423)

我有一个springboot/springsecurity应用程序,我想在其中使用jwtauth和由springsecurity提供的jdbcuserdetailsmanager。
以下是我目前掌握的情况:
示例Application.java

package org.example.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

jwttokenfilter.java(使用userdetailsservice)

package org.example.app.auth;

import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

import static org.springframework.util.ObjectUtils.isEmpty;

@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    private final JwtTokenUtil jwtTokenUtil;
    private final UserDetailsService userDetailsService;

    public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, JdbcUserDetailsManager jdbcUserDetailsManager) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.userDetailsService = jdbcUserDetailsManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        // Get authorization header and validate
        final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (isEmpty(header) || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        // Get jwt token and validate
        final String token = header.split(" ")[1].trim();
        if (!jwtTokenUtil.validate(token)) {
            chain.doFilter(request, response);
            return;
        }

        // Get user identity and set it on the spring security context
        String username = jwtTokenUtil.getUsername(token);
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken
                authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null,
                userDetails == null ?
                        List.of() : userDetails.getAuthorities()
        );

        authentication.setDetails(
                new WebAuthenticationDetailsSource().buildDetails(request)
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

}

jwttokenutil.java(对于本例并不重要)

package org.example.app.auth;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

import static java.lang.String.format;

@Component
public class JwtTokenUtil {
    Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);

    private final String jwtSecret = "aYEFtKMCn0xCg5caH1nnFuHfdAB0lBOvdonxq80VqOGNnG6QcyagXWOLrUdqJnzexUXYceMhGNFNYsA" +
            "6rblSibUEh0yRsJ3XO1um1iMdoekOPzj4zKlokcu9TxTbz5DHYVLkqX3q9JrLgbLZFXD8ynOHfRHRL5Ge64iFZBVm9X517fwZrNornOm" +
            "K2L7hUz10SgZpxAz6";
    private final String jwtIssuer = "example.org";

    public String generateAccessToken(User user) {
        byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
        Key key = Keys.hmacShaKeyFor(keyBytes);
        return Jwts.builder()
                .setSubject(format("%s", user.getUsername()))
                .setIssuer(jwtIssuer)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000)) // 1 hour
                .signWith(key, SignatureAlgorithm.HS512)
                .compact();
    }

    public String getUsername(String token) {
        JwtParser jwtParser = Jwts.parserBuilder()
                .setSigningKey(jwtSecret).build();
        Claims claims = jwtParser
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject().split(",")[1];
    }

    public boolean validate(String token) {
        try {
            JwtParser jwtParser = Jwts.parserBuilder()
                    .setSigningKey(jwtSecret).build();
            jwtParser.parseClaimsJws(token);
            return true;
        } catch (SecurityException ex) {
            logger.error("Invalid JWT signature - {}", ex.getMessage());
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token - {}", ex.getMessage());
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token - {}", ex.getMessage());
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token - {}", ex.getMessage());
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty - {}", ex.getMessage());
        }
        return false;
    }

}

securityconfiguration.java(使用jwttokenfilter)

package org.example.app.config;

import org.example.app.auth.JwtTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.sql.DataSource;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private final JwtTokenFilter jwtTokenFilter;
    private final DataSource dataSource;

    public SecurityConfiguration(JwtTokenFilter jwtTokenFilter, DataSource dataSource) {
        this.jwtTokenFilter = jwtTokenFilter;
        this.dataSource = dataSource;
    }

    public void configureHttpSecurity(HttpSecurity http) {
        http.addFilterBefore(
                jwtTokenFilter,
                UsernamePasswordAuthenticationFilter.class
        );
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    @Bean
    public JdbcUserDetailsManager userDetailsManager(AuthenticationManager authenticationManager,
                                                     AuthenticationManagerBuilder authenticationManagerBuilder)
            throws Exception {
        JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcUserDetailsManagerConfigurer =
                authenticationManagerBuilder.jdbcAuthentication().dataSource(dataSource);

        JdbcUserDetailsManager jdbcUserDetailsManager = jdbcUserDetailsManagerConfigurer.getUserDetailsService();
        jdbcUserDetailsManager.setAuthenticationManager(authenticationManager);

        return jdbcUserDetailsManager;
    }
}

当应用程序尝试启动时,我看到以下内容:


***************************

APPLICATION FAILED TO START

***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  jwtTokenFilter defined in file [/example-app/build/classes/java/main/org/example/app/auth/JwtTokenFilter.class]
↑     ↓
|  securityConfiguration defined in file [/example-app/build/classes/java/main/org/example/app/config/SecurityConfiguration.class]
└─────┘

我想知道如何解决这个循环依赖,同时记住:
jwttokenfilter需要一个userdetailsmanager,因为我想在数据库中查找该用户,看看该用户是否仍然存在
securityconfiguration需要一个jwttokenfilter,它可以在httpsecurity上设置
免责声明:jwt相关代码的灵感来自https://www.toptal.com/spring/spring-security-tutorial
n、 b:我不确定在数据库中验证用户权限是否有意义,因为jwt的一个卖点是它是无状态的。。。我可以把当局放在jwt代币里。

chhkpiq4

chhkpiq41#

看看@lazy annotation,我也遇到了同样的问题,而且效果很好。
尝试以下操作:

public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, @Lazy JdbcUserDetailsManager jdbcUserDetailsManager) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.userDetailsService = jdbcUserDetailsManager;
}
8cdiaqws

8cdiaqws2#

如果你使用 @Autowired 而不是构造函数注入。
例子:

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtTokenFilter jwtTokenFilter;
    private final DataSource dataSource;

    public Security(DataSource dataSource) 
    {
        this.dataSource = dataSource;
    }
}

相关问题