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去获取授权的资源保持下来
- OAuth2RestTemplate
它封装获取token方法,对rest template的封装,为获取token等提供便捷方法 - DefaultUserInfoRestTemplateFactory
DefaultUserInfoRestTemplateFactory实例化OAuth2RestTemplate,提供了OAuth2RestTemplate - ResourceServerTokenServicesConfiguration
配置创建DefaultUserInfoRestTemplateFactory给resource server用 - OAuth2ClientAuthenticationProcessingFilter
它的构造器需要传入defaultFilterProcessesUrl,用于指定这个filter拦截哪个url,它依赖OAuth2RestTemplate来获取token,依赖ResourceServerTokenServices进行校验token
- 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端口为资源客户端
http://localhost:7001/demon/test/hi?name=lixx2、未登录状态,转向授权服务进行用户登录授权
文章图片
文章图片
3、用户登录授权成功,返回客户端主页/home
文章图片
4、客户端网页访问接口,接口远程调用资源服务返回结果
文章图片
【spring|OAuth2授权客户端访问资源服务】通过最后一步可以看到客户端访问资源服务内部携带了token过去,该token被资源服务获取并从授权服务获取用户认证信息,鉴定具有权限访问然后返回对应结果,这个过程我在单点登录以及资源服务两篇文章中有讲解到,比较复杂,这里不再赘述。
源码获取、合作、技术交流请获取如下联系方式:
QQ交流群:961179337
文章图片
微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:lixx2048@163.com
推荐阅读
- =======j2ee|spring用注解实现注入的@resource,@autowired,@inject区别
- jar|springboot项目打成jar包和war包,并部署(快速打包部署)
- 数据库|效率最高的Excel数据导入---(c#调用SSIS Package将数据库数据导入到Excel文件中【附源代码下载】)...
- java人生|35K 入职华为Java开发那天,我哭了(这 5 个月做的一切都值了)
- Java毕业设计项目实战篇|Java项目:在线嘿嘿网盘系统设计和实现(java+Springboot+ssm+mysql+maven)
- 微服务|微服务系列:服务发现与注册-----Eureka(面试突击!你想了解的Eureka都在这里.持续更新中......)
- 每日一书|每日一书丨终于有人把云原生讲明白了
- java|ApplicationListener和SpringApplicationRunListener的联系
- Spring|SpringSecurity--自定义登录页面、注销登录配置
- 性能|性能工具之 Jmeter 通过 SpringBoot 工程启动