手把手入门OAuth2授权码模式(Authorization Code)

1.简单介绍

OAuth2 授权码模式模式基本上是用用户凭证获取token 后来获取资源的访问权限。其交互步骤如下图:

交互过程如下:

  1. 用户在客户端程序上操作某些功能希望从资源服务器获取数据
  2. 客户端程序重定向浏览器请求到授权服务器要求授权,在重定向之前客户端程序会给授权服务器传递一个参数作为回调地址
  3. 授权服务器请求用户同意,这个步骤一般需要用户先登录,如果已经登录则可能弹出一个交互页面请求用户同意授权
  4. 用户决定同意授权
  5. 授权服务器重定向到第二步的回调地址,并且在URL 后附带一个Authorization Code,这个Authorization Code 是明文传递给客户端程序的,所以不安全
  6. 客户端程序用 Authorization Code 和 认证的密钥获取 access token
  7. 客户端程序用access token 访问资源服务器(也就是实际的业务接口)
  8. 资源服务器返回数据给客户端程序

交互图如下

2.授权服务器

Spring Authorization Server 是一个spring 专门处理授权服务的项目,我们构建授权服务器就是基于它。 我们先一步一步操作完成后再看整个流程。

2.1 建立基本项目结构

整体项目结构如下

创建一个maven 项目oauth2-auth-server
pom.xml 如下:
源码地址 :细节请参考项目源码

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.7</version><relativePath /> <!-- lookup parent from repository --></parent><groupId>com.xue</groupId><artifactId>oauth2-auth-server</artifactId><version>2022.04.28</version><name>oauth2-auth-server</name><description>spring Authorization Server Demo project for Spring Boot</description><properties><java.version>17</java.version><p6spy>3.9.1</p6spy></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>0.2.3</version></dependency><dependency><groupId>p6spy</groupId><artifactId>p6spy</artifactId><version>${p6spy}</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

注意 spring-security-oauth2-authorization-server这个依赖是最重要的
项目的配置文件application.yml如下
源码地址 :[细节请参考项目源码]

#https://docs.spring.io/spring-boot/docs spring文档
# https://docs.spring.io/spring-boot/docs/2.6.3/reference/html/application-properties.html#application-properties
server:port: 9000
# 默认激活dev配置
spring:profiles:active: "dev"#active: "prod"group:"dev": "common,jpa,h2,dev""prod": "common,jpa,mysql,prod"---
spring:config:activate:on-profile: "mysql"
jdbc:database: mysqlinit-mode: ALWAYSdriver-class-name: com.mysql.cj.jdbc.Driver---
spring:config:activate:on-profile: "h2"h2:console:#数据库控制台 http://localhost:9000/h2-console/login.jsppath: /h2-consoleenabled: truesetting:web-allow-others: true
jdbc:database: h2init-mode: EMBEDDEDdriver-class-name: org.h2.Driver---
spring:config:activate:on-profile: "dev"jdbc:#是否打印sql,true 或falseshow-sql: trueurl: mem:db1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;TRACE_LEVEL_SYSTEM_OUT=2username: testpassword: ---
spring:config:activate:on-profile: "prod"devtools:restart:enabled : false
jdbc:#是否打印sql,true 或falseshow-sql: falseurl: //localhost:8024/test?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8username: testpassword: 123456
---
spring:config:activate:on-profile: "common"application:name: auth-serverlogging:path : ./    datasource:driver-class-name: ${jdbc.driver-class-name-show-sql-${jdbc.show-sql}}url  : ${jdbc.url-show-sql${jdbc.show-sql}}username: ${jdbc.username}password: ${jdbc.password}#连接池池参数配置type: com.zaxxer.hikari.HikariDataSourcehikari:pool-name: hikariDataSourcePoolconnection-test-query: /* ping */  SELECT 1#connection-test-query: SELECT 1maximum-pool-size: 20minimum-idle: 5#默认30000  30sconnection-timeout: 10000#默认5000validation-timeout: 3000#默认trueis-auto-commit: false#默认falseis-read-only: falsejdbc:#不打印sqldriver-class-name-show-sql-false: ${jdbc.driver-class-name}url-show-sql-false : jdbc:${jdbc.database}:${jdbc.url}#打印sqldriver-class-name-show-sql-true : com.p6spy.engine.spy.P6SpyDriverurl-show-sql-true : jdbc:p6spy:${jdbc.database}:${jdbc.url}---
spring:config:activate:on-profile: "jpa"jpa:database: ${jdbc.database}show-sql: falsehibernate:ddl-auto: updateopen-in-view: false

注意我们的服务运行在 9000 端口

