【问题标题】:Spring Boot Security Context return null when using a JWT token使用 JWT 令牌时 Spring Boot 安全上下文返回 null
【发布时间】:2017-09-04 20:03:00
【问题描述】:

我创建了一个需要使用 JWT 进行身份验证的 REST API。

我的实现与https://auth0.com/blog/securing-spring-boot-with-jwts/上的代码非常相似

当我尝试返回当前用户时,我总是收到 null 返回。

我的代码:

网络安全

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
      // login
      .antMatchers(HttpMethod.POST, "/login")
      .permitAll()

      .anyRequest()
      .authenticated()
      .and()
      .addFilterBefore(new JWTLoginFilter(
        "/login", authenticationManager(), logService), UsernamePasswordAuthenticationFilter.class)
      .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}

JWTAuthenticationFilter

public class JWTAuthenticationFilter extends GenericFilterBean {

  @Override
  public void doFilter(
      ServletRequest req,
      ServletResponse res,
      FilterChain filterChain) throws IOException, ServletException {

    Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)req);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    filterChain.doFilter(req, res);
  }
}

我没有包含所有 JWT 身份验证的代码,因为 JWT 工作正常,用户访问也可以。 我认为问题出在过滤器或某些配置中。

然后,我创建了一个facade 以获取servicecontroller 上的当前用户,使用以下代码(http://www.baeldung.com/get-user-in-spring-security 上的方法4):

public Authentication getAuthentication() {
    return SecurityContextHolder.getContext().getAuthentication();
}

但这不起作用。 - SecurityContextHolder.getContext() 返回org.springframework.security.core.context.SecurityContextImpl@ffffffff: Null authentication。 - SecurityContextHolder.getContext().getAuthentication() 返回 null 对象。

更新(和解决方案)

在我的控制器中,如果我使用此代码:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();

我可以获取当前用户,但在我的服务中,完全相同的代码不起作用。 但后来,我记得SecurityContext 在另一个线程上“丢失”了(来源:https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html),我的服务是异步的

@Async
public CompletableFuture<Optional<ViewUserDto>> findByLogin(String login) throws InterruptedException {
...
}

因此,使用此处的代码:https://stackoverflow.com/a/40347437/4794469,一切正常。 我不知道这是否会给我的代码带来任何副作用(所有单元测试都有效)

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

    我曾在一个与您的授权流程相似的应用程序中工作:

    WebSecurityConfigurerAdapter

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private AuthenticationProvider provider;
    
        @Autowired
        private TokenAuthenticationService tokenService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder builder) throws Exception {
            builder.authenticationProvider(provider);       
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin().disable();         
            http.csrf().disable();
    
    
            http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/users", "/v1/oauth/token").permitAll()
                .anyRequest().authenticated()
                .and()          
                .addFilterBefore(new OAuthTokenFilter("/v1/oauth/token", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new AuthorizationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);            
        }   
    
    }
    

    AbstractAuthenticationProcessingFilter

    public class OAuthTokenFilter extends AbstractAuthenticationProcessingFilter {
    
        private final ObjectMapper MAPPER = new ObjectMapper();
    
        private TokenAuthenticationService service;
    
        public OAuthTokenFilter(String url, AuthenticationManager manager, TokenAuthenticationService service) {
            super(new AntPathRequestMatcher(url));
            setAuthenticationManager(manager);
            this.service = service;
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException   {       
            Login login = MAPPER.readValue(request.getInputStream(), Login.class);              
            UsernamePasswordAuthenticationToken token = 
                    new UsernamePasswordAuthenticationToken(login.getUsername(), login, Arrays.asList());
    
            return getAuthenticationManager().authenticate(token);
        }
    
    
        @Override
        protected void successfulAuthentication(
                HttpServletRequest request, 
                HttpServletResponse response, 
                FilterChain chain,
                Authentication authentication) throws IOException, ServletException {
    
            User credentials = (User)  authentication.getPrincipal();                   
            String token = service.jwt(credentials);                
            String json = MAPPER.writeValueAsString(new AuthorizationToken(token, "Bearer"));
    
            response.addHeader("Content-Type", "application/json");
            response.getWriter().write(json);
            response.flushBuffer();     
        }
    }
    

    GenericFilterBean

    public class AuthorizationFilter extends GenericFilterBean {
    
        private TokenAuthenticationService service;
    
        public AuthorizationFilter(TokenAuthenticationService service) {
            this.service = service;
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            Authentication authentication = service.getAuthentication((HttpServletRequest)request);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request, response);
        }
    
    }
    

    TokenAuthenticationService

    @Service
    public class TokenAuthenticationService {
    
        public static final String JWT_SECRET_ENV = "JWT_SECRET";
        public static final String ISSUER = "my issuer";
    
        public static final String ROLE_CLAIM = "role";
        public static final String THIRDY_PARTY_ID_CLAIM = "thirdy_party_id";   
        public static final String TOKEN_PREFIX = "Bearer";
        public static final String HEADER = "Authorization";
    
        @Autowired
        private Environment environment;
    
        public Authentication getAuthentication(HttpServletRequest request) {
            String token  = request.getHeader(HEADER);      
            String secret = environment.getProperty(JWT_SECRET_ENV);
    
            if (token != null) {
    
                try {
    
                    String bearer = token.replace(TOKEN_PREFIX, "").trim();
    
                    Algorithm algorithm = Algorithm.HMAC256(secret);
                    JWTVerifier verifier = JWT.require(algorithm)
                        .withIssuer(ISSUER)
                        .build();
    
                    DecodedJWT jwt = verifier.verify(bearer);
    
                    User user = new User();
                    user.setId(jwt.getSubject());
                    user.setThirdPartyId(jwt.getClaim(THIRDY_PARTY_ID_CLAIM).asString());
                    user.setRole(jwt.getClaim(ROLE_CLAIM).asString());
    
                    List<GrantedAuthority> authorities =     AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
                    return new UsernamePasswordAuthenticationToken(user, null, authorities);
    
                } catch (Exception e){
                    e.printStackTrace(System.out);
                }
            }
    
            return null;
        }
    
    }
    

    然后,控制器:

    @RestController
    public class UserController {
    
        @ResponseBody
        @GetMapping("/v1/users/{id}")   
        @PreAuthorize("hasAuthority('USER')")
        public User get(@PathVariable("id") String id, Authentication authentication) {     
    
            User user = (User) authentication.getPrincipal();
            return user;
        }
    }
    

    【讨论】:

    • 基本上,您的代码非常相似。在我更改了TokenAuthenticationServiceUsernamePasswordAuthenticationToken 的返回实例)之后,我可以像我之前所说的那样在控制器中获取当前用户,但仍然无法从 Facade(外部控制器)获取。所以,到目前为止,我的解决方案是将经过身份验证的用户从控制器传递给服务
    【解决方案2】:

    我在我的网络应用上启用 JWT 时遇到了类似的问题。 您需要:“Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files”。

    1. 请从以下 URL 下载此包并替换 US_export_policy.jar、local_policy.jar (\jre\lib\security)

    2. 如果还是不行,那么需要替换掉\lib\security中的上述jar文件

    http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

    【讨论】:

    • 现在试过了,但问题仍然存在。仍然接收空身份验证
    猜你喜欢
    • 2020-07-18
    • 2021-01-22
    • 2017-12-27
    • 2019-02-05
    • 2020-08-20
    • 2021-06-17
    • 1970-01-01
    • 2018-11-12
    • 2020-02-12
    相关资源
    最近更新 更多