Spring Security Oauth2 之 理解OAuth 2.0授权流程

1. Oauth 定义

1.1 角色
OAuth定义了四个角色:
资源所有者

一个能够授权访问受保护资源的实体。当资源所有者是一个人时,它被称为最终用户。

资源服务器

托管受保护资源的服务器能够使用访问令牌接受和响应受保护的资源请求。

客户

代表资源所有者及其授权的应用程序进行受保护的资源请求。术语客户端并不意味着任何特定的实现特征(例如,应用程序是在服务器,台式机还是其他设备上执行的)。

授权服务器

服务器在成功认证资源所有者并获得授权后向客户端发放访问令牌。
1.2 协议流程

图1中所示的抽象OAuth 2.0流程描述了四个角色之间的交互,并包含以下步骤:

(A) 客户端请求资源所有者的授权。授权请求可以直接给资源所有者(如图所示),或者优选间接地通过授权服务器作为中介。
(B) 客户端接收授权许可,这是一种代表资源所有者授权的凭证,使用本规范中定义的四种授权类型之一或使用扩展授权类型表示。授权授予类型取决于客户端用于请求授权的方法以及授权服务器支持的类型。
(C) 客户端通过向授权服务器进行认证并携带授权来请求访问令牌。
(D) 授权服务器对客户端进行身份验证并验证授权,并且如果有效则发出访问令牌。
(E) 客户端从资源服务器请求受保护的资源并通过携带访问令牌进行认证。
(F) 资源服务器验证访问令牌,并且如果有效,则为该请求提供服务。

客户从资源所有者(步骤(A)和(B)中描述)获得授权许可的首选方法是使用授权服务器作为中介
2 模式

oauth2根据使用场景不同,分成了4种模式
● 客户端模式(client credentials):标准的 Server 授权模式,非常适合 Server 端的 Web 应用。一旦资源的拥有者授权访问他们的数据之后,他们将会被重定向到 Web 应用并在 URL 的查询参数中附带一个授权码(code)。在客户端里,该 code 用于请求访问令牌(access_token)。并且该令牌交换的过程是两个服务端之前完成的,防止其他人甚至是资源拥有者本人得到该令牌。另外,在该授权模式下可以通过 refresh_token 来刷新令牌以延长访问授权时间,也是最为复杂的一种方式。
● 密码模式(resource owner password credentials) : 自己有一套用户体系,这种模式要求用户提供用户名和密码来交换访问令牌(access_token)。该模式仅用于非常值得信任的用户,例如API提供者本人所写的移动应用。虽然用户也要求提供密码,但并不需要存储在设备上。因为初始验证之后,只需将 OAuth 的令牌记录下来即可。如果用户希望取消授权,因为其真实密码并没有被记录,因此无需修改密码就可以立即取消授权。token本身也只是得到有限的授权,因此相比最传统的 username/password 授权,该模式依然更为安全。
● 授权码模式(authorization code) :该模式是所有授权模式中最简单的一种,并为运行于浏览器中的脚本应用做了优化。当用户访问该应用时,服务端会立即生成一个新的访问令牌(access_token)并通过URL的#hash段传回客户端。这时,客户端就可以利用JavaScript等将其取出然后请求API接口。该模式不需要授权码(code),当然也不会提供refresh token以获得长期访问的入口。
● 简化模式(implicit) :没有用户的概念,一种基于 APP 的密钥直接进行授权,因此 APP 的权限非常大。它适合像数据库或存储服务器这种对 API 的访问需求。

Oauth基于客户端与认证服务器验证的能力定义了两种客户端类型(以及,维护客户端认证信息的能力): 客户端模式、密码模式。

基础参数定义:
grant_type (发放令牌类型)、
client_id (客户端标识id)
username(用户帐号)
password (用户密码)
client_secret(客户端标识密钥)
refresh_token (刷新令牌)
scope(表示权限范围,可选项)
Oauth2 Client 集成

pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
package com.lvyuanj.upms.oauthclient.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;@Configuration
public class UpmsOauth2LoginConfig {@EnableWebSecuritypublic static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UpmsLogoutHandler upmsLogoutHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().logout().addLogoutHandler(upmsLogoutHandler).and().oauth2Login();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().mvcMatchers("/js/**","/css/**");}}@Beanpublic OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);}@Beanpublic OAuth2AuthorizedClientRepository authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) {return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);}@Beanpublic ClientRegistrationRepository clientRegistrationRepository() {return new InMemoryClientRegistrationRepository(this.upmsOauth2ClientRegistration());}@Bean@ConfigurationProperties(prefix = "spring.security.oauth2.client.registration.upms")public UpmsClientRegistration upmsClientRegistration(){return new UpmsClientRegistration();}@Bean@ConfigurationProperties(prefix = "spring.security.oauth2.client.provider.upms")public UpmsClientProvider upmsClientProvider(){return new UpmsClientProvider();}private ClientRegistration upmsOauth2ClientRegistration() {return ClientRegistration.withRegistrationId(this.upmsClientRegistration().getProvider()).clientId(this.upmsClientRegistration().getClientId()).clientSecret(this.upmsClientRegistration().getClientSecret()).clientAuthenticationMethod(ClientAuthenticationMethod.BASIC).authorizationGrantType(new AuthorizationGrantType(this.upmsClientRegistration().getAuthorizationGrantType())).redirectUriTemplate(this.upmsClientRegistration().getRedirectUriTemplate()).scope(this.upmsClientRegistration().getScope()).authorizationUri(this.upmsClientProvider().getAuthorizationUri()).tokenUri(this.upmsClientProvider().getTokenUri()).userInfoUri(this.upmsClientProvider().getUserInfoUri()).userNameAttributeName(this.upmsClientProvider().getUserNameAttribute()).clientName(this.upmsClientRegistration().getClientName()).build();}
}

oauth registration config

package com.lvyuanj.upms.oauthclient.config;import lombok.Data;@Data
public class UpmsClientProvider {private String authorizationUri;private String tokenUri;private String userInfoUri;private String userNameAttribute;
}package com.lvyuanj.upms.oauthclient.config;import lombok.Data;@Data
public class UpmsClientRegistration {private String provider;private String clientId;private String clientSecret;private String clientName;private String authorizationGrantType;private String redirectUriTemplate;private String scope;}

退出登陆配置

package com.lvyuanj.upms.oauthclient.config;import com.lvyuanj.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Slf4j
@Service
public class UpmsLogoutHandler implements LogoutHandler {@Value("${com.lvyuanj.upms.client.upms.logout-uri}")private String logoutUrl;@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {try {String access_token = (String) request.getSession().getAttribute("access_token");String params = "";if(StringUtils.isNotBlank(access_token)){params = "?access_token="+access_token;}response.sendRedirect(logoutUrl+params);} catch (IOException e) {e.printStackTrace();}}
}

登陆成功跳转到index

package com.lvyuanj.upms.oauthclient.controller;import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Map;@Slf4j
@Controller
public class OAuth2Controller {@Autowiredprivate OAuth2AuthorizedClientService authorizedClientService;@RequestMapping("/")public String index(Model model, OAuth2AuthenticationToken authentication, HttpServletRequest request) {log.debug("authentication:"+ JSONObject.toJSONString(authentication));OAuth2AuthorizedClient authorizedClient = this.getAuthorizedClient(authentication);if(null != authorizedClient){String access_token = authorizedClient.getAccessToken().getTokenValue();log.debug("access_token: "+ access_token);request.getSession().setAttribute("access_token", access_token);model.addAttribute("userName", authentication.getName());model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());return "index";}else {return "logout";}}private OAuth2AuthorizedClient getAuthorizedClient(OAuth2AuthenticationToken authentication) {return this.authorizedClientService.loadAuthorizedClient(authentication.getAuthorizedClientRegistrationId(), authentication.getName());}@RequestMapping("/userinfo")public String userinfo(Model model,OAuth2AuthenticationToken authentication) {// authentication.getAuthorizedClientRegistrationId() returns the// registrationId of the Client that was authorized during the Login flowOAuth2AuthorizedClient authorizedClient =this.authorizedClientService.loadAuthorizedClient(authentication.getAuthorizedClientRegistrationId(),authentication.getName());OAuth2AccessToken accessToken = authorizedClient.getAccessToken();System.out.println(accessToken.getTokenValue());Map userAttributes = Collections.emptyMap();String userInfoEndpointUri = authorizedClient.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();if (!StringUtils.isEmpty(userInfoEndpointUri)) {// userInfoEndpointUri is optional for OIDC ClientsuserAttributes = WebClient.builder().filter(oauth2Credentials(authorizedClient)).build().get().uri(userInfoEndpointUri).retrieve().bodyToMono(Map.class).block();}model.addAttribute("userAttributes", userAttributes);return "userinfo";}private ExchangeFilterFunction oauth2Credentials(OAuth2AuthorizedClient authorizedClient) {return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {ClientRequest authorizedRequest = ClientRequest.from(clientRequest).header(HttpHeaders.AUTHORIZATION, "Bearer " + authorizedClient.getAccessToken().getTokenValue()).build();return Mono.just(authorizedRequest);});}
}