我们把用户信息用JPA 保存到数据库中,所以先建立用户模型
User.java

package com.xue.pojo;@Entity
@Table(name = "sys_user")
public class User implements UserDetails {private static final long serialVersionUID = 1L;@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;private final String username;private final String password;private final String role;... 代码太多请参考项目源码
}

UserRepository.java 用户DAO 层

public interface UserRepository extends CrudRepository<User, Long> {User findByUsername(String username);}

SecurityConfig.java spring安全配置

@EnableWebSecurity
public class SecurityConfig {@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// @formatter:offreturn http//设置了<iframe>标签的“同源策略”,解决了上面出现的问题//https://blog.csdn.net/weixin_43981241/article/details/108240756.headers().frameOptions().sameOrigin().and()//使得h2 console页面不去做csrf检查.csrf().ignoringAntMatchers("/h2-console/**").and().authorizeRequests()//使得访问h2 console时,不需要先登录系统.antMatchers("/h2-console/**").permitAll().and().authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().anonymous()).formLogin().and().build();// @formatter:on}@BeanUserDetailsService userDetailsService(UserRepository userRepo) {return username -> userRepo.findByUsername(username);}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

这里最主要的是任何匿名的访问都会重定向到登录页面
下边人认证服务器配置
AuthorizationServerConfig.java

@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);return http.formLogin(Customizer.withDefaults()).build();}// 注册客户端,目前时从内存种加载,正式环境需要从数据库种加载@Beanpublic RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {// @formatter:offRegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("taco-admin-client").clientSecret(passwordEncoder.encode("secret"))//密钥.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).redirectUri("http://127.0.0.1:9090/login/oauth2/code/taco-admin-client").scope("writeIngredients").scope("deleteIngredients").scope(OidcScopes.OPENID).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();// @formatter:on// 如果持久化到数据库中// https://blog.csdn.net/qq_35067322/article/details/121347137return new InMemoryRegisteredClientRepository(registeredClient);}@Beanpublic ProviderSettings providerSettings() {return ProviderSettings.builder().issuer("http://localhost:9000").build();}@Beanpublic JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {RSAKey rsaKey = generateRsa();JWKSet jwkSet = new JWKSet(rsaKey);return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);}private static RSAKey generateRsa() throws NoSuchAlgorithmException {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();return new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();}private static KeyPair generateRsaKey() throws NoSuchAlgorithmException {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);return keyPairGenerator.generateKeyPair();}@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}}

我们还需要在启动类AuthServerApplication.java中加载一些数据

@SpringBootApplication
public class AuthServerApplication {public static void main(String[] args) {SpringApplication.run(AuthServerApplication.class, args);}// 启动时初始化默认数据@Beanpublic ApplicationRunner dataLoader(UserRepository repo, PasswordEncoder encoder) {return args -> {repo.save(new User(1L, "a1", encoder.encode("1"), "ROLE_ADMIN"));repo.save(new User(2L, "a2", encoder.encode("1"), "ROLE_ADMIN"));};}
}

2.2 启动项目并测试

我们启动项目
访问
http://localhost:9000/ 会看到首页,首页代码请参看上面的源码连接

点击连接

请参考上图输入
其中JDBC URL 为 jdbc:h2:mem:db1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;TRACE_LEVEL_SYSTEM_OUT=2

用户 test 密码为 空
点击connect 看到数据库 sys_user表中有2条数据
其中 a1用户密码为 1,a2用户密码为 2,密码都是加密过的

测试登录,用户名输入 a1,密码为1
http://localhost:9000/login


看到响应码为403

在浏览器中访问

http://localhost:9000/oauth2/authorize?response_type=code&client_id=taco-admin-client&redirect_uri=http://127.0.0.1:9090/login/oauth2/code/taco-admin-client&scope=writeIngredients+deleteIngredients

会看到如下页面,当前登录的用户为 a1

至此基本完成认证服务器

3.资源服务器

资源服务器简单说来就是我们的对外接口

3.1 建立基本项目结构

项目源码

其中pom.xml 中最重要的是集成了以下内容

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>

