java - 多个安全适配器 - addFilterBefore 无法按预期工作
问题描述
我想为API
和进行配置MVC
。授权API
是JWT
,而授权MVC
是x509 client certificate
(separetley 两种配置都很好)。
预期行为:
- request /v2/api/** 将使用在
addFilterBefore
验证 jwt 和创建上下文的地方给出的过滤器进行过滤 - 所有其他请求将使用 ssl 客户端证书身份验证。
现在它是如何工作的 - 任何请求都会触发 addFilterBefore 并且由于缺少 jwt 令牌而被拒绝。
我的配置类:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter
{
@Configuration
@Order(1)
public static class JwtAuth extends WebSecurityConfigurerAdapter
{
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.antMatcher("/v2/api/**").authorizeRequests()
.antMatchers("/v2/api/**").authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
}
@Configuration
@Order(4)
public class x509Authenticator extends X509AuthenticationServer
{
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().authenticated()
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and().exceptionHandling().accessDeniedPage("/forbidden");
}
@Bean
public UserDetailsService userDetailsService()
{
return new UserDetailsService()
{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
}
}
}
解决方案
请注意:您提供的信息/代码是不够的。由于您没有提供代码,X509AuthenticationServer
我JwtRequestFilter
不知道到底错在哪里。
假设这是您试图解释所有可能性的迫切要求。
现在它是如何工作的 - 任何请求都会触发 addFilterBefore 并且由于缺少 jwt 令牌而被拒绝。
是的,它会通过任何请求的过滤器。在过滤器中您应该注意的一件事是检查标头,如果存在则继续进行 JWT 身份验证,否则跳过 JWT 身份验证(如果块,请检查我的过滤器类代码)。
由于您的 Spring Security 配置了 x509,身份验证对象将由证书主题使用正则表达式设置.x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
对于 X509 请求主体将来自客户端证书主题
对于 JWT 请求,因为它由服务器证书组成,主体将从服务器证书主题中设置。现在通过从 JWT 令牌获取用户名并授予这些用户所需的权限来覆盖主体。
Subject: EMAILADDRESS=nlpraveennl@gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
下面给出了客户证书的详细信息并查看主题。从正则表达式收到的本金是“vedanta”
[
Version: V1
Subject: EMAILADDRESS=vedanta@gmail.com, CN=vedanta, OU=some unit, O=some comapany, L=San diego, ST=CA, C=US
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 4096 bits
modulus: 817322829240490927539679977649457347113079769252522527800627995161015683461174014845370356721299781132807751160825513223084246773438383264091041820195243162665509891042158368380019167357193944418022840037166629645037683573001749034046239097004884878919482801656531508586406926436161114752390080130151471441072696492233900694526232243678291240183028778626646828176141484812179759739175161094296327915519918417719601837669497535100237474334129104633049344560273440876841464270970566837970617275659841740418106346304917775719024288908219661239022501256531355020518204348890248534705892780384737192506755315365883201062844995260628679776392945804218936346471987181753817158168697446990117525268883019172878686864407803654159029932574084328051385395658876802073285425506794524283532870369321962543974623256690683454729681498079311854836252232196330777070325603711372859419866719120909302184446204160932841096818392866335724975192880990513954025684813917171551040959488266330875554906980729293764667616531866907648957912796417658137011975409928985274876102141544954375822680116494691313951508398067207453068155470604238471192479465455519868221517071480577973522583550201343549400093003081375335896091143428006322327934212827941149315676683
public exponent: 65537
Validity: [From: Tue Oct 15 09:04:58 IST 2019,
To: Fri Oct 09 09:04:58 IST 2020]
Issuer: EMAILADDRESS=nlpraveennl@gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
SerialNumber: [ baafa655 dd56be52]
]
还有什么可能是错的
1. 对于 v2 API(JWT):您没有在请求中发送服务器证书。我同意它不需要客户端证书,但服务器只接受通过 SSL 连接的通信。
您无法通过 curl cmd 对其进行测试。仅当您的 SSL 由 CA 签名时才有效。
您不能在 google chrome 中对此进行测试,因为 google chrome 还需要 CA 签署的 SSL。
用于测试的技巧:
使用服务器证书和安全密钥以及您的 JWT 令牌,如下所示
curl -ik --cert server.crt --key serverPrivateKey.pem -H "Authorization:Bearer jwtToken" "https://localhost:8443/v2/api/yourpath"
它应该工作。
2. 对于 v2 以外的 API(无 JWT):您未在请求中包含客户端证书。正确的测试方法是。
curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1/hello"
curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1.5/hello"
我已经从我的角度进行了测试,它对我有用,没有任何故障。我唯一的东西
仅供参考在这里添加代码。
配置@Configuration
@EnableWebSecurity
public class SpringSecurityConfig
{
@Configuration
@Order(1)
public static class JwtConfiguration extends WebSecurityConfigurerAdapter
{
@Autowired
private JwtAuthenticationTokenFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.antMatcher("/v2/**").authorizeRequests()
.antMatchers("/v2/**").authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
}
@Configuration
@Order(4)
public static class X509Configuration extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().authenticated()
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService())
.and().exceptionHandling().accessDeniedPage("/forbidden");
}
@Bean
public UserDetailsService userDetailsService()
{
return new UserDetailsService()
{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
System.out.println(username);
System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
};
}
}
}
Jwt 身份验证令牌过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
{
System.out.println("(((((((((((((((())))))))))))))");
final String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer "))
{
String authToken = header.substring(7);
System.out.println(authToken);
try
{
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
{
// here username should be validated with database and get authorities from database if valid
// Say just to hard code sending same username received
if (jwtTokenUtil.validateToken(authToken, username))
{
List<GrantedAuthority> authList = new ArrayList<>();
authList.add(new SimpleGrantedAuthority("ROLE_APIUSER"));
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, authList);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
catch (Exception e)
{
System.out.println("Unable to get JWT Token, possibly expired");
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtility 类
@Component
public class JwtTokenUtil implements Serializable
{
private static final long serialVersionUID = 8544329907338151549L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
private String secret = "my-secret";
public String getUsernameFromToken(String token)
{
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token)
{
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver)
{
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token)
{
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token)
{
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String username)
{
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, username);
}
private String doGenerateToken(Map<String, Object> claims, String subject)
{
return "Bearer "+Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, String usernameFromToken)
{
final String username = getUsernameFromToken(token);
return (username.equals(usernameFromToken) && !isTokenExpired(token));
}
//To generate token for testing
public static void main(String[] args)
{
JwtTokenUtil tu = new JwtTokenUtil();
String s1 = tu.generateToken("hello");
System.out.println(s1);
String user = tu.getUsernameFromToken(s1);
System.out.println(user);
}
}
控制器/API
@RestController
public class HelloController
{
//Client certificate
@RequestMapping(path = "/v1/hello")
public String helloV1()
{
return "HELLO Version 1 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
}
//Client certificate
@RequestMapping(path = "/v1.5/hello")
public String helloV1Dot5()
{
return "HELLO Version 1.5 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
}
//Jwt
@RequestMapping(path = "/v2/hello")
public String helloV2()
{
return "HELLO Version 2 - "+SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
推荐阅读
- javascript - 如果用户不响应,Alexa 会发出错误(“技能响应被标记为失败”)
- c# - MethodInfo.Invoke 返回的对象与目标类型不匹配
- python - 如何在表示航班的字典中列出重复值?
- python - python numpy exponentially spaced samples between a start and end value
- c++ - 我应该如何运行游戏循环并更改其对象?
- javascript - 带有 javascript 的交互式日历
- javascript - 如何在另一个文件中获取组件的状态反应本机
- python - Groupby 前两个最早日期,然后是前两个日期之间的平均时间 - 熊猫
- php - 图像生成
- android - 使用 Kotlin 检索 Firebase 数据并为列表视图填充模型