【问题标题】:Do we have to empty SecurityContextHolder after request is finished with JWT使用 JWT 完成请求后,我们是否必须清空 SecurityContextHolder
【发布时间】:2017-12-13 12:56:54
【问题描述】:

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

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

有什么问题? 一旦我生成了我的令牌,我似乎可以调用我的安全方法而无需添加我的授权标头。

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

我的代码:

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;

    }
}

这是我的控制器测试类

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

}

谢谢

【问题讨论】:

    标签: java spring spring-boot spring-security jwt


    【解决方案1】:

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

    1. 我们需要通过将其添加到我们的 WebSecurityConfig 来获得无状态会话管理策略

    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

    1. 第二件事是我的代码中的一个错字,我们必须确保在我们的过滤器条件下身份验证为空:

    如果(用户名!= null && SecurityContextHolder.getContext().getAuthentication() == null)

    【讨论】:

      猜你喜欢
      • 2020-06-08
      • 2012-12-23
      • 1970-01-01
      • 2021-04-05
      • 2013-12-23
      • 2017-07-13
      • 1970-01-01
      • 2020-02-03
      • 1970-01-01
      相关资源
      最近更新 更多