Spring Security Oauth2 Client 非常重要的过滤器:OAuth2LoginAuthenticationFilter

org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter#attemptAuthentication方法

    public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*";    --在oauth2登陆成功之后,此过滤器拦截回调接口地址@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException, IOException, ServletException {MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {  // 判断回调数据是否有CODE 或者 STATE 参数OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}OAuth2AuthorizationRequest authorizationRequest =this.authorizationRequestRepository.removeAuthorizationRequest(request, response);if (authorizationRequest == null) {OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}String registrationId = (String) authorizationRequest.getAdditionalParameters().get(OAuth2ParameterNames.REGISTRATION_ID);ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);if (clientRegistration == null) {OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,"Client Registration not found with Id: " + registrationId, null);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)).replaceQuery(null).build().toUriString();OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));OAuth2LoginAuthenticationToken authenticationResult =(OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(authenticationResult.getPrincipal(),authenticationResult.getAuthorities(),authenticationResult.getClientRegistration().getRegistrationId());OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(authenticationResult.getClientRegistration(),oauth2Authentication.getName(),authenticationResult.getAccessToken(),authenticationResult.getRefreshToken());this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);return oauth2Authentication;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/814509.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统编程---文件IO

一、系统调用 由操作系统实现并提供给外部应用程序的编程接口(Application Programming Interface&#xff0c;API),用户程序可以通过这个特殊接口来获得操作系统内核提供的服务 系统调用和库函数的区别&#xff1a; 系统调用(系统函数) 内核提供的函数 库调用 …

一起学习python——基础篇(19)

今天来说一下python的如何修改文件名称、获取文件大小、读取文中指定的某一行内容。 1、修改文件名称&#xff1a; import os testPath"D:/pythonFile/test.txt" testPath2"D:/pythonFile/test2.txt" #修改文件名称使用rename方法&#xff0c; #第一个参…

TQ15EG开发板教程:在MPSOC上运行ADRV9009(vivado2018.3)

首先需要在github上下载两个文件&#xff0c;本例程用到的文件以及最终文件我都会放在网盘里面&#xff0c; 地址放在最后面。在github搜索hdl选择第一个&#xff0c;如下图所示 GitHub网址&#xff1a;https://github.com/analogdevicesinc/hdl/releases 点击releases选择版…

31省结婚、离婚、再婚等面板数据(1990-2022年)

01、数据介绍 一般来说&#xff0c;经济发达地区的结婚和离婚率相对较高&#xff0c;而经济欠发达地区的结婚和离婚率相对较低。此外&#xff0c;不同省份的文化、习俗、社会观念等因素也会对结婚和离婚情况产生影响。 本数据从1990年至2022年&#xff0c;对各地区的结婚、离…

Vue-router的编程式导航有哪些方法

Vue Router 的编程式导航主要提供了以下方法&#xff1a; push&#xff1a;这个方法会向 history 栈添加一个新的记录&#xff0c;所以当用户点击浏览器后退按钮时&#xff0c;则回到之前的 URL。当你点击 <router-link> 时&#xff0c;这个方法会在内部被调用&#xff…

6-169 删除递增链表两个值之间的元素 - 人邮DS(C 第2版)线性表习题2(8)

设计一个算法,删除递增有序链表中值大于mink且小于maxk的所有元素(mink和maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同 )。 函数接口定义: void DeleteMinMax(LinkList const &L, int mink, int maxk); L - 递增链表的指针 mink - 被删除元素值的最…

【C++】每日一题 392 判断子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#…

014_files_in_MATLAB中的文件读写

MATLAB中的文件读写 这一篇就要简单介绍MATLAB中的典型文件类型和文件操作。 基于字节流的接口 Matlab本身提供的文件操作是比较接近底层的&#xff0c;这一套底层的文件原语&#xff0c;主要是fopen、fclose、fread、fwrite、fseek、ftell、feof、ferror等函数。这些函数的…

Github 2024-04-14 php开源项目日报Top9

根据Github Trendings的统计,今日(2024-04-14统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9TypeScript项目1Laravel: 以优雅语法简化Web开发 创建周期:4028 天开发语言:PHP协议类型:MIT LicenseStar数量:30824 个Fork数量:1…

《青少年成长管理2024》046 “成长目标:你是谁呀?”2/3

《青少年成长管理2024》046 “成长目标&#xff1a;你是谁呀&#xff1f;”2/3 七、机器智能&#xff1f;八、天赋没有对错&#xff08;一&#xff09;天赋的客观性&#xff08;二&#xff09;我笨我没错&#xff08;三&#xff09;我聪明只是我幸运&#xff08;四&#xff09;…

在Linux驱动中,如何确保中断上下文的正确保存和恢复?

大家好&#xff0c;今天给大家介绍在Linux驱动中&#xff0c;如何确保中断上下文的正确保存和恢复&#xff1f;&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 在Linux驱动中&am…

windows系统搭建OCR半自动标注工具PaddleOCR

深度学习 文章目录 深度学习前言一、环境搭建准备方式1&#xff1a;安装Anaconda搭建1. Anaconda下载地址: [点击](https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?CM&OD)2. 创建新的conda环境 方式2. 直接安装python 二、安装CPU版本1. 安装PaddlePaddle2、安装…

中国省级人口结构数据集(2002-2022年)

01、数据简介 人口结构数据不仅反映了地域特色&#xff0c;更是预测地区未来发展趋势的重要工具。在这些数据中&#xff0c;总抚养比、少年儿童抚养比和老年人口抚养比是三大核心指标。 少儿抚养比0-14周岁人口数/15-64周岁人口数 老年抚养比65周岁及以上人口数/15-64周岁人…

leetcode每日一题(1702. 修改后的最大二进制字符串)

题目描述 题解 这道题贪心的思想&#xff0c;我们只需要尽可能多的把0变成1&#xff0c;而且进行操作1才能使其变大。观察发现以下几点&#xff1a; 不论原字符串有多少个0&#xff0c;最后都会剩余1个0。 假设原字符串只有一个0&#xff0c;不能进行任何操作&#xff0c;显然…

一招将vscode自动补全的双引号改为单引号

打开设置&#xff0c;搜索quote&#xff0c;在结果的HTML选项下找到自动完成&#xff0c;设置默认引号类型即可。 vscode版本&#xff1a;1.88.1&#xff0c; vscode更新日期&#xff1a;2024-4-10

利用Java代码调用Lua脚本改造分布式锁

4.8 利用Java代码调用Lua脚本改造分布式锁 lua脚本本身并不需要大家花费太多时间去研究&#xff0c;只需要知道如何调用&#xff0c;大致是什么意思即可&#xff0c;所以在笔记中并不会详细的去解释这些lua表达式的含义。 我们的RedisTemplate中&#xff0c;可以利用execute方…

共轭梯度法 Conjugate Gradient Method (线性及非线性)

1. 线性共轭梯度法 共轭梯度法&#xff08;英语&#xff1a;Conjugate gradient method&#xff09;&#xff0c;是求解系数矩阵为对称正定矩阵的线性方程组的数值解的方法。 共轭梯度法是一个迭代方法&#xff0c;它适用于 1. 求解线性方程组&#xff0c; 2. 共轭梯度法也可…

学习基于pytorch的VGG图像分类 day5

注&#xff1a;本系列博客在于汇总CSDN的精华帖&#xff0c;类似自用笔记&#xff0c;不做学习交流&#xff0c;方便以后的复习回顾&#xff0c;博文中的引用都注明出处&#xff0c;并点赞收藏原博主. 目录 VGG的数据集处理 1.数据的分类 2.对数据集的处理 VGG的分类标签设置 …

2款Notepad++平替工具(实用、跨平台的文本编辑器)

前言 今天大姚给大家分享2款Notepad平替工具&#xff0c;实用、跨平台&#xff08;支持Window/MacOS/Linux操作系统平台&#xff09;的文本编辑器。 NotepadNext NotepadNext是一个跨平台的 Notepad 的重新实现。开发是使用 QtCreator 和 Microsft Visual C (msvc) 编译器完…

python输入某年某月某日判断这一天是这一年的第几天

如何使用python实现输入某年某月某日判断这一天是这一年的第几天 from datetime import datetime #引入日期类 def is_leap_year(year):"""判断是否为闰年"""return (year % 4 0 and year % 100 ! 0) or (year % 400 0)# 根据年份和月份返回当…