【发布时间】:2019-10-26 06:41:36
【问题描述】:
上下文:我正在编写一个 Web 应用程序,旨在为两个不同的 API 提供服务,每个 API 都有自己的身份验证过滤器。两个过滤器都处理和验证 JWT 令牌,但是令牌本身包含不同的有效负载并且来自不同的身份验证源。此外,即使令牌不同且来源不同,但它们共享相同的登录凭据,并使用相同的密钥进行保护。
即:您可以使用相同的凭据访问这些身份验证 URL 中的任何一个,然后返回具有完全不同负载的不同 JWT 令牌。
/auth/auth1/login /auth/auth2/登录
问题: 我遇到的问题是,如果我对其中一个进行身份验证,我就可以访问另一个。即使不提供令牌。
意思是如果我去 /requests/something,使用来自身份验证提供程序 1 的承载令牌进行身份验证,然后转到 /ims/oneroster/v1p1/somethingElse(不从身份验证提供程序 2 传递不同的令牌)我能够访问数据,即使我没有使用与该路径关联的过滤器进行身份验证。
目前,我知道确保每个过滤器正确检查用户令牌是否有效的唯一方法是将 SecurityContextHolder.clearContext(); 放在每个过滤器的顶部过滤器 doFilterInternal 方法。但是,我很确定我不应该这样做。
谁能看到我下面的问题,或者提出一些建议?
SecurityConfig.class
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
//https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#multiple-httpsecurity
@Configuration @Order(1)
public static class XPressWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final CacheService cacheService;
public XPressWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/requests/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilter(new JWTAuthorizationFilter(authenticationManagerBean(), cacheService))
//.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
;
}
}
@Configuration @Order(2)
public static class OneRosterWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final CacheService cacheService;
public OneRosterWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/ims/oneroster/v1p1/**")
.authorizeRequests().anyRequest().authenticated()
.and().addFilter(new OneRosterAuthorizationFilter(authenticationManagerBean(), cacheService))
;
}
}
}
OneRosterAuthorizationFilter.class
public class OneRosterAuthorizationFilter extends BasicAuthenticationFilter {
private final CacheService cacheService;
public OneRosterAuthorizationFilter(AuthenticationManager authManager, CacheService cacheService) {
super(authManager);
this.cacheService = cacheService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
logger.debug("GOING TO ONEROSTER FILTER");
AuthRequest authRequest = new AuthRequest(req);
if(authRequest.isAuthEnabled()) {
if(authRequest.isHeader() || (authRequest.isParameter() && authRequest.isAllowTokenParameter())) {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req, authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req, AuthRequest authRequest) {
if(StringUtils.isBlank(authRequest.getToken())) {
return null; //Token was blank... 403 Forbidden
}
DecodedToken decodedToken = TokenDecoder.decodeToken(authRequest.getToken());
Application application = null;
if(decodedToken != null) {
application = new Application(decodedToken.getAppId(), decodedToken.getToken(), cacheService);
}
try {
if(!System.getenv("provider_id").equalsIgnoreCase(decodedToken.getProviderId())) {
throw new JWTVerificationException("Provider Ids Don't Match....");
}
if(application != null && StringUtils.isNotBlank(application.getApp().getProviderSecret())) {
JWT.require(Algorithm.HMAC256(application.getApp().getProviderSecret().getBytes()))
.withIssuer(PropertiesLoader.getInstance().getProperty("security.auth.jwt.issuer"))
.build().verify(authRequest.getToken());
return new UsernamePasswordAuthenticationToken(application, decodedToken.getToken(), getACLs(application));
}
}
catch (JWTVerificationException exception) {
//https://medium.com/fullstackblog/spring-security-jwt-token-expired-custom-response-b85437914b81
req.setAttribute("JWTVerificationException", exception.getMessage());
return null;
}
return null; //DecodedToken or Application was null... 403 Forbidden
}
private Collection<GrantedAuthority> getACLs(Application application) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
application.getPermissions().forEach(pathPermission -> {
if(pathPermission.getGet()) {
grantedAuthorities.add(new SimpleGrantedAuthority("get:" + pathPermission.getPath()));
}
if(pathPermission.getPost()) {
grantedAuthorities.add(new SimpleGrantedAuthority("post:" + pathPermission.getPath()));
}
if(pathPermission.getPut()) {
grantedAuthorities.add(new SimpleGrantedAuthority("put:" + pathPermission.getPath()));
}
if(pathPermission.getDelete()) {
grantedAuthorities.add(new SimpleGrantedAuthority("delete:" + pathPermission.getPath()));
}
});
return grantedAuthorities;
}
}
JWTAuthorizationFilter.class
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private final CacheService cacheService;
public JWTAuthorizationFilter(AuthenticationManager authManager, CacheService cacheService) {
super(authManager);
this.cacheService = cacheService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
logger.debug("GOING TO JWT FILTER");
AuthRequest authRequest = new AuthRequest(req);
if(authRequest.isAuthEnabled()) {
if(authRequest.isHeader() || (authRequest.isParameter() && authRequest.isAllowTokenParameter())) {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req, authRequest);
if(authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req, AuthRequest authRequest) {
if(StringUtils.isBlank(authRequest.getToken())) {
return null; //Token was blank... 403 Forbidden
}
DecodedToken decodedToken = TokenDecoder.decodeToken(authRequest.getToken());
Application application = null;
if(decodedToken != null) {
application = new Application(decodedToken.getApplication_id(), decodedToken.getToken(), cacheService);
}
try {
if(application != null && StringUtils.isNotBlank(application.getApp().getProviderSecret())) {
JWT.require(Algorithm.HMAC256(application.getApp().getProviderSecret().getBytes()))
.withIssuer(PropertiesLoader.getInstance().getProperty("security.auth.jwt.issuer"))
.build().verify(authRequest.getToken());
return new UsernamePasswordAuthenticationToken(application, decodedToken.getToken(), getACLs(application));
}
}
catch (JWTVerificationException exception) {
//https://medium.com/fullstackblog/spring-security-jwt-token-expired-custom-response-b85437914b81
req.setAttribute("JWTVerificationException", exception.getMessage());
return null;
}
return null; //DecodedToken or Application was null... 403 Forbidden
}
private Collection<GrantedAuthority> getACLs(Application application) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
application.getPermissions().forEach(pathPermission -> {
if(pathPermission.getGet()) {
grantedAuthorities.add(new SimpleGrantedAuthority("get:" + pathPermission.getPath()));
}
if(pathPermission.getPost()) {
grantedAuthorities.add(new SimpleGrantedAuthority("post:" + pathPermission.getPath()));
}
if(pathPermission.getPut()) {
grantedAuthorities.add(new SimpleGrantedAuthority("put:" + pathPermission.getPath()));
}
if(pathPermission.getDelete()) {
grantedAuthorities.add(new SimpleGrantedAuthority("delete:" + pathPermission.getPath()));
}
});
return grantedAuthorities;
}
}
Note: Application is the class that implements UserDetails
【问题讨论】:
-
您需要 HTTP 会话吗?如果你的服务是无状态的,你就不会有这种问题。
-
目前我认为我不需要会话。但是假设我在未来做这件事会有多困难呢?另外,如果我可以让它无状态,我将如何解决当前问题?谢谢。
-
要使其无状态,您必须更改会话策略,请参阅SessionCreationPolicy。您还必须确保您的应用程序不会自行打开(您的代码)。
-
使用会话的最佳方式是将您的应用程序拆分为两个应用程序(在同一个容器或两个不同的容器中运行)。
-
谢谢@dur。我想我现在会尝试采用无状态方法,如果结果证明我需要会话,我会将它们分开。
标签: spring spring-boot spring-security