Master JWT Authentication in Spring Security: From Basics to Implementation
This guide walks you through the various HTTP authentication methods, explains JWT Bearer authentication, and demonstrates how to implement a custom JwtAuthenticationFilter in Spring Security, configure it, and use JWT tokens for securing API endpoints, including token refresh handling.
1. Introduction
Welcome to the Spring Security practical series. Previously we covered how to create a JWT generator and return a Json Web Token after authentication. This article examines how to use JWT for request authentication. The demo method is provided at the end.
2. Common HTTP Authentication Methods
To use JWT in HTTP requests, you need to understand common HTTP authentication methods.
2.1 HTTP Basic Authentication
HTTP Basic Authentication, also called basic auth, uses Base64 to encode username and password and places the encoded string in the request Header. It is essentially clear‑text transmission and should be used only over HTTPS. The authentication flow is:
The client sends a GET request, the server responds with 401 Unauthorized and a WWW-Authenticate header specifying the authentication scheme and realm. The client then prompts for credentials, sends them in the Header, and upon successful verification the server returns 200.
2.2 HTTP Digest Authentication
Digest authentication addresses the weaknesses of Basic auth by using a random nonce and the MD5 algorithm to create a digest of the username and password. The flow is similar to Basic but more complex:
Step 1: Server returns a 401 response with a WWW-Authenticate header containing realm and nonce. The client uses these values to compute the digest.
Step 2: Client sends a request with an Authorization header that includes username, realm, nonce, uri and response.
Step 3: Server validates the digest and, if successful, returns the requested resource.
2.3 SSL Client Authentication
SSL client authentication (HTTPS) provides a higher security level but requires a CA certificate. It involves public‑key and private‑key pairs, asymmetric and symmetric encryption, and is more complex.
2.4 Form‑Based Authentication
Form authentication is not part of the HTTP specification and therefore varies in implementation. It typically works with cookies and sessions: after a successful login the server returns a sessionId stored in a cookie, which the client includes in subsequent requests for authentication.
2.5 Bearer Authentication with JWT
After obtaining a JWT via form login, you can use it as a Bearer token. The client places Bearer <token> in the Authorization header. This token‑based scheme is defined in RFC 6750 as part of OAuth 2.0 but can also be used independently. Note the space between “Bearer” and the token, and use HTTPS.
3. Implementing JWT Authentication in Spring Security
3.1 Define a Json Web Token Filter
Spring Security’s default filters do not handle Bearer authentication, so we create a custom JwtAuthenticationFilter extending OncePerRequestFilter. The filter extracts the JWT from the Authorization header, validates it, loads user details, and populates the security context.
package cn.felord.spring.security.filter;
import cn.felord.spring.security.exception.SimpleAuthenticationEntryPoint;
import cn.felord.spring.security.jwt.JwtTokenGenerator;
import cn.felord.spring.security.jwt.JwtTokenPair;
import cn.felord.spring.security.jwt.JwtTokenStorage;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
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 java.util.Objects;
/**
* jwt 认证拦截器 用于拦截 请求 提取jwt 认证
*
* @author dax
* @since 2019/11/7 23:02
*/
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String AUTHENTICATION_PREFIX = "Bearer ";
private AuthenticationEntryPoint authenticationEntryPoint = new SimpleAuthenticationEntryPoint();
private JwtTokenGenerator jwtTokenGenerator;
private JwtTokenStorage jwtTokenStorage;
public JwtAuthenticationFilter(JwtTokenGenerator jwtTokenGenerator, JwtTokenStorage jwtTokenStorage) {
this.jwtTokenGenerator = jwtTokenGenerator;
this.jwtTokenStorage = jwtTokenStorage;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() != null) {
chain.doFilter(request, response);
return;
}
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(header) && header.startsWith(AUTHENTICATION_PREFIX)) {
String jwtToken = header.replace(AUTHENTICATION_PREFIX, "");
if (StringUtils.hasText(jwtToken)) {
try {
authenticationTokenHandle(jwtToken, request);
} catch (AuthenticationException e) {
authenticationEntryPoint.commence(request, response, e);
}
} else {
authenticationEntryPoint.commence(request, response, new AuthenticationCredentialsNotFoundException("token is not found"));
}
}
chain.doFilter(request, response);
}
private void authenticationTokenHandle(String jwtToken, HttpServletRequest request) throws AuthenticationException {
JSONObject jsonObject = jwtTokenGenerator.decodeAndVerify(jwtToken);
if (Objects.nonNull(jsonObject)) {
String username = jsonObject.getStr("aud");
JwtTokenPair jwtTokenPair = jwtTokenStorage.get(username);
if (Objects.isNull(jwtTokenPair)) {
if (log.isDebugEnabled()) {
log.debug("token : {} is not in cache", jwtToken);
}
throw new CredentialsExpiredException("token is not in cache");
}
String accessToken = jwtTokenPair.getAccessToken();
if (jwtToken.equals(accessToken)) {
JSONArray jsonArray = jsonObject.getJSONArray("roles");
String roles = jsonArray.toString();
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(roles);
User user = new User(username, "[PROTECTED]", authorities);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, authorities);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} else {
if (log.isDebugEnabled()) {
log.debug("token : {} is not matched", jwtToken);
}
throw new BadCredentialsException("token is not matched");
}
} else {
if (log.isDebugEnabled()) {
log.debug("token : {} is invalid", jwtToken);
}
throw new BadCredentialsException("token is invalid");
}
}
}Key points: anonymous requests must not carry a token; token validation includes checking the cache, matching the access token, and converting roles to authorities.
3.2 Configure JwtAuthenticationFilter
Inject the filter into the Spring IoC container and place it before UsernamePasswordAuthenticationFilter in the security chain.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(new SimpleAccessDeniedHandler())
.authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)
// jwt must be configured before UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginProcessingUrl(LOGIN_PROCESSING_URL)
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout()
.addLogoutHandler(new CustomLogoutHandler())
.logoutSuccessHandler(new CustomLogoutSuccessHandler());
}4. Using JWT for Request Validation
Define a protected endpoint (e.g., http://localhost:8080/foo/test). Without a token the request receives a 401 response. After obtaining a JWT, include it in the request header (e.g., via Postman) and the request succeeds.
5. Refreshing JWT Tokens
Access tokens and refresh tokens are issued as a pair. A separate filter can intercept refresh‑token requests, validate the token, and issue a new access token.
Further Reading
Spring Security实战:Spring Boot 下的自动配置 (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247488902&idx=3&sn=fc4140a574424772afddfd1e82122e96)
Spring Security实战:路径Uri中的 Ant 风格 (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247488925&idx=3&sn=a7ceb193cc20560052d32148e5a4efcd)
Spring Security实战:自定义配置类入口 (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247488948&idx=3&sn=cbb77e520a55280dba30aa7815adefec)
Spring Security实战:搞清楚 UserDetails (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247488806&idx=4&sn=57cb036a2130347d9dc5ecc4a99a5e7a)
Spring Security实战:登录成功后返回 JWT Token (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247488820&idx=3&sn=213bf5356bb2317aecca29c4f9441199)
Spring Security实战:实现自定义退出登录 (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247489028&idx=3&sn=3d3f0476b5e3db524981961d23f3527d)
Spring Security实战:自定义异常处理 (http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==∣=2247489052&idx=3&sn=3e57a672016670e4378a41941b41c15b)
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
