问题描述
我正在开发一个 Web 应用程序来公开许多受 OAuth 2.0 保护的 RESTful 服务.这是计划的架构:
I'm developing a web application to expose a number of RESTful services secured by OAuth 2.0. Here is the planned architecture:
1- OAuth 授权提供商: WSO2 身份服务器 (IS)
1- OAuth Authorization Provider: WSO2 Identity Server (IS)
2- OAuth 资源服务器:使用以下技术的 Java 网络应用程序:
2- OAuth Resource Server: Java web application using the following technologies:
- 泽西岛(实施和公开网络服务)
- Spring Security(实现 OAuth 资源服务器部分)
我看过几个例子(ex1, ex2, ex3 等...)了解如何使用 WSO2 IS 作为授权服务器 + WSO2 ESB 作为资源服务器来保护 RESTful 服务.就我而言,这不是我所需要的.
I've seen several examples (ex1, ex2, ex3, etc...) on how to secure RESTful services using WSO2 IS as an authorization server + WSO2 ESB as a resource server. This is NOT what I need in my case.
遗憾的是,授权服务器和资源服务器之间的交互超出了OAuth2 RFC.所以,我找不到太多关于它应该是什么样子的信息.
Unfortunately, the interaction between the authorization server and the resource server is beyond the scope of the OAuth2 RFC. So, I couldn't find much about how should it look like.
这是我的问题:
- 如何配置 Spring Security 作为资源服务器来验证由外部 OAuth 提供者(例如 WSO2 IS)发布的访问令牌?
- 资源服务器应该如何识别给定访问令牌的范围?
- 如何根据 WSO2 IS 的访问令牌识别资源所有者?
谢谢
推荐答案
经过一番研究,我想出了如何去做.该解决方案分为 2 个主要部分:WSO2 IS 配置 &资源服务器配置.
After doing some research, I figured out how to do it. The solution is divided into 2 main parts: WSO2 IS configuration & Resources server configuration.
基本场景如下:
1- 客户端(例如移动应用程序)通过向资源服务器(在我的例子中是 Java Web 应用程序)发送请求来使用安全资源(例如 Web 服务).
1- A client (e.g. mobile app) consume a secured resource (e.g. web service) by sending a request to the resources sever (Java web application in my case).
2- 资源服务器验证请求中的授权"标头并提取访问令牌.
2- The resources server validates the "Authorization" header in the request and extracts the access token.
3- 资源服务器通过将访问令牌发送到授权服务器(WSO2 IS)来验证访问令牌.
3- The resources server validates the access token by sending it to the authorization server (WSO2 IS).
4- 授权服务器以验证响应进行响应.
4- The authorization server responds with validation response.
5- 资源服务器验证响应并决定是授予还是拒绝对所请求资源的访问.
5- The resources server validates the response and decides whether to grant or deny access to the requested resource.
在我的演示中,我使用了 WSO2 IS 5.0.0 和 Spring security 3.1.0.
In my demo, I used WSO2 IS 5.0.0 and Spring security 3.1.0.
WSO2 IS 将充当授权服务器.因此,应将其配置为支持 OAuth 2.0.为此,应按如下方式添加和配置新的服务提供者:
WSO2 IS will act as the authorization server. So, it should be configured to support OAuth 2.0. To do so, a new service provider should be added and configured as follows:
(a) 登录 WSO2 IS 管理控制台.
(a) Login to WSO2 IS management console.
(b) 添加一个新的服务提供者并为其指定名称和描述.
(b) Add a new service provider and give it a name and description.
(c) 在入站身份验证配置下>> OAuth/OpenID Connect 配置 >> 点击配置.
(c) Under Inbound Authentication Configuration >> OAuth/OpenID Connect Configuration >> Click Configure.
(d) 如下面的屏幕截图所示配置 OAuth 2.0 提供程序,然后单击添加.我们需要密码授权类型,它映射到资源所有者密码凭据授权类型.它最适合我的情况(保护网络服务).
(d) Configure OAuth 2.0 provider as shown in the below screenshot and click Add. We'll need Password grant type which maps to Resource Owner Password Credentials grant type. It is best suited for my case (securing web services).
(e) 在 OAuth/OpenID Connect Configuration 下,您会发现生成了 OAuth Client Key 和 OAuth Client Secret.它们与用户名、密码和范围一起用于生成访问令牌.
(e) Under OAuth/OpenID Connect Configuration, you'll find OAuth Client Key and OAuth Client Secret generated. They are used along with username, password, and scope to generate access tokens.
如前所述,演示 Java Web 应用程序将同时充当资源服务器和客户端.要充当资源服务器,Spring 安全性需要知道如何验证访问令牌.因此,应提供令牌服务实现.
As mentioned earlier, the demo Java web application will act as Resources server and client at the same time. To act as resources server, Spring security needs to know how to validate access tokens. So, a token services implementation should be provided.
(a) 配置 spring 作为资源服务器.这是一个示例配置:
(a) Configure spring to act as resources server. Here is a sample configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/security/oauth2
http://www.springframework.org/schema/security/spring-security-oauth2.xsd">
<bean id="tokenServices" class="com.example.security.oauth2.wso2.TokenServiceWSO2" />
<bean id="authenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
<security:authentication-manager alias="authenticationManager" />
<oauth2:resource-server id="resourcesServerFilter" token-services-ref="tokenServices" />
<security:http pattern="/services/**" create-session="stateless" entry-point-ref="authenticationEntryPoint" >
<security:anonymous enabled="false" />
<security:custom-filter ref="resourcesServerFilter" before="PRE_AUTH_FILTER" />
<security:intercept-url pattern="/services/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
</security:http>
</beans>
此处配置了使用令牌服务实现 TokenServiceWSO2 的 resource-server.resource-server 标记实际上已转换为安全过滤器.拦截模式被添加到/services/**"并且资源服务器过滤器被添加到链中.
Here, a resource-server that uses a token services implementation TokenServiceWSO2 is configured. The resource-server tag is actually transformed to a security filter. An interception pattern is added to "/services/**" and the resources sever filter is added to the chain.
(b) 实施 OAuth 2.0 令牌服务ResourceServerTokenServices.该实现将以访问令牌作为输入,将其传递给 WSO2 IS 公开的 OAuth2TokenValidationService 服务,验证响应并返回一个处理过的对象,其中包含有关令牌颁发者、有效性、范围、对应的基本数据JWT 令牌,...
(b) Implement OAuth 2.0 token services ResourceServerTokenServices. The implementation will take an access token as an input, pass it to OAuth2TokenValidationService service exposed by WSO2 IS, validate the response and return a processed object containing the basic data about the token's issuer, validity, scope, corresponding JWT token, ...
public class TokenServiceWSO2 implements ResourceServerTokenServices {
@Autowired
TokenValidatorWSO2 tokenValidatorWSO2;
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
try {
TokenValidationResponse validationResponse = tokenValidatorWSO2.validateAccessToken(accessToken);
OAuth2Request oAuth2Request = new OAuth2Request(null, null, null, true, validationResponse.getScope(), null, null, null,null);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(validationResponse.getAuthorizedUserIdentifier(), null, null);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
return oAuth2Authentication;
} catch (ApplicationException ex) {
// Handle exception
}
}
public OAuth2AccessToken readAccessToken(String accessToken) {
// TODO Add implementation
}
}
TokenValidatorWSO2 类实现了调用 WSO2 IS 的 Web 服务 OAuth2TokenValidationService
TokenValidatorWSO2 class implements the logic to call WSO2 IS's web service OAuth2TokenValidationService
@Component
public class TokenValidatorWSO2 implements OAuth2TokenValidator{
private static final Logger logger = Logger.getLogger(TokenValidatorWSO2.class);
@Value("${server_url}")
private String serverUrl;
@Value("${validation_service_name}")
private String validationServiceName;
@Value("${comsumer_key}")
private String consumerKey;
@Value("${admin_username}")
private String adminUsername;
@Value("${admin_password}")
private String adminPassword;
private OAuth2TokenValidationServiceStub stub;
private static final int TIMEOUT_IN_MILLIS = 15 * 60 * 1000;
public TokenValidationResponse validateAccessToken(String accessToken) throws ApplicationException {
logger.debug("validateAccessToken(String) - start");
if(stub == null) {
initializeValidationService();
}
OAuth2TokenValidationRequestDTO oauthRequest;
TokenValidationResponse validationResponse;
OAuth2TokenValidationRequestDTO_OAuth2AccessToken oAuth2AccessToken;
try {
oauthRequest = new OAuth2TokenValidationRequestDTO();
oAuth2AccessToken = new OAuth2TokenValidationRequestDTO_OAuth2AccessToken();
oAuth2AccessToken.setIdentifier(accessToken);
oAuth2AccessToken.setTokenType("bearer");
oauthRequest.setAccessToken(oAuth2AccessToken);
OAuth2TokenValidationResponseDTO response = stub.validate(oauthRequest);
if(!response.getValid()) {
throw new ApplicationException("Invalid access token");
}
validationResponse = new TokenValidationResponse();
validationResponse.setAuthorizedUserIdentifier(response.getAuthorizedUser());
validationResponse.setJwtToken(response.getAuthorizationContextToken().getTokenString());
validationResponse.setScope(new LinkedHashSet<String>(Arrays.asList(response.getScope())));
validationResponse.setValid(response.getValid());
} catch(Exception ex) {
logger.error("validateAccessToken() - Error when validating WSO2 token, Exception: {}", ex);
}
logger.debug("validateAccessToken(String) - end");
return validationResponse;
}
private void initializeValidationService() throws ApplicationException {
try {
String serviceURL = serverUrl + validationServiceName;
stub = new OAuth2TokenValidationServiceStub(null, serviceURL);
CarbonUtils.setBasicAccessSecurityHeaders(adminUsername, adminPassword, true, stub._getServiceClient());
ServiceClient client = stub._getServiceClient();
Options options = client.getOptions();
options.setTimeOutInMilliSeconds(TIMEOUT_IN_MILLIS);
options.setProperty(HTTPConstants.SO_TIMEOUT, TIMEOUT_IN_MILLIS);
options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, TIMEOUT_IN_MILLIS);
options.setCallTransportCleanup(true);
options.setManageSession(true);
} catch(AxisFault ex) {
// Handle exception
}
}
}
TokenValidationResponse 类保存令牌验证响应中返回的基本数据.
TokenValidationResponse class holds the basic data returned in token validation response.
public class TokenValidationResponse {
private String jwtToken;
private boolean valid;
private Set<String> scope;
private String authorizedUserIdentifier;
public String getJwtToken() {
return jwtToken;
}
public void setJwtToken(String jwtToken) {
this.jwtToken = jwtToken;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public Set<String> getScope() {
return scope;
}
public void setScope(Set<String> scope) {
this.scope = scope;
}
public String getAuthorizedUserIdentifier() {
return authorizedUserIdentifier;
}
public void setAuthorizedUserIdentifier(String authorizedUserIdentifier) {
this.authorizedUserIdentifier = authorizedUserIdentifier;
}
}
3- 客户端应用程序配置
最后一步是配置 OAuth 2.0 保护的资源.基本上,使用根 URL 路径/services/**"配置要保护的 Web 服务.在我的演示中,我使用了 Jersey.
3- Client Application Configuration
The last step is to configure the resources to be protected by OAuth 2.0. Basically, configure the web services to be secured with a root URL path "/services/**". In my demo, I used Jersey.
最后一步是使用受保护的 Web 服务.这是通过将 Authorization 标头添加到值为 "的请求中来完成的,例如bearer 7fbd71c5b28fdf0bdb922b07915c4d5".
The last step is to consume the secured web services. This is done by adding Authorization header to the request with value " ", for example "bearer 7fbd71c5b28fdf0bdb922b07915c4d5".
附言所描述的示例仅用于说明目的.它可能缺少一些实现,异常处理,......请评论以进一步查询.
P.S. The described sample is just for clarification purposes. It may be missing some implementations, exception handling, ... Kindly comment for further inquiries.
这篇关于OAuth 2.0 使用 Spring Security + WSO2 身份服务器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!