非法 base64url 字符:从令牌 Java JWT Spring 引导获取 claims/decode 时的“ ”
Illegal base64url character: ' ' when getting claims/decode from token Java JWT Spring Boot
当我从 JWT 令牌获得一些声明以验证用户身份验证时,我收到以下错误:
Illegal base64url character: ' '
创建 JWT 完全没问题,但“解码”似乎有一些问题...
我还尝试了一个 base64url 解码器在获取声明之前解码令牌,但随后令牌无效。
我的 JWToken class 我在其中编码和“解码”令牌:
@Component
public class JWToken {
private static final String JWT_USERNAME_CLAIM = "sub";
private static final String JWT_ADMIN_CLAIM = "admin";
@Value("${jwt.issuer}")
private String issuer;
@Value("${jwt.passPhrase}")
private String passPhrase;
@Value("${jwt.duration-of-validity}")
private int expiration;
public String encode(String name, boolean admin) {
String token = Jwts.builder()
.claim(JWT_USERNAME_CLAIM, name)
.claim(JWT_ADMIN_CLAIM, admin)
.setSubject(name)
.setIssuer(issuer)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, passPhrase).compact();
return token;
}
//for retrieving any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(passPhrase).parseClaimsJws(token).getBody();
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//validate token
public Boolean validateToken(String token, String name) {
final String username = getUsernameFromToken(token);
return (username.equals(name) && !isTokenExpired(token));
}
}
我的请求过滤器:
@Component
public class JWTRequestFilter extends OncePerRequestFilter {
private static final Set<String> SECURED_PATHS =
Set.of("/api/offers", "/api/bids");
private final JWToken jwToken;
@Autowired
public JWTRequestFilter(JWToken jwToken) {
this.jwToken = jwToken;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
String servletPath = request.getServletPath();
if (HttpMethod.OPTIONS.matches(request.getMethod()) || SECURED_PATHS.stream().noneMatch(servletPath::startsWith)) {
filterChain.doFilter(request, response);
return;
}
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
System.out.println(jwtToken);
username = jwToken.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
throw new AuthenticationException("authentication problem");
} catch (ExpiredJwtException e) {
throw new AuthenticationException("authentication problem");
}
} else {
System.out.println(requestTokenHeader);
logger.warn("JWT Token does not begin with Bearer String");
//throw new AuthenticationException("authentication problem");
}
if(jwToken.validateToken(jwtToken, username)){
filterChain.doFilter(request, response);
}
// Once we get the token validate it.
}
}
当我使用 header 中生成的 JWT 令牌对 /api/offers
进行获取请求时,我的控制台出现错误:
io.jsonwebtoken.io.DecodingException: Illegal base64url character: ' '
at io.jsonwebtoken.io.Base64.ctoi(Base64.java:221) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64.decodeFast(Base64.java:270) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:23) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.ExceptionPropagatingDecoder.decode(ExceptionPropagatingDecoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:309) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:610) ~[jjwt-impl-0.11.2.jar:0.11.2]
at team10.server.aucserver.security.JWToken.getAllClaimsFromToken(JWToken.java:47) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getClaimFromToken(JWToken.java:51) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getUsernameFromToken(JWToken.java:57) ~[classes/:na]
at team10.server.aucserver.security.JWTRequestFilter.doFilterInternal(JWTRequestFilter.java:52) ~[classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
而第47行是JWToken中的getAllClaimsFromToken
方法class.
再举一个例子,这是编码生成的标记之一:
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyb25ueSIsImFkbWluIjpmYWxzZSwiaXNzIjoicHJpdmF0ZSBjb21wYW55IiwiaWF0IjoxNjExMDA1NTc4LCJleHAiOjE2MTEwMDY3Nzh9.dQwEVfSNa6EIx-U-bgHN50hmrN0wYkj-8jXRoFLTx6JB53ERBWuGUeiXLqtiJ_jTGxEISB-Lv7E9KAyPk8nV3g
您正在解码的不是令牌,您正在尝试解码整个header值。 Bearer
不是令牌的一部分,它是身份验证方案。
更一般地说,您正在编写自己的安全基础结构,这几乎总是一个非常糟糕的主意。 Spring 安全 JWT 会自动为您处理所有这些;改用它。
出于某种原因,子字符串函数在标记前保留了一些白色 space。我在 JWTRequestFilter 中更改了该行。
旧:
jwtToken = requestTokenHeader.substring(7);
新:
jwtToken = requestTokenHeader.split(" ")[1].trim();
添加的 .trim() 将删除字符串前后的任何白色 space,所以这就是我的解决方案
你也可以用这个:
jwtToken = requestTokenHeader.substring("Bearer ".length());
它解决了我的一些问题。
当我从 JWT 令牌获得一些声明以验证用户身份验证时,我收到以下错误:
Illegal base64url character: ' '
创建 JWT 完全没问题,但“解码”似乎有一些问题... 我还尝试了一个 base64url 解码器在获取声明之前解码令牌,但随后令牌无效。
我的 JWToken class 我在其中编码和“解码”令牌:
@Component
public class JWToken {
private static final String JWT_USERNAME_CLAIM = "sub";
private static final String JWT_ADMIN_CLAIM = "admin";
@Value("${jwt.issuer}")
private String issuer;
@Value("${jwt.passPhrase}")
private String passPhrase;
@Value("${jwt.duration-of-validity}")
private int expiration;
public String encode(String name, boolean admin) {
String token = Jwts.builder()
.claim(JWT_USERNAME_CLAIM, name)
.claim(JWT_ADMIN_CLAIM, admin)
.setSubject(name)
.setIssuer(issuer)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, passPhrase).compact();
return token;
}
//for retrieving any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(passPhrase).parseClaimsJws(token).getBody();
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//validate token
public Boolean validateToken(String token, String name) {
final String username = getUsernameFromToken(token);
return (username.equals(name) && !isTokenExpired(token));
}
}
我的请求过滤器:
@Component
public class JWTRequestFilter extends OncePerRequestFilter {
private static final Set<String> SECURED_PATHS =
Set.of("/api/offers", "/api/bids");
private final JWToken jwToken;
@Autowired
public JWTRequestFilter(JWToken jwToken) {
this.jwToken = jwToken;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
String servletPath = request.getServletPath();
if (HttpMethod.OPTIONS.matches(request.getMethod()) || SECURED_PATHS.stream().noneMatch(servletPath::startsWith)) {
filterChain.doFilter(request, response);
return;
}
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
System.out.println(jwtToken);
username = jwToken.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
throw new AuthenticationException("authentication problem");
} catch (ExpiredJwtException e) {
throw new AuthenticationException("authentication problem");
}
} else {
System.out.println(requestTokenHeader);
logger.warn("JWT Token does not begin with Bearer String");
//throw new AuthenticationException("authentication problem");
}
if(jwToken.validateToken(jwtToken, username)){
filterChain.doFilter(request, response);
}
// Once we get the token validate it.
}
}
当我使用 header 中生成的 JWT 令牌对 /api/offers
进行获取请求时,我的控制台出现错误:
io.jsonwebtoken.io.DecodingException: Illegal base64url character: ' '
at io.jsonwebtoken.io.Base64.ctoi(Base64.java:221) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64.decodeFast(Base64.java:270) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:23) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.ExceptionPropagatingDecoder.decode(ExceptionPropagatingDecoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:309) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:610) ~[jjwt-impl-0.11.2.jar:0.11.2]
at team10.server.aucserver.security.JWToken.getAllClaimsFromToken(JWToken.java:47) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getClaimFromToken(JWToken.java:51) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getUsernameFromToken(JWToken.java:57) ~[classes/:na]
at team10.server.aucserver.security.JWTRequestFilter.doFilterInternal(JWTRequestFilter.java:52) ~[classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
而第47行是JWToken中的getAllClaimsFromToken
方法class.
再举一个例子,这是编码生成的标记之一:
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyb25ueSIsImFkbWluIjpmYWxzZSwiaXNzIjoicHJpdmF0ZSBjb21wYW55IiwiaWF0IjoxNjExMDA1NTc4LCJleHAiOjE2MTEwMDY3Nzh9.dQwEVfSNa6EIx-U-bgHN50hmrN0wYkj-8jXRoFLTx6JB53ERBWuGUeiXLqtiJ_jTGxEISB-Lv7E9KAyPk8nV3g
您正在解码的不是令牌,您正在尝试解码整个header值。 Bearer
不是令牌的一部分,它是身份验证方案。
更一般地说,您正在编写自己的安全基础结构,这几乎总是一个非常糟糕的主意。 Spring 安全 JWT 会自动为您处理所有这些;改用它。
出于某种原因,子字符串函数在标记前保留了一些白色 space。我在 JWTRequestFilter 中更改了该行。
旧:
jwtToken = requestTokenHeader.substring(7);
新:
jwtToken = requestTokenHeader.split(" ")[1].trim();
添加的 .trim() 将删除字符串前后的任何白色 space,所以这就是我的解决方案
你也可以用这个:
jwtToken = requestTokenHeader.substring("Bearer ".length());
它解决了我的一些问题。