再则是
SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig {@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// @formatter:offreturn http//设置了<iframe>标签的“同源策略”,解决了上面出现的问题//https://blog.csdn.net/weixin_43981241/article/details/108240756.headers().frameOptions().sameOrigin().and()//使得h2 console页面不去做csrf检查.csrf().ignoringAntMatchers("/h2-console/**").and().authorizeRequests()//使得访问h2 console时,不需要先登录系统.antMatchers("/h2-console/**").permitAll().and()//需要拦截的API.authorizeRequests().antMatchers(HttpMethod.POST, "/api/ingredients").hasAuthority("SCOPE_writeIngredients").antMatchers(HttpMethod.DELETE, "/api/ingredients").hasAuthority("SCOPE_deleteIngredients").and()//开启 ResourceServer.oauth2ResourceServer(oauth2 -> oauth2.jwt()).build();// @formatter:on}}

注意画红线的地方

还有application.yml 的配置

server:port: 8080# 默认激活dev配置
spring:profiles:active: "dev"#active: "prod"group:"dev": "common,jpa,h2,dev""prod": "common,jpa,mysql,prod"security:oauth2:resourceserver:jwt:jwk-set-uri: http://localhost:9000/oauth2/jwks

这里配置了spring.security.oauth2.resourceserver.jwt.jwk-set-uri指向了 认证服务器http://localhost:9000/oauth2/jwks

此外的接口和DAO层请参考代码
项目源码

3.2 测试项目

4.第三方应用

第三方应用用户当前访问的应用

4.1 建立基本项目结构

项目源码地址


pom.xml 注意以下内容

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId></dependency>

application.yml 配置是非常重要的

server:port: 9090# tag::client_config[]
# tag::provider_config_0[]
spring:security:oauth2:client:
# end::provider_config_0[]registration:taco-admin-client:provider: tacocloudclient-id: taco-admin-clientclient-secret: secretauthorization-grant-type: authorization_coderedirect-uri: "http://127.0.0.1:9090/login/oauth2/code/{registrationId}"scope: writeIngredients,deleteIngredients,openid
# end::client_config[]
# tag::provider_config[]provider:tacocloud:issuer-uri: http://localhost:9000
# end::provider_config[]

SecurityConfig.java 文件内容如下

@Configuration
public class SecurityConfig {@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated()).oauth2Login(oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/taco-admin-client")).oauth2Client(withDefaults());return http.build();}@Bean@RequestScopepublic IngredientService ingredientService(OAuth2AuthorizedClientService clientService) {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();String accessToken = null;if (authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();if (clientRegistrationId.equals("taco-admin-client")) {OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId,oauthToken.getName());accessToken = client.getAccessToken().getTokenValue();}}return new RestIngredientService(accessToken);}}

RestIngredientService.java 是客户端通过 rest api 访问资源服务器的具体实现类

public class RestIngredientService implements IngredientService {private RestTemplate restTemplate;/*public RestIngredientService() {*/public RestIngredientService(String accessToken) {this.restTemplate = new RestTemplate();if (accessToken != null) {this.restTemplate.getInterceptors().add(getBearerTokenInterceptor(accessToken));}}@Overridepublic Iterable<Ingredient> findAll() {return Arrays.asList(restTemplate.getForObject("http://localhost:8080/api/ingredients",Ingredient[].class));}@Overridepublic Ingredient addIngredient(Ingredient ingredient) {return restTemplate.postForObject("http://localhost:8080/api/ingredients",ingredient,Ingredient.class);}private ClientHttpRequestInterceptorgetBearerTokenInterceptor(String accessToken) {ClientHttpRequestInterceptor interceptor =new ClientHttpRequestInterceptor() {@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] bytes,ClientHttpRequestExecution execution) throws IOException {request.getHeaders().add("Authorization", "Bearer " + accessToken);return execution.execute(request, bytes);}};return interceptor;}}

其他代码请参考
项目源码地址

4.2 测试项目

如果一切没问题的话,现在可以来一个全流程的测试

  1. 启动授权服务器 oauth2-auth-server

  2. 启动资源服务器oauth2-res-server

  3. 启动客户端 oauth2-client-app

  4. 我们现在就是用户 访问 oauth2-client-app 程序 在浏览器中输入 http://localhost:9090/ 访问

  5. 如果你已经登录会直接看到第6步的页面,由于我们没有登录 被拦截后重定向到认证服务器 页面,如下图

  6. 输入a1,密码 1 进入授权页面

  7. 点击同意授权程序重定向到

  8. 点击原材料管理,发现现在客户端可以访问资源服务器提供的接口

5.总结一下大体流程

5.1在 oauth2-client-app 这个应用中需要访问 oauth2-res-server 的一些接口,

5.2 由于这些接口 是我们用户的私有信息所以需要我们授权,在访问接口时会重定向到oauth2-auth-server 要求授权,

5.3 我们统一授权后 oauth2-client-app 会获取一个token

5.4 oauth2-res-server就会返回数据

