简体   繁体   English

OAuth 2.0 使用 Spring Security + WSO2 身份服务器

[英]OAuth 2.0 using Spring Security + WSO2 Identity Server

I'm developing a web application to expose a number of RESTful services secured by OAuth 2.0.我正在开发一个 Web 应用程序来公开许多受 OAuth 2.0 保护的 RESTful 服务。 Here is the planned architecture:这是计划的架构:

1- OAuth Authorization Provider: WSO2 Identity Server (IS) 1- OAuth 授权提供者: WSO2 身份服务器 (IS)

2- OAuth Resource Server: Java web application using the following technologies: 2- OAuth 资源服务器:使用以下技术的 Java Web 应用程序:

  • Jersey (to implement and expose the web services) Jersey(实现和公开 Web 服务)
  • Spring Security (to implement the OAuth Resource Server part) Spring Security(实现 OAuth Resource Server 部分)

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.我已经看到了几个关于如何使用 WSO2 IS 作为授权服务器 + WSO2 ESB 作为资源服务器来保护 RESTful 服务的示例( ex1ex2ex3等...)。 This is NOT what I need in my case.在我的情况下,这不是我所需要的。

Unfortunately, the interaction between the authorization server and the resource server is beyond the scope of the OAuth2 RFC .不幸的是,授权服务器和资源服务器之间的交互超出了OAuth2 RFC的范围。 So, I couldn't find much about how should it look like.所以,我找不到太多关于它应该是什么样子的信息。

Here are my questions:以下是我的问题:

  • How to configure spring security to act as a resource server to validate an access token issued by an external OAuth provider (eg WSO2 IS)?如何配置 Spring Security 作为资源服务器来验证由外部 OAuth 提供者(例如 WSO2 IS)发布的访问令牌?
  • How should the resource server identify the scope of a given access token?资源服务器应该如何识别给定访问令牌的范围?
  • How to identify the resource owner given an access token from WSO2 IS?给定来自 WSO2 IS 的访问令牌,如何识别资源所有者?

Thanks谢谢

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 .该解决方案分为 2 个主要部分: WSO2 IS 配置资源服务器配置

The basic scenario goes as follows:基本场景如下:

1- A client (eg mobile app) consume a secured resource (eg web service) by sending a request to the resources sever (Java web application in my case). 1- 客户端(例如移动应用程序)通过向资源服务器(在我的情况下为 Java Web 应用程序)发送请求来使用安全资源(例如 Web 服务)。

2- The resources server validates the "Authorization" header in the request and extracts the access token. 2- 资源服务器验证请求中的“授权”标头并提取访问令牌。

3- The resources server validates the access token by sending it to the authorization server (WSO2 IS). 3- 资源服务器通过将访问令牌发送到授权服务器(WSO2 IS)来验证访问令牌。

4- The authorization server responds with validation response. 4- 授权服务器以验证响应进行响应。

5- The resources server validates the response and decides whether to grant or deny access to the requested resource. 5- 资源服务器验证响应并决定是授予还是拒绝对所请求资源的访问。

In my demo, I used WSO2 IS 5.0.0 and Spring security 3.1.0.在我的演示中,我使用了 WSO2 IS 5.0.0 和 Spring security 3.1.0。


1- WSO2 IS Configuration 1- WSO2 IS 配置

WSO2 IS will act as the authorization server . WSO2 IS 将充当授权服务器 So, it should be configured to support OAuth 2.0.因此,应将其配置为支持 OAuth 2.0。 To do so, a new service provider should be added and configured as follows:为此,应添加和配置新的服务提供者,如下所示:

(a) Login to WSO2 IS management console. (a) 登录 WSO2 IS 管理控制台。

(b) Add a new service provider and give it a name and description. (b) 添加一个新的服务提供者并给它一个名称和描述。

服务提供者配置:截图 1

(c) Under Inbound Authentication Configuration >> OAuth/OpenID Connect Configuration >> Click Configure . (c) 在Inbound Authentication Configuration >> OAuth/OpenID Connect Configuration >> 点击Configure 下

(d) Configure OAuth 2.0 provider as shown in the below screenshot and click Add . (d) 配置 OAuth 2.0 提供程序,如下面的屏幕截图所示,然后单击添加 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).它最适合我的情况(保护 Web 服务)。

服务提供者配置:截图 2

(e) Under OAuth/OpenID Connect Configuration , you'll find OAuth Client Key and OAuth Client Secret generated. (e) 在OAuth/OpenID Connect Configuration 下,您会发现生成的OAuth Client KeyOAuth Client Secret They are used along with username, password, and scope to generate access tokens.它们与用户名、密码和范围一起用于生成访问令牌。


2- Resources Server Configuration 2-资源服务器配置

As mentioned earlier, the demo Java web application will act as Resources server and client at the same time.如前所述,演示 Java Web 应用程序将同时充当资源服务器和客户端。 To act as resources server, Spring security needs to know how to validate access tokens.要充当资源服务器,Spring 安全性需要知道如何验证访问令牌。 So, a token services implementation should be provided.因此,应提供令牌服务实现。

(a) Configure spring to act as resources server. (a) 配置 spring 作为资源服务器。 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>

Here, a resource-server that uses a token services implementation TokenServiceWSO2 is configured.这里配置了一个使用令牌服务实现TokenServiceWSO2资源服务器 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.拦截模式被添加到“/services/**”并且资源服务器过滤器被添加到链中。

(b) Implement OAuth 2.0 token services ResourceServerTokenServices . (b) 实现 OAuth 2.0 令牌服务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, ...该实现将以访问令牌作为输入,将其传递给 WSO2 IS 公开的OAuth2TokenValidationService服务,验证响应并返回一个处理过的对象,其中包含有关令牌颁发者、有效性、范围、相应 JWT 令牌的基本数据,...

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 class implements the logic to call WSO2 IS's web service OAuth2TokenValidationService TokenValidatorWSO2类实现了调用 WSO2 IS 的 Web 服务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 class holds the basic data returned in token validation response. TokenValidationResponse类保存令牌验证响应中返回的基本数据。

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- Client Application Configuration 3- 客户端应用程序配置

The last step is to configure the resources to be protected by OAuth 2.0.最后一步是配置要受 OAuth 2.0 保护的资源。 Basically, configure the web services to be secured with a root URL path "/services/**".基本上,使用根 URL 路径“/services/**”配置要保护的 Web 服务。 In my demo, I used Jersey.在我的演示中,我使用了 Jersey。


4- Test The Client Application 4- 测试客户端应用程序

The last step is to consume the secured web services.最后一步是使用受保护的 Web 服务。 This is done by adding Authorization header to the request with value "这是通过将Authorization标头添加到具有值“的请求”来完成的 ", for example " bearer 7fbd71c5b28fdf0bdb922b07915c4d5 ". ”,例如“承载 7fbd71c5b28fdf0bdb922b07915c4d5 ”。


PS The described sample is just for clarification purposes. PS 所描述的示例仅用于说明目的。 It may be missing some implementations, exception handling, ... Kindly comment for further inquiries.它可能缺少一些实现,异常处理,......请评论以进一步查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM