英文:
SSO + JWT Validation Best Practice?
问题
以下是翻译好的内容:
所以我想询问有关JWT验证的问题。
我的Spring Boot项目为除了登录和注销端点之外的任何API请求实现了JWT验证。
我的登录和注销端点(..../api/v1/auth/login,..../api/v1/auth/logout)是基于另一个源的单点登录(SSO)。
因此,当我使用该SSO登录时,它们会将他们的JWT令牌返回给我的后端处理。
然后,当我访问我的另一个API时,我希要在使用之前先验证生成的JWT,但是尽管我的JWT匹配,它总是返回401错误。
接下来是我的认证类:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Value("${xxxx.app.client_secret}")
private String clientSecret;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String tokenRequest = httpServletRequest.getHeader("Authorization");
validateJwtToken(tokenRequest.substring(7));
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(tokenRequest, tokenRequest);
return getAuthenticationManager().authenticate(requestAuthentication);
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
在 validateJwtToken 方法中,没有问题,因为它给我返回了 true,但当它返回到 attemptAuthentication 方法时,在该类中捕获了以下错误:
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
抱歉我的代码不够好,这是我第一次将JWT安全性应用到项目中。那么在这种情况下,我应该怎么做呢?谢谢,非常感谢任何帮助或建议。
英文:
so i want to ask question about JWT Validation.
My springboot project implementing jwt validation for any api request except login and logout endpoint.
my login and logout endpoint (..../api/v1/auth/login, ..../api/v1/auth/logout) is SSO based from another source.
So when i login with that SSO, they are returning their JWT token to my backend process.
then, when i hit my another api, i want to validate it first with the generated jwt before, but although my jwt was match, it always give 401 error.
then this is my authentication class :
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Value("${xxxx.app.client_secret}")
private String clientSecret;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String tokenRequest = httpServletRequest.getHeader("Authorization");
validateJwtToken(tokenRequest.substring(7));
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(tokenRequest, tokenRequest);
return getAuthenticationManager().authenticate(requestAuthentication);
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
in validateJwtToken method, there is no problem since it give me true return, but when it back to attemptAuthentication, it catch error like this in that class
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
sorry for my bad code, this is my first time implementing a jwt security to my project. So what should i do when i have case like this ? thank you, any help or suggestion will be appreciated.
答案1
得分: 0
你可以尝试这样做:如果令牌是由用户名生成为主题,那么从令牌中解析出它并传递给UsernamePasswordAuthenticationToken,在数据库中进行验证并设置在SecurityContext中。
String username = Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken).getBody().getSubject();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
Spring Security UserDetailsService 自定义实现示例:
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// ApplicationUser - 表示应用程序中用户的实体示例
ApplicationUser user = // 在这里调用返回用户名的 SSO 服务
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), emptyList());
}
}
在应用程序的 SecurityConfiguration 类中的配置:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl);
}
// 其他配置 ...
}
英文:
Can you try this: If the token is generated by username as Subject, then parse it from token and pass to UsernamePasswordAuthenticationToken to verify in database and set in SecurityContext
String username = Jwts.parser().setSigningKey(clientSecret).parseClaimsJws(authToken).getBody().getSubject();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
Spring Security UserDetailsService custom implementation ex:
public class UserDeatilsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// ApplicationUser - example for entity representing user in application
ApplicationUser user = // here call your SSO service returing username
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), emptyList());
}}
Configuration in Application SecurityConfiguration class:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDeatilsServiceImpl userDetailsServiceImpl;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl);
}
// other configurations ...
专注分享java语言的经验与见解,让所有开发者获益!
评论