spring|OAuth2授权客户端访问资源服务

OAuth客户端访问资源服务 一、简介 在单点登录一文,我们是通过注解@EnableOAuth2Sso实现单点登录的,我们了解到OAuth2获取token的方式是通过OAuth2RestOperations请求授权服务获取授权码的模式实现的,当授权服务认证通过事后携带code转发到重定向地址(如客户端服务器的/login地址),客户端获取到code之后,在通过code交换到token。最后通过token的权限鉴定用户是否能否访问对应资源;
@EnableOAuth2Sso实现的原理是通过OAuth2RestOperations远程调用授权服务来与授权服务直接交互,使用OAuth2AuthenticationProcessingFilter拦截授权服务发送过来的授权码并进行code交换token,使用RemoteTokenServices对token请求进行封装;
@EnableOAuth2Client实现原理与@EnableOAuth2Sso完全一致。不一样的是在OAuth2ClientAuthenticationProcessingFilter拦截认证成功换取token之后会调转到客户端的某个默认页面,所以@EnableOAuth2Client一般用作客户端网页资源服务;
二、实现 通过对授权码模式交换token的过程,我们可以知道要实现client的主要思路:

  • 需要创建一个redirectUri的controller或者filter进行处理授权服务返回的code
  • 根据返回的授权码code去授权服务请求token
  • 获取token之后将token与用户绑定
  • 使用token去获取授权的资源保持下来
实际上在授权client默认实现是通过如下几个对象实现:
  • OAuth2RestTemplate
    它封装获取token方法,对rest template的封装,为获取token等提供便捷方法
  • DefaultUserInfoRestTemplateFactory
    DefaultUserInfoRestTemplateFactory实例化OAuth2RestTemplate,提供了OAuth2RestTemplate
  • ResourceServerTokenServicesConfiguration
    配置创建DefaultUserInfoRestTemplateFactory给resource server用
  • OAuth2ClientAuthenticationProcessingFilter
    它的构造器需要传入defaultFilterProcessesUrl,用于指定这个filter拦截哪个url,它依赖OAuth2RestTemplate来获取token,依赖ResourceServerTokenServices进行校验token
经过上面的分析,如果我们自己实现则主要是配置3个关键对象:
  • OAuth2RestTemplate
    获取token
  • ResourceServerTokenServices
    校验token
  • OAuth2ClientAuthenticationProcessingFilter
    拦截redirectUri,根据authentication code获取token(依赖前面两个对象)
如我们访问客户端资源发现未授权自动到授权服务获取授权码实例:
http://127.0.0.1:7000/oauth/authorize?client_id=my_client_id&redirect_uri=http://localhost:7001/demon/login&response_type=code&state=7mzD9P

