草庐IT

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

coder 2024-03-09 原文

我正在开发一个Web应用程序,以公开由OAuth 2.0保护的许多RESTful服务。这是计划的体系结构:

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

2- OAuth资源服务器: Java Web应用程序,使用以下技术:

  • Jersey(用于实现和公开Web服务)
  • Spring Security(用于实现OAuth资源服务器部分)

  • 我已经看到了几个有关如何使用WSO2 IS作为授权服务器+ WSO2 ESB作为资源服务器来保护RESTful服务的示例(ex1ex2ex3等)。这不是我所需要的。

    不幸的是,授权服务器和资源服务器之间的交互超出了OAuth2 RFC的范围。因此,我找不到更多的外观。

    这是我的问题:
  • 如何配置spring安全性以充当资源服务器来验证由外部OAuth提供者发出的访问 token (例如WSO2 IS)?
  • 资源服务器应如何识别给定访问 token 的范围?
  • 如何识别从WSO2 IS获得访问 token 的资源所有者?

  • 谢谢

    最佳答案

    经过研究后,我想出了方法。该解决方案分为两个主要部分: WSO2 IS配置资源服务器配置

    基本方案如下:

    1-客户端(例如,移动应用程序)通过向资源服务器(在我的情况下为Java Web应用程序)发送请求来消耗安全资源(例如,Web服务)。

    2-资源服务器验证请求中的“授权” header 并提取访问 token 。

    3-资源服务器通过将访问 token 发送到授权服务器(WSO2 IS)来验证访问 token 。

    4-授权服务器以验证响应进行响应。

    5-资源服务器验证响应并决定是授予还是拒绝对所请求资源的访问。

    在我的演示中,我使用了WSO2 IS 5.0.0和Spring security 3.1.0。

    1- WSO2 IS配置

    WSO2 IS将充当授权服务器。因此,应将其配置为支持OAuth 2.0。为此,应添加新的服务提供商并将其配置如下:

    (a)登录到WSO2 IS管理控制台。

    (b)添加一个新的服务提供商,并为其提供名称和描述。

    (c)在入站身份验证配置下 >> OAuth/OpenID Connect配置 >>单击配置

    (d)如以下屏幕截图所示配置OAuth 2.0提供程序,然后单击添加。我们需要密码授予类型,它映射到资源所有者密码凭证授予类型。最适合我的情况(确保Web服务安全)。

    (e)在 OAuth/OpenID Connect配置下,您会找到 OAuth客户端 key OAuth客户端 key 。它们与用户名,密码和范围一起使用以生成访问 token 。

    2-资源服务器配置

    如前所述,该演示Java Web应用程序将同时充当资源服务器和客户端。为了充当资源服务器,Spring安全需要知道如何验证访问 token 。因此,应提供 token 服务实现。

    (a)将spring配置为充当资源服务器。这是一个示例配置:

    <?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>
    

    在这里,配置了使用 token 服务实现 TokenServiceWSO2 资源服务器资源服务器标记实际上已转换为安全过滤器。将拦截模式添加到“/services/**”,并将资源服务器过滤器添加到链。

    (b)实现OAuth 2.0 token 服务 ResourceServerTokenServices 。该实现将以访问 token 作为输入,将其传递给WSO2 IS公开的 OAuth2TokenValidationService 服务,验证响应并返回包含有关 token 的发行者,有效性,范围,相应的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
    @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 类保存 token 验证响应中返回的基本数据。
    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保护的资源。基本上,将Web服务配置为使用根URL路径“/services/**”保护。在演示中,我使用了Jersey。

    4-测试客户端应用程序

    最后一步是使用 protected Web服务。这是通过将授权 header 添加到带有值“ ”的请求中来完成的,例如“承载7fbd71c5b28fdf0bdb922b07915c4d5 ”。

    P.S.所描述的示例仅用于说明目的。它可能缺少某些实现,异常处理等。

    关于java - 使用Spring Security + WSO2身份服务器的OAuth 2.0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27551358/

    有关java - 使用Spring Security + WSO2身份服务器的OAuth 2.0的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    9. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

      我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

    10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    随机推荐