手把手OAuth2授权码模式(Authorization Code)相关推荐

  1. Spring Security OAuth2 授权码模式 (Authorization Code)

    前言 Spring Security OAuth2 授权码模式 (Authorization Code) 应该是授权登录的一个行业标准 整体流程 首先在平台注册获取CLIENT_ID和CLIENT_S ...

  2. Apache Oltu 实现 OAuth2.0 服务端【授权码模式(Authorization Code)】

    要实现OAuth服务端,就得先理解客户端的调用流程,服务提供商实现可能也有些区别,实现OAuth服务端的方式很多,具体可能看 http://oauth.net/code/ 各语言的实现有(我使用了Ap ...

  3. Spring Security Oauth2 授权码模式下 自定义登录、授权页面

    主要说明:基于若依springcloud微服务框架的2.1版本 嫌弃缩进不舒服的,直接访问我的博客站点: http://binarydance.top//aticle_view.html?aticle ...

  4. java调用授权接口oauth2_微信授权就是这个原理,Spring Cloud OAuth2 授权码模式

    上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录.本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式 ...

  5. 面试官:能说一说微信授权的原理吗?(Spring Cloud OAuth2 授权码模式)

    我是风筝,公众号「古时的风筝」,一个简单的程序员鼓励师. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 上一篇文章Spring Cloud OA ...

  6. OAuth2授权码模式

    OAuth2协议 为什么要有OAuth2协议? 为了解决第三方服务在无需用户提供账号密码的情况下访问用户的私有资源的一套流程规范. 怎么实现OAuth2协议? 接入OAuth2协议,Authoriza ...

  7. java 授权码模式_Spring Security OAuth2 授权码模式的实现

    写在前边 在文章OAuth 2.0 概念及授权流程梳理 中我们谈到OAuth 2.0的概念与流程,这里我准备分别记一记这几种授权模式的demo,一方面为自己的最近的学习做个总结,另一方面做下知识输出, ...

  8. Spring Security(十一):授权认证(OAuth2)-授权码模式(authorization_code)

    一:简介 简单说,OAuth就是一种授权机制.数据的所有者告诉系统,同意授权第三方应用进入系统,获取部分允许获取的数据.系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用. ...

  9. IdentityServer4系列 | 授权码模式

    一.前言 在上一篇关于简化模式中,通过客户端以浏览器的形式请求「IdentityServer」服务获取访问令牌,从而请求获取受保护的资源,但由于token携带在url中,安全性方面不能保证.因此,我们 ...

  10. OAuth2.0协议入门(一):OAuth2.0协议的基本概念以及使用授权码模式(authorization code)实现百度账号登录

    一 OAuth2.0协议的基本概念 (1)OAuth2.0协议 OAuth协议,是一种授权协议,不涉及具体的代码,只是表示一种约定的流程和规范.OAuth协议一般用于用户决定是否把自己在某个服务商上面 ...

最新文章

  1. 算法--------数组------反转字符串中的元音字母
  2. Redis允许远程访问
  3. Mockito教程:使用Mockito进行测试和模拟
  4. 洛谷——P1546 最短网络 Agri-Net
  5. python双向链表
  6. python渲染html页面_在Python中使用CasperJS获取JS渲染生成的HTML内容的教
  7. 编译器GCC的Windows版本 : MinGW-w64安装教程
  8. Linux进程、线程、任务调度(1)贵在坚持
  9. 将pem证书转换为crt和key
  10. 远程控制,从个人便捷走向企业安全
  11. STM32单片机介绍2
  12. 大三计算机写学术论文,学院大三本科生在高水平国际会议发表学术论文
  13. LaTeX \subfloat 引用子图片使用小括号
  14. centos6的yum源
  15. 道阻且长,行则将至;行而不辍,未来可期。
  16. 查找计算机里包含相关文字,搜索word包含文字内容
  17. 在哪个范围内的计算机网络可以称为局域网,计算机网络概述 习题
  18. 微信“开放”第三天,互联网有什么不一样?
  19. “GitHub: Your account has been flagged.”的解决方法
  20. 云计算厂商怎么打造自己的生态网络

热门文章

  1. VBA之正则表达式(30)-- 提取机构代码
  2. VS Code 安装 VSIX 插件
  3. FCM——(Fuzzy C-means)模糊C均值算法
  4. fcm基本原理_FCM聚类算法介绍
  5. 基于springboot的网上零食购物系统
  6. 计算机 A类会议论文,一篇论文被CCF A类会议SIGIR 2021录用!
  7. 51单片机外围模块——红外通信
  8. Elasticsearch摄取节点(十)——GeoIP以及Grok处理器
  9. 智能客服搭建(4) - 语音流的分贝计算
  10. 服务器上excel文件损坏,excel文件打不开的原因和解决方法 excel文件损坏怎么修复...