根据以上分析,我们新建一个配置类,创建以上3个对象:
package com.easystudy.config; import java.io.IOException; import java.util.Arrays; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.token.AccessTokenProviderChain; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; /** * @文件名称: OAuth2ClientConfig.java * @功能描述: * @版权信息:www.easystudy.com * @技术交流:961179337(QQ群) * @编写作者:lixx2048@163.com * @联系方式:941415509(QQ) * @开发日期:2020年8月1日 * @历史版本: V1.0 * @备注信息: */ @Configuration @EnableOAuth2Client public class OAuth2ClientConfig { /** * @功能描述: 创建token认证远程调用http模板 * @版权信息:www.easystudy.com * @编写作者:lixx2048@163.com * @开发日期:2020年8月1日 * @备注信息: */ @Bean public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {AuthorizationCodeAccessTokenProvider authCodeProvider = new AuthorizationCodeAccessTokenProvider(); authCodeProvider.setStateMandatory(false); AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(authCodeProvider)); OAuth2RestTemplate template = new OAuth2RestTemplate(details, context); template.setAccessTokenProvider(provider); return template; } /** * @功能描述: 注册处理redirect uri的filter * @版权信息:www.easystudy.com * @编写作者:lixx2048@163.com * @开发日期:2020年8月1日 * @备注信息: 拦截redirectUri,根据授权服务器返回的授权码code获取token */ @Bean public OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter( OAuth2RestTemplate oauth2RestTemplate, RemoteTokenServices tokenService) {// 创建重定向URL过滤器 OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter("/login"); // 设置token远程调用接口 filter.setTokenServices(tokenService); // 设置远程调用使用的模板 filter.setRestTemplate(oauth2RestTemplate); // 设置回调成功的页面 filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler() { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 认证成功之后返回的主页 this.setDefaultTargetUrl("/home"); super.onAuthenticationSuccess(request, response, authentication); } }); return filter; } /** * @功能描述: 注册token检查服务-从远程授权服务获取用户认证信息 * @版权信息:www.easystudy.com * @编写作者:lixx2048@163.com * @开发日期:2020年8月1日 * @备注信息: */ @Bean @Primary public RemoteTokenServices tokenService(OAuth2ProtectedResourceDetails details) { RemoteTokenServices tokenService = new RemoteTokenServices(); tokenService.setCheckTokenEndpointUrl("http://127.0.0.1:7000/oauth/check_token"); tokenService.setClientId(details.getClientId()); tokenService.setClientSecret(details.getClientSecret()); return tokenService; } // /** //* @功能描述: 授权客户端详情 //* @版权信息:www.easystudy.com //* @编写作者:lixx2048@163.com //* @开发日期:2020年8月1日 //* @备注信息: //*/ //@Bean //@ConfigurationProperties("security.oauth2.client") //public AuthorizationCodeResourceDetails google() { //return new AuthorizationCodeResourceDetails(); //} // ///** //* @功能描述: 授权资源服务配置 //* @版权信息:www.easystudy.com //* @编写作者:lixx2048@163.com //* @开发日期:2020年8月1日 //* @备注信息: //*/ //@Bean //@ConfigurationProperties("security.oauth2.resource") //public ResourceServerProperties googleResource() { //return new ResourceServerProperties(); //} }

创建这3个对象之后,我们就拥有了授权客户端处理code换取token的过程了,如果登录成功则默认进入该客户端前端页面/home;
浏览器访问客户端资源权限配置:
package com.easystudy.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; /** * @文件名称: WebSecurityConfig.java * @功能描述: WebSecurity安全配置 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写作者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年7月26日 * @备注信息: 任何需要身份验证的请求都将被重定向到授权服务器 */ @Configuration @EnableOAuth2Sso public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 自定义重定向拦截过滤器 @Autowired private OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter; /** * @功能描述: 受保护资源访问策略配置 * @编写作者: lixx2048@163.com * @开发日期: 2020年7月26日 * @历史版本: V1.0 * @参数说明: * @返回值: */ @Override protected void configure(HttpSecurity http) throws Exception { // 资源访问安全策略 ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); registry .anyRequest() .authenticated() //.permitAll() .and() // 配置自定义过滤器-注意在BasicAuthenticationFilter拦截之前处理 .addFilterBefore(oauth2ClientAuthenticationProcessingFilter, BasicAuthenticationFilter.class) // 跨域请求配置 .csrf().disable(); }/** * @功能描述: 静态资源忽略放行配置 * @编写作者: lixx2048@163.com * @开发日期: 2020年7月26日 * @历史版本: V1.0 * @参数说明: * @返回值: */ @Override public void configure(WebSecurity web) throws Exception { // 放行静态资源,否则添加oauth2情况下无法显示 web.ignoring().antMatchers("/favor.ico", "/favicon.ico","/v2/api-docs", "/swagger-resources/configuration/ui", "/swagger-resources","/swagger-resources/configuration/security", "/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**", "**/favicon.ico", "/index"); } }

这里,我配置的所有资源都需要认证访问。
因为我们拥有了自定义的OAuth2RestTemplate,我们授权客户端就可以通过OAuth2RestTemplate对远程资源服务器发起授权请求,请求实例如下:
package com.easystudy.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; /**@文件名称: TestController.java * @功能描述: TODO(用一句话描述该文件做什么) * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写作者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年7月27日 * @历史版本: V1.0 */ @RestController @RequestMapping("/test") @Api(value = "https://www.it610.com/article/OAuth2 Client测试接口文档", tags = "OAuth2 Client测试接口文档") public class TestController { @Autowired OAuth2RestTemplate oAuth2RestTemplate; /** * @功能描述: 访问资源服务 * @版权信息:www.easystudy.com * @编写作者:lixx2048@163.com * @开发日期:2020年8月1日 * @备注信息: */ @GetMapping("/{id}") public String getDemoAuthResource(@PathVariable Long id){ ResponseEntity responseEntity = oAuth2RestTemplate.getForEntity("http://localhost:7002/test/hi?name=lixx", String.class); return responseEntity.getBody(); } @GetMapping("/hi") @ApiOperation(value="https://www.it610.com/article/打招呼1", notes="打招呼1") public String hi(@RequestParam(name = "name", required = true) String name){ return "hi " + name; } }

若上所示,当我们登陆认证之后,再次使用浏览器对接口/test/1发起请求,可以看到客户端可以从资源服务获取到对应的信息。 其中http://localhost:7002/test/hi?name=lixx为资源服务的地址。
其中客户端配置:
server: port: 7001 servlet: context-path: /demon spring: application: name: resource #渲染模板配置 thymeleaf: #模板的模式,支持 HTML, XML TEXT JAVASCRIPT mode: HTML5 #编码 可不用配置 encoding: UTF-8 #内容类别,可不用配置 #开发配置为false,避免修改模板还要重启服务器 cache: false #配置模板路径,默认是templates,可以不用配置 prefix: classpath:/templates #默认类型 servlet: content-type: text/html #oauth2客户端 security: oauth2: resource: filter-order: 3 id: resource_server_id tokenInfoUri: http://127.0.0.1:7000/oauth/check_token preferTokenInfo: true #user-info-uri: http://127.0.0.1:7000/user/principal #prefer-token-info: false #如下可暂时不用配置-仅做保留 client: clientId: my_client_id clientSecret: my_client_secret accessTokenUri: http://127.0.0.1:7000/oauth/token userAuthorizationUri: http://127.0.0.1:7000/oauth/authorize pre-established-redirect-uri: http://localhost:70001/callback #日志打印配置 logging: config: classpath:logback.xml #actuator: info: author: name: 李祥祥 email: lixiang6153@126.com hostory: - date: 2018-08-28 10:10:10 user: lixiang6153@126.com - date: 2018-07-10 08:30:00 user: test@126.com build: artifact: "@project.artifactId@" name: "@project.name@" version: "@project.version@"

认证成功之后的默认页面/home(使用thymleaf):
Home Page - 锐客网 /test/1 /test/hi?name=lixx

三、测试 我准备了3台服务器:
  • 7000端口为授权服务
  • 7001端口为授权客户端
  • 7002端口为资源客户端
1、访问客户端地址:
http://localhost:7001/demon/test/hi?name=lixx
spring|OAuth2授权客户端访问资源服务
文章图片

2、未登录状态,转向授权服务进行用户登录授权
spring|OAuth2授权客户端访问资源服务
文章图片

3、用户登录授权成功,返回客户端主页/home
spring|OAuth2授权客户端访问资源服务
文章图片

4、客户端网页访问接口,接口远程调用资源服务返回结果
spring|OAuth2授权客户端访问资源服务
文章图片

【spring|OAuth2授权客户端访问资源服务】通过最后一步可以看到客户端访问资源服务内部携带了token过去,该token被资源服务获取并从授权服务获取用户认证信息,鉴定具有权限访问然后返回对应结果,这个过程我在单点登录以及资源服务两篇文章中有讲解到,比较复杂,这里不再赘述。
源码获取、合作、技术交流请获取如下联系方式:
QQ交流群:961179337
spring|OAuth2授权客户端访问资源服务
文章图片

微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:lixx2048@163.com

    推荐阅读