java - 使用 JWT 完成请求后是否必须清空 SecurityContextHolder

标签 java spring spring-boot spring-security jwt

我正在基于一些开源项目和网上找到的一些文档,使用 Spring boot 来实现 JWT。

什么在起作用? 我能够生成我的 token ,并且在第一次点击时,当我尝试调用一些安全方法时,我被撤销,这很好。

有什么问题吗? 生成 token 后,似乎我可以调用我的安全方法,而无需添加我的授权 header 。

我正在调试我的代码,发现我在 SecurityContextHolder 中设置了身份验证,但请求完成后我不会清空此变量。在每个实现中都没有人这样做,所以我的问题是我是否必须这样做才能让我的代码按预期工作,仅在存在带有有效 token 的授权 header 时检索安全路径?

我的代码:

WebSecurityConfig 类:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserSecurityService userSecurityService;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/public").permitAll()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated();

        // And filter other requests to check the presence of JWT in header
        http.addFilterBefore(jwtAuthenticationFilterBean(),
                UsernamePasswordAuthenticationFilter.class);

        // Disable page caching
        http.headers().cacheControl();
    }

    @Bean
    public JWTAuthenticationFilter jwtAuthenticationFilterBean() {
        return new JWTAuthenticationFilter();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userSecurityService);
    }
}

JWTAuthenticationFiler 类

public class JWTAuthenticationFilter extends OncePerRequestFilter {


    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${jwt.token.header}")
    private String tokenHeader;

    @Autowired
    TokenAuthenticationService tokenAuthenticationService;


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String token = httpServletRequest.getHeader(tokenHeader);

        String username = tokenAuthenticationService.getUsernameFromToken(token);

        if(username != null && SecurityContextHolder.getContext().getAuthentication() != null){
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (tokenAuthenticationService.validateToken(token, userDetails)) {
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }

}

TokenAuthenticationService 类

@Service
public class TokenAuthenticationService implements Serializable {

    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_AUDIENCE = "audience";
    static final String CLAIM_KEY_CREATED = "created";
    static final String CLAIM_KEY_EXPIRED = "exp";

    static final long EXPIRATIONTIME = 864_000_000; // 10 days
    static final String SECRET = "ThisIsASecret";
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";

    @Value("${jwt.token.expiration}")
    private Long expiration;

    @Value("${jwt.token.secret}")
    private String secret;

    public String generateToken(UserDetails user) {

        Map<String, Object> claims = new HashMap<>();

        claims.put(CLAIM_KEY_USERNAME, user.getUsername());

        final Date createdDate = new Date();
        claims.put(CLAIM_KEY_CREATED, createdDate);

        final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);

        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        String username;
        try{
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        }catch (Exception e){
            username = null;
        }
        return username;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e){
            claims = null;
        }
        return claims;
    }

    public boolean validateToken(String token, UserDetails userDetails) {

        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);

        if(userDetails.getUsername().equals(username) && !isTokenExpired(token)){
            return true;
        }
        return false;
    }

    private boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        }catch (Exception e){
            expiration = null;
        }
        return expiration;
    }

    private Date getCreatedDateFromToken(String token) {
        Date createdDate;
        try{
            final Claims claims = getClaimsFromToken(token);
            createdDate = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        }catch (Exception e){
            createdDate = null;
        }
        return createdDate;

    }
}

这是我的 Controller 测试类

@RestController
public class TestController {

    @GetMapping("/public")
    public String testPublic(){
        return "Welcom to the public place";
    }

    @GetMapping("/private")
    @PreAuthorize("hasRole('USER')")
    public String testPrivate(){
        return "Welcome to the private place";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String testAdmin(){
        return "Welcome to the admin place";
    }

}

谢谢

最佳答案

所以我的问题的答案需要进行两处修改:

  1. 我们需要将其添加到我们的 WebSecurityConfig 中来拥有无状态 session 管理策略

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

  • 第二件事是我的代码中的拼写错误,我们必须确保在过滤器的条件下身份验证为空:
  • if(username != null && SecurityContextHolder.getContext().getAuthentication() == null)

    关于java - 使用 JWT 完成请求后是否必须清空 SecurityContextHolder,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45001060/

    相关文章:

    java - 无法在 Selenium 和 Java 中使用 className 定位元素

    multithreading - Spring Batch - 如何在多个线程中读取一个大文件?

    spring-boot - 如何忽略 Spring JPA findBy 存储库中的重音?

    java - 将java中的字符串分成两部分

    java - JUnit类路径和ContextClassLoader不一致还是我的误解

    java - Spring MVC - 下拉对象选择 - 无主标识符

    java - Spring Boot 和 Zuul 不支持代理的图标

    java - HttpMessageNotReadableException : Could not read document: Stream closed;

    java - Aspose:生成首页的缩略图

    spring - Spring 3.1.1 的 Java 版本