如何使用 Spring Security OAuth2 和 MITREID Connect Introspect 保护资源?
Posted
技术标签:
【中文标题】如何使用 Spring Security OAuth2 和 MITREID Connect Introspect 保护资源?【英文标题】:How to Protect a Resource using Spring Security OAuth2 and MITREID Connect Introspect? 【发布时间】:2017-08-01 05:23:13 【问题描述】:我们正在构建一个 REST 资源服务器(一个 Java 示例应用程序),我们计划使用 MITREID Connect 项目中提供的 RFC7662 定义的身份传播机制来保护它。我们测试了两种配置方法、XML 设置以及添加到资源服务器类的基于注释的设置(请参阅下面附加的示例代码)。
我们的测试显示 Spring Security 例程初始化成功,但是我们没有成功触发 Bearer 令牌通过授权头。请求和资源成功执行,但没有进行令牌解析和自省验证。请检查下面附加的配置设置和日志。
欢迎支持隔离组件(Spring Security、Spring Oauth2 和 Mitreid Connect Introspect)之间的缺失线路。
设置文件:spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.1.xsd">
<sec:http pattern="/css/**" security="none" />
<sec:http pattern="/js/**" security="none" />
<sec:http auto-config="true" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="oauthAuthenticationEntryPoint"
pattern="/rest/service/sample/restService">
<sec:custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
</sec:http>
<sec:authentication-manager alias="authenticationManager">
</sec:authentication-manager>
<!-- Begin OAuth2 Introspect configuration -->
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="W3IDRealm" />
</bean>
<oauth:resource-server id="resourceServerFilter"
token-services-ref="introspectingService" />
<bean id="introspectingService"
class="org.mitre.oauth2.introspectingfilter.IntrospectingTokenService">
<property name="introspectionConfigurationService" ref="staticIntrospectionConfigurationService">
</property>
</bean>
<!-- <oauth:resource
id="protectedResource"
access-token-uri="$oidc.tokenEndpointUri"
client-secret="$oidc.clientSecret"
client-id="$oidc.clientId"></oauth:resource> -->
<bean
class="org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService"
id="staticIntrospectionConfigurationService">
<property name="introspectionUrl" value="$oidc.introspectEndpointUri" />
<property name="clientConfiguration">
<bean class="org.mitre.oauth2.model.RegisteredClient">
<property name="clientId" value="$oidc.clientId" />
<property name="clientSecret" value="$oidc.clientSecret" />
</bean>
</property>
<!-- <property name="introspectionAuthorityGranter">
<bean class="org.mitre.oauth2.introspectingfilter.SimpleIntrospectionAuthorityGranter">
<property name="authorities">
<value>ROLE_API</value>
</property>
</bean>
</property> -->
</bean>
resource.java
package com.red.sampleoidcservice;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@EnableWebSecurity
@Configuration
@EnableResourceServer
public class RestController
private static final Logger logger = LoggerFactory.getLogger(RestController.class);
@RequestMapping(value = "/restService", method = RequestMethod.POST)
public @ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose)
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "\"status\":\"OK\"";
protected static class ResourceServer extends ResourceServerConfigurerAdapter
@Override
public void configure(HttpSecurity http) throws Exception
http.requestMatchers().antMatchers("/rest/service/sample/restService").and().authorizeRequests()
.anyRequest().access("#oauth2.hasScope('read')");
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception
resources.resourceId("W3IDRealm");
post.java
// HTTP POST request
private void sendPost(String token) throws Exception
try
token = "blablabla";
TrustManager[] trustAllCerts = new TrustManager[] new X509TrustManager()
public java.security.cert.X509Certificate[] getAcceptedIssuers()
return new X509Certificate[0];
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
;
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(sc, new String[] "TLSv1.2" , null,
org.apache.http.conn.ssl.SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(f).build();
HttpPost postRequest = new HttpPost("https://localhost:9444/rest/service/sample/restService");
postRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("client_id", clientId));
formparams.add(new BasicNameValuePair("client_secret", clientSecret));
formparams.add(new BasicNameValuePair("token", token));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "utf-8");
postRequest.setEntity(entity);
postRequest.setHeader("Authorization", "Bearer " + token + "");
HttpResponse response = httpClient.execute(postRequest, new BasicHttpContext());
int statusCode = response.getStatusLine().getStatusCode();
logger.info("HTTP status code : " + statusCode);
catch (Exception e)
logger.error(e.getMessage());
追踪:
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 5872 ms
调试:org.springframework.web.servlet.DispatcherServlet - Servlet 'appServlet' 配置成功 调试:org.springframework.web.context.support.StandardServletEnvironment - 添加具有最低搜索优先级的 [servletConfigInitParams] PropertySource 调试:org.springframework.web.context.support.StandardServletEnvironment - 添加具有最低搜索优先级的 [servletContextInitParams] PropertySource 调试:org.springframework.web.context.support.StandardServletEnvironment - 添加具有最低搜索优先级的 [jndiProperties] PropertySource 调试:org.springframework.web.context.support.StandardServletEnvironment - 添加具有最低搜索优先级的 [systemProperties] PropertySource 调试:org.springframework.web.context.support.StandardServletEnvironment - 添加具有最低搜索优先级的 [systemEnvironment] PropertySource 调试:org.springframework.web.context.support.StandardServletEnvironment - 使用 PropertySources [servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment] 初始化 StandardServletEnvironment 调试:org.springframework.web.filter.DelegatingFilterProxy - 初始化过滤器'springSecurityFilterChain' 调试:org.springframework.beans.factory.support.DefaultListableBeanFactory - 返回单例 bean 'org.springframework.security.filterChainProxy' 的缓存实例 调试:org.springframework.web.filter.DelegatingFilterProxy - 过滤器“springSecurityFilterChain”配置成功 调试:org.springframework.web.servlet.DispatcherServlet - 名为“appServlet”的 DispatcherServlet 处理 [/rest/service/sample/restService] 的 POST 请求 调试:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - 查找路径 /restService 的处理程序方法 调试:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - 返回处理程序方法 [public java.lang.String com.red.sampleoidcservice.RestController.restService(javax.servlet.http.HttpServletRequest,javax.servlet. http.HttpServletResponse)] 调试:org.springframework.beans.factory.support.DefaultListableBeanFactory - 返回单例 bean 'restController' 的缓存实例 INFO : com.red.sampleoidcservice.RestController - 调用休息服务 调试:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - 使用 [org.springframework.http.converter.StringHttpMessageConverter@6912d551] 将 ["status":"OK"] 写为 "text/plain" 调试:org.springframework.web.servlet.DispatcherServlet - Null ModelAndView 返回到名为“appServlet”的 DispatcherServlet:假设 HandlerAdapter 完成了请求处理 调试:org.springframework.web.servlet.DispatcherServlet - 成功完成请求 调试:org.springframework.beans.factory.support.DefaultListableBeanFactory - 返回单例 bean 'delegatingApplicationListener' 的缓存实例
找到解决方案
带注释的配置
/*******************************************************************************
* Copyright 2014 The MITRE Corporation
* and the MIT Kerberos and Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.RED.sampleoidcservice;
import java.io.IOException;
import java.security.Principal;
import java.util.Locale;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.oauth2.introspectingfilter.IntrospectingTokenService;
import org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.RegisteredClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.ModelAndView;
@Controller
@EnableWebSecurity
@Configuration
@EnableResourceServer // [2]
@ComponentScan( "com.RED.sampleoidcservice" )
public class ResourceServer extends ResourceServerConfigurerAdapter
private static final Logger logger = LoggerFactory.getLogger(ResourceServer.class);
@Value("$oidc.jwks.keys")
private String jwksString;
@Value("$oidc.introspectEndpointUri")
private String introspectURL;
@Value("$oidc.clientId")
private String clientId;
@Value("$oidc.clientSecret")
private String clientSecret;
IntrospectingTokenService introspectTokenService = new IntrospectingTokenService();
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView modelHome(Locale locale, Principal p)
logger.info("Initializing service resource");
ModelAndView model = new ModelAndView("/home.tiles");
return model;
@RequestMapping(value = "/jwk", method = RequestMethod.GET, produces = "application/json")
public @ResponseBody String jwk()
return jwksString;
@RequestMapping(value = "/restService", method = RequestMethod.POST)
public @ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose)
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
String token = headerAuth.split(" ")[1];
// introspectTokenService.readAccessToken(token);
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "\"status\":\"OK\"";
@Override
public void configure(HttpSecurity http) throws Exception
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
// http.addFilterBefore(new TokenExtractorFilter(), BasicAuthenticationFilter.class).requestMatchers()
// .antMatchers("/rest/service/sample/restService").and().authorizeRequests().anyRequest()
// .access("ROLE_API");
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception
resources.resourceId("W3IDRealm");
resources.tokenExtractor(new BearerTokenExtractor());
StaticIntrospectionConfigurationService introspectConfig = new StaticIntrospectionConfigurationService();
introspectConfig.setIntrospectionUrl(introspectURL);
RegisteredClient client = new RegisteredClient();
client.setClientId(clientId);
client.setClientSecret(clientSecret);
client.setTokenEndpointAuthMethod(AuthMethod.NONE);
introspectConfig.setClientConfiguration(client);
introspectTokenService.setIntrospectionConfigurationService(introspectConfig);
resources.tokenServices(introspectTokenService);
private static class OAuthRequestedMatcher implements RequestMatcher
public boolean matches(HttpServletRequest request)
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token")!=null;
return haveOauth2Token || haveAccessToken;
class TokenExtractorFilter extends OncePerRequestFilter implements Filter, InitializingBean
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
//UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
BearerTokenExtractor bte = new BearerTokenExtractor();
String mytoken = bte.extract(request).toString();
logger.info("Filter activated");
【问题讨论】:
【参考方案1】:我发布了一个使用基于注释的配置的解决方案,它帮助我们使用 Java 断点调试流程细节。使用 /introspect 的 OAuth2 Bearer 令牌验证现在正在运行。感谢 MITREID 提供的所有调试支持。请参阅报告最后部分中发布的代码解决方案,如上所示。
【讨论】:
以上是关于如何使用 Spring Security OAuth2 和 MITREID Connect Introspect 保护资源?的主要内容,如果未能解决你的问题,请参考以下文章
在没有 servlet api 的 webflux 项目中使用 OAuth2 和 Spring Security OAuth2 和 reactor netty
具有密码授予类型的 WebClient 的 Spring Security OAuth 客户端不要求新令牌
Spring security:Spring security 如何在 SessionRegistry 中注册新会话?
如何使用 Spring Security 模拟身份验证和授权
如何使用 Spring-Security 3 和 Hibernate 4 将 spring security xml 配置 hibernate 转换为 java config