带有 JWT AuthenticationCredentialsNotFoundException 的 Spring 安全配置
Posted
技术标签:
【中文标题】带有 JWT AuthenticationCredentialsNotFoundException 的 Spring 安全配置【英文标题】:Spring security configuration with JWT AuthenticationCredentialsNotFoundException 【发布时间】:2019-08-06 00:15:38 【问题描述】:当我尝试使用 POSTMAN 调用受保护的 ws 并在登录 Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBZGVsSVAiLCJpYXQiOjE1NTI2MDM4NTIsImV4cCI6MTU1MjY5MDI1Mn0.OfzkQlhubdLBa9cV7O231M1AB8ya9g5Q1zefhjhPvJIICz45SUunT2xP6r008O-oxUXBQT3RrRha7n6gSo9Jpw
后使用获得的令牌传递授权标头时出现以下错误。
例外:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379)
org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:223)
org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
com.spring.jwtauthentication.controller.TestRestAPIs$$EnhancerBySpringCGLIB$$11db1a81.adminAccess(<generated>)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
请在下面找到我的实现:
1) 登录 api AuthRestAPIs.java :
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthRestAPIs
@Autowired
AuthenticationManager authenticationManager;
@Autowired
private UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Autowired
PasswordEncoder encoder;
@Autowired
JwtProvider jwtProvider;
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser( @RequestBody LoginForm loginRequest)
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateJwtToken(authentication);
return ResponseEntity.ok(new JwtResponse(jwt));
测试 api apiTestRestAPIs.java(受保护的资源):
@RestController
public class TestRestAPIs
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/api/test//admin")
public String adminAccess()
return ">>> Admin Contents";
2) 安全配置类
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = "com.spring.jwtauthentication", scopedProxy = ScopedProxyMode.INTERFACES)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthEntryPoint unauthorizedHandler;
public JwtAuthTokenFilter authenticationJwtTokenFilter()
JwtAuthTokenFilter test = new JwtAuthTokenFilter();
return test;
@Autowired
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
@Override
protected void configure(HttpSecurity http) throws Exception
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
3) JwtAuthTokenFilter.java :
@Component
public class JwtAuthTokenFilter extends OncePerRequestFilter
@Autowired
private JwtProvider tokenProvider;
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException
try
String jwt = getJwt(request);
if (jwt!=null && tokenProvider.validateJwtToken(jwt))
String username = tokenProvider.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
catch (Exception e)
logger.error("Can NOT set user authentication -> Message: ", e);
filterChain.doFilter(request, response);
private String getJwt(HttpServletRequest request)
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer "))
return authHeader.replace("Bearer ","");
return null;
4) JwtProvider.java
@Component
public class JwtProvider
private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);
private String jwtSecret = "jwtOlfaSecretKey";
private int jwtExpiration =86400;
public String generateJwtToken(Authentication authentication)
UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpiration*1000))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
public boolean validateJwtToken(String authToken)
try
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
catch (SignatureException e)
logger.error("Invalid JWT signature -> Message: ", e);
catch (MalformedJwtException e)
logger.error("Invalid JWT token -> Message: ", e);
catch (ExpiredJwtException e)
logger.error("Expired JWT token -> Message: ", e);
catch (UnsupportedJwtException e)
logger.error("Unsupported JWT token -> Message: ", e);
catch (IllegalArgumentException e)
logger.error("JWT claims string is empty -> Message: ", e);
return false;
public String getUserNameFromJwtToken(String token)
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody().getSubject();
5) JwtAuthEntryPoint.java :
@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint
private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException
logger.error("Unauthorized error. Message - ", e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
6) web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>myApp</display-name>
<servlet>
<servlet-name>myApp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myApp</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/cfg.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
7) pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
【问题讨论】:
首先您的 JwtAuthTokenFilter 被标记为 @Component 但您不使用它,而是创建它的一个新实例。用它!自动连线!还要确保服务器实际上正在接收令牌。确保角色(userdetails.getAuthorities() 不为空)。第二件事,JWT 的目的是无状态。 UserDetails userDetails = userDetailsService.loadUserByUsername(username) 一段代码使它不是无状态的。如果您想要/需要权限,请使用您的 jwtprovider 类将其作为声明传递到 JWT 令牌本身中。你的代码的其他部分没问题。希望对你有帮助 【参考方案1】:使用@PreAuthorize("hasAuthority('ADMIN')") 代替@PreAuthorize("hasRole('ADMIN')"),这适用于您的情况。
【讨论】:
以上是关于带有 JWT AuthenticationCredentialsNotFoundException 的 Spring 安全配置的主要内容,如果未能解决你的问题,请参考以下文章
带有 JWT 的 AngularJS 或 SPA - 到期和刷新