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<HttpSecurity>.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 = "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<String> responseEntity = oAuth2RestTemplate.getForEntity("http://localhost:7002/test/hi?name=lixx", String.class);
        return responseEntity.getBody();
    }
    
	@GetMapping("/hi")
	@ApiOperation(value="打招呼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):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
		<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
		<meta name="viewport" content="width=device-width, initial-scale=1"/>
		<title>Home Page</title>
	</head>
	<body>
		<div class="container" align="center">
		    <h2><a href="/demon/test/1">/test/1</a></h2>
		    <h2><a href="/demon/test/hi?name=lixx">/test/hi?name=lixx</a></h2>
		</div>
	</body>
</html>

三、测试

我准备了3台服务器:

  • 7000端口为授权服务
  • 7001端口为授权客户端
  • 7002端口为资源客户端

1、访问客户端地址:

http://localhost:7001/demon/test/hi?name=lixx
在这里插入图片描述

2、未登录状态,转向授权服务进行用户登录授权
在这里插入图片描述

3、用户登录授权成功,返回客户端主页/home
在这里插入图片描述

4、客户端网页访问接口,接口远程调用资源服务返回结果
在这里插入图片描述

通过最后一步可以看到客户端访问资源服务内部携带了token过去,该token被资源服务获取并从授权服务获取用户认证信息,鉴定具有权限访问然后返回对应结果,这个过程我在单点登录以及资源服务两篇文章中有讲解到,比较复杂,这里不再赘述。

源码获取、合作、技术交流请获取如下联系方式:

QQ交流群:961179337
在这里插入图片描述

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳里的沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值