基于springboot3实现单点登录(二):认证服务端搭建

前言

上文我们介绍了oauth2.0的相关理论和流程,本文我们继续实现。

Oauth2协议中有个很重要的概念,叫做”端点“,

以下整理了一些常用的端点及其参考访问路径及使用场景的信息,供参考。

这些端点在oauth2.0协议的整个生命周期中扮演着非常重要的角色。

端点说明参考值使用场景
访问授权端点/oauth2/authorize当客户端需要请求用户的授权时,会将用户重定向到这个端点。用户在这里进行登录验证,并决定是否授权给客户端。一旦用户同意授权,授权服务器会重定向用户回客户端预先注册的重定向URI,携带一个授权码或直接的访问令牌(取决于所使用的授权类型)
获取令牌端点/oauth2/token客户端使用这个端点来交换授权码、用户密码、客户端证书等凭证,以获取访问令牌和刷新令牌。客户端在请求时通常需要进行身份验证,以证明自己有权请求令牌。
用户信息端点/oauth2/userInfo客户端使用访问令牌访问这个端点,以获取授权用户的基本信息,如用户名、电子邮件地址等。这通常用于获取用户个人资料,以便于个性化用户体验或进行进一步的业务逻辑处理。
撤销令牌端点/oauth2/revoke当访问令牌或刷新令牌不再需要或应当被撤销时,客户端或资源所有者可以使用这个端点来撤销令牌。这有助于保护用户数据的安全,防止令牌被滥用。
校验令牌端点/oauth2/introspect资源服务器使用这个端点来检查访问令牌的有效性和属性,以决定是否允许客户端访问受保护资源。这有助于确保只有有效的令牌才能访问资源。
公钥端点/oauth2/jwks如果使用JWT(JSON Web Tokens)作为令牌,客户端可以使用这个端点来获取用于验证JWT签名的公钥。这对于确保令牌未被篡改非常重要。
刷新令牌端点/oauth2/token实际上,刷新令牌的功能通常是在令牌端点中实现的。客户端使用刷新令牌来获取新的访问令牌,当原始的访问令牌过期时,这允许客户端无需重新进行用户授权即可继续访问资源。

实现一个oauth2.0服务器服务就需要提供以上这些端点的实现,本文我们实现一个基于Oauth2.0的认证服务器,并通过验证这些端点来验证其完整性和可用性。

准备

本文所依赖的环境如下:

环境/工具名称版本
Java17
Springboot3.2.2
MybatisPlus3.5.5
Authorization-Server1.3.1
Spring6.3.3
SpringSecurity6.3.3
Maven3.6.5
MySql8.0.33

开始

1. 新建springboot+maven项目

2. 修改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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.zjtx.tech.security</groupId><artifactId>springboot3-sso</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version></parent><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></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-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId><version>3.1.0</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.2.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><repositories><repository><id>public</id><name>Aliyun Public Repository</name><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring</id><name>Aliyun Spring Repository</name><url>https://maven.aliyun.com/repository/spring</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository><repository><id>maven</id><name>Maven Central Repository</name><url>https://repo.maven.apache.org/maven2</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository><repository><id>sonatype</id><name>Sonatype Repository</name><url>https://s01.oss.sonatype.org/content/groups/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>public</id><name>aliyun nexus</name><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories><build><finalName>sso-server</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.6.5</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>

3. 添加认证服务器配置文件

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.zjtx.tech.security.demo.provider.MyAuthenticationEntryPoint;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate JdbcTemplate jdbcTemplate;@Value("${spring.security.oauth2.authorization-server.issuer}")private String issuer;@Resourceprivate TokenAuthenticationFilter jwtTokenAuthenticationFilter;@Resourceprivate MyAuthenticationEntryPoint authenticationEntryPoint;@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {// 配置OAuth2授权服务器的默认安全设置OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);// 配置授权端点和OIDC,默认设置http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).authorizationEndpoint(Customizer.withDefaults()).oidc(Customizer.withDefaults());// 配置异常处理,包括访问被拒绝和未认证的处理方式http.exceptionHandling((exceptions) -> exceptions.accessDeniedHandler((request, response, accessDeniedException) ->accessDeniedException.printStackTrace()).authenticationEntryPoint(authenticationEntryPoint))// 配置OAuth2资源服务器,使用JWT令牌验证.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));// 构建并返回安全配置return http.build();}@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) -> web.ignoring().requestMatchers("/css/**", "/js/**", "/images/**","/webjars/**", "/favicon.ico", "/login.html");}@Bean@Order(2)public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)throws Exception {// 配置HTTP安全设置,包括授权规则、CORS、会话管理、过滤器、表单登录、CSRF保护和异常处理http// 设置对特定路径的请求不需要认证,如/auth/**和/oauth2/**的请求.authorizeHttpRequests((authorize) ->authorize.requestMatchers(new AntPathRequestMatcher("/auth/**"),new AntPathRequestMatcher("/oauth2/**")).permitAll()// 设置任何其他请求都需要认证.anyRequest().authenticated())// 配置CORS,使用默认设置.cors(Customizer.withDefaults())// 设置总是创建会话,确保每次请求都有会话.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))// 在UsernamePasswordAuthenticationFilter之前添加自定义的JWT令牌认证过滤器.addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)// 配置表单登录,使用默认设置.formLogin(Customizer.withDefaults())// 禁用CSRF保护.csrf(AbstractHttpConfigurer::disable)// 配置异常处理,设置未认证的请求跳转到登录页面.exceptionHandling((exceptions) -> {exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login.html"));});// 构建并返回安全配置对象return http.build();}@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("oauth2-client").clientSecret(passwordEncoder.encode("123456"))// 客户端认证基于请求头.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 配置授权的支持方式.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("https://www.baidu.com").scope("user").scope("admin")// 客户端设置,设置用户需要确认授权.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false) .build()).build();JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());if (repositoryByClientId == null) {registeredClientRepository.save(registeredClient);}return registeredClientRepository;}@Beanpublic JWKSource<SecurityContext> jwkSource() {// 配置 JWK 源KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}private static KeyPair generateRsaKey() {// 生成 RSA 密钥对KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();} catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}/*** 配置 JWT 解码器*/@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}/*** 配置授权服务器设置*/@Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().issuer(issuer).build();}/*** 配置认证管理器*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}/*** 配置安全上下文存储库*/@Beanpublic SecurityContextRepository securityContextRepository() {return new HttpSessionSecurityContextRepository();}/*** 配置授权服务*/@Beanpublic OAuth2AuthorizationService auth2AuthorizationService(RegisteredClientRepository registeredClientRepository) {return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);}/*** 配置授权同意服务*/@Beanpublic OAuth2AuthorizationConsentService authorizationConsentService(RegisteredClientRepository registeredClientRepository) {return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}
}

针对上述配置,作如下说明:

  1. OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http):应用默认的安全配置。
  2. http.getConfigurer(OAuth2AuthorizationServerConfigurer.class):配置授权端点和OIDC的默认设置, 开启了OIDC后会开启一些节点,如/oauth2/jwks, /.well-known/openid-configuration
  3. http.exceptionHandling((exceptions) -> exceptions:配置异常处理,包括访问被拒绝和未认证的处理方式。
  4. http.oauth2ResourceServer((resourceServer) -> resourceServer:配置OAuth2资源服务器,使用JWT令牌验证。
  5. webSecurityCustomizer():定义了一个Web安全自定义器,用于忽略某些请求路径的安全检查。
  6. defaultSecurityFilterChain(http):配置HTTP安全设置,包括授权规则、CORS、会话管理、过滤器、表单登录、CSRF保护和异常处理。
  7. registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder):配置注册客户端存储库。
  8. jwkSource():配置JWK源,用于生成RSA密钥对。
  9. jwtDecoder(JWKSource jwkSource):配置JWT解码器。
  10. authorizationServerSettings():配置授权服务器设置。
  11. authenticationManager(AuthenticationConfiguration authenticationConfiguration):配置认证管理器。
  12. securityContextRepository():配置安全上下文存储库。
  13. auth2AuthorizationService(RegisteredClientRepository registeredClientRepository):配置授权服务。
  14. authorizationConsentService(RegisteredClientRepository registeredClientRepository):配置授权同意服务。

4. 添加配置文件

server:port: 8080spring:datasource:driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourceurl: jdbc:mysql://ip:port/dbname?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTCusername: usernamepassword: passwordsecurity:oauth2:authorization-server:issuer: http://localhost:8080logging:level:org.springframework.security: info

数据库脚本文件:

CREATE TABLE oauth2_registered_client (id varchar(100) NOT NULL,client_id varchar(100) NOT NULL,client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,client_secret varchar(200) DEFAULT NULL,client_secret_expires_at timestamp DEFAULT NULL,client_name varchar(200) NOT NULL,client_authentication_methods varchar(1000) NOT NULL,authorization_grant_types varchar(1000) NOT NULL,redirect_uris varchar(1000) DEFAULT NULL,post_logout_redirect_uris varchar(1000) DEFAULT NULL,scopes varchar(1000) NOT NULL,client_settings varchar(2000) NOT NULL,token_settings varchar(2000) NOT NULL,PRIMARY KEY (id)
);/*
IMPORTANT:If using PostgreSQL, update ALL columns defined with 'blob' to 'text',as PostgreSQL does not support the 'blob' data type.
*/
CREATE TABLE oauth2_authorization (id varchar(100) NOT NULL,registered_client_id varchar(100) NOT NULL,principal_name varchar(200) NOT NULL,authorization_grant_type varchar(100) NOT NULL,authorized_scopes varchar(1000) DEFAULT NULL,attributes blob DEFAULT NULL,state varchar(500) DEFAULT NULL,authorization_code_value blob DEFAULT NULL,authorization_code_issued_at timestamp DEFAULT NULL,authorization_code_expires_at timestamp DEFAULT NULL,authorization_code_metadata blob DEFAULT NULL,access_token_value blob DEFAULT NULL,access_token_issued_at timestamp DEFAULT NULL,access_token_expires_at timestamp DEFAULT NULL,access_token_metadata blob DEFAULT NULL,access_token_type varchar(100) DEFAULT NULL,access_token_scopes varchar(1000) DEFAULT NULL,oidc_id_token_value blob DEFAULT NULL,oidc_id_token_issued_at timestamp DEFAULT NULL,oidc_id_token_expires_at timestamp DEFAULT NULL,oidc_id_token_metadata blob DEFAULT NULL,refresh_token_value blob DEFAULT NULL,refresh_token_issued_at timestamp DEFAULT NULL,refresh_token_expires_at timestamp DEFAULT NULL,refresh_token_metadata blob DEFAULT NULL,user_code_value blob DEFAULT NULL,user_code_issued_at timestamp DEFAULT NULL,user_code_expires_at timestamp DEFAULT NULL,user_code_metadata blob DEFAULT NULL,device_code_value blob DEFAULT NULL,device_code_issued_at timestamp DEFAULT NULL,device_code_expires_at timestamp DEFAULT NULL,device_code_metadata blob DEFAULT NULL,PRIMARY KEY (id)
);CREATE TABLE oauth2_authorization_consent (registered_client_id varchar(100) NOT NULL,principal_name varchar(200) NOT NULL,authorities varchar(1000) NOT NULL,PRIMARY KEY (registered_client_id, principal_name)
);

5. resources下的静态页面

本文目前的配置是前后端不分离场景的实现,也就是说它的登录页面其实是在resources文件夹下的。

如果需要做前后端分离的实现的话需要给前端返回401,由前端重定向到登录页面即可。

这里给出简单示例:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>统一身份认证-登陆</title><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta http-equiv="Access-Control-Allow-Origin" content="*"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="format-detection" content="telephone=no"><link rel="stylesheet" href="./js/layui/css/layui.css" /><link rel="stylesheet" href="./css/layui-blue.css" /><script type="text/javascript" src="./js/layui/layui.all.js" ></script><script type="text/javascript" src="./js/common.js" ></script><!--[if lt IE 9]><script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script><script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script><![endif]-->
</head>
<body>
<div class="main-body"><div class="login-main"><div class="login-top"><span>统一身份认证平台</span><span class="bg1"></span><span class="bg2"></span></div><form class="layui-form login-bottom" id="loginForm"><div class="center"><div class="item"><span class="icon icon-2"></span><input type="text" name="username" lay-verify="required"  placeholder="请输入登录账号" maxlength="24"/></div><div class="item"><span class="icon icon-3"></span><input type="password" name="password" lay-verify="required"  placeholder="请输入密码" maxlength="20"><span class="bind-password icon icon-4"></span></div></div><div class="layui-form-item" style="text-align:center; width:100%;height:100%;margin:0;"><button class="login-btn" lay-submit lay-filter="login">立即登录</button></div></form></div>
</div>
<div class="footer">这里是版权信息,请自行替换
</div>
<script>console.log("lay-ui:", layui)// 获取URL中的查询字符串const urlParams = new URLSearchParams(window.location.search);// 通过参数名获取参数值const redirectUri = urlParams.get('redirect_uri');console.log('redirectUri: ' + redirectUri);layui.use(['form','jquery'], function () {var $ = layui.$, form = layui.form,  layer = layui.layer;// 登录过期的时候,跳出ifram框架if (top.location != self.location) top.location = self.location;$('.bind-password').on('click', function () {if ($(this).hasClass('icon-5')) {$(this).removeClass('icon-5');$("input[name='password']").attr('type', 'password');} else {$(this).addClass('icon-5');$("input[name='password']").attr('type', 'text');}});$('.icon-nocheck').on('click', function () {if ($(this).hasClass('icon-check')) {$(this).removeClass('icon-check');} else {$(this).addClass('icon-check');}});layui.form.on('submit(login)', function(data){$.ajax({url: '/auth/usernamePwd',type: 'GET',// dataType: 'json',data: {username: data.field.username,password: data.field.password},success: function(data) {if(data.code === 200) {layer.msg('登录成功',{time : 1000},function(){console.log("登录成功", data)// location.href='./index.html';if(redirectUri) {location.href = redirectUri;} else {location.href='./test/demo';}})}if(data.code === 6000) {layer.msg(data.msg, {time : 1000})}}})return false;});});
</script>
</body>
</html>

登录页的效果如下:
登录页面

6. 其他说明

  1. 本文中涉及的一些认证授权、异常处理或者公共类相关代码在之前文章有提到过,这里不再重复。

参考文章: 【工作记录】基于springboot3+springsecurity6实现多种登录方式(一)_springboot3 security token 登录-CSDN博客

【工作记录】基于springboot3+springsecurity实现多种方式登录及鉴权(二)_spring security实现不通过用户名密码鉴权-CSDN博客

  1. 上述配置中客户端的配置是通过Jdbc直接存储到数据库然后再查询出来的,这里还有一些其他的自定义的实现方式,本文仅做示例,不做限制。

测试

期望实现的效果

  1. 通过页面访问时跳转到登录页面,登录成功后可以正常携带code跳转到目标页面
  2. 上文提到的应当有的端点都可以正常访问

开始测试

流程回顾

上篇文章我们提到过oauth2.0的认证的完整流程,认证授权方式有多种,本文我们以authorize_code为例,我们这里再回顾一下:

  1. 授权请求:
    第三方应用(客户端)希望访问资源所有者的资源,但资源存储在资源服务器上。客户端首先构建一个URL,指向授权服务器的/authorize端点,并添加必要的参数,如client_id(客户端标识符)、response_type=code(表示请求授权码)、redirect_uri(授权后重定向的URL)以及scope(请求的权限范围)。

  2. 用户认证与授权:
    用户被重定向到授权服务器的登录页面进行身份验证。
    验证成功后,用户会被呈现一个授权页面,询问是否允许客户端应用访问其资源。
    如果用户同意,授权服务器将用户重定向回客户端提供的redirect_uri,并在URL中附加一个授权码(code)作为查询参数。

  3. 兑换令牌:
    客户端使用接收到的授权码,连同其client_id和client_secret(如果已配置),向授权服务器的/token端点发送一个HTTP POST请求,请求访问令牌(access token)和刷新令牌(refresh token)。
    授权服务器验证请求后,返回一个JSON格式的响应,其中包含access_token、token_type(通常是Bearer)、expires_in(过期时间)以及可选的refresh_token。

  4. 访问受保护资源:
    客户端现在可以使用access_token来访问资源服务器上的受保护资源。在每个请求的头部中,客户端需要包含Authorization: Bearer <access_token>这样的字段。

    当然还有另外一种实现就是第三方通过access_token结合/oauth2/userinfo端点获取用户信息并跟自己的用户体系关联,生成自己跌token并返回给前端,后续使用自己生成的token进行资源访问。

  5. 刷新令牌:
    当access_token即将过期时,客户端可以使用refresh_token来请求一个新的access_token,而无需再次进行完整的授权流程。
    刷新令牌请求同样发送到/token端点,但使用不同的参数。

  6. 令牌撤销:
    如果不再需要访问令牌,客户端可以请求撤销access_token或refresh_token,以防止未来的访问尝试。

授权链接组装

根据oauth2.0协议的规范,我们请求/oauth2/authorize授权端点时需要提供的参数包含: client_idgrant_typescoperedirect_uri,还有个可选的state参数。

正常来讲,应该是有个应用注册的页面的,为了示例,上文中我们手动新建了一个客户端应用,配置如下:

@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("oauth2-client").clientSecret(passwordEncoder.encode("123456"))// 客户端认证基于请求头.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 配置授权的支持方式.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("https://www.baidu.com").scope("user").scope("admin")// 客户端设置,设置用户需要确认授权.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false) .build()).build();JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());if (repositoryByClientId == null) {registeredClientRepository.save(registeredClient);}return registeredClientRepository;}

在项目启动的时候会执行这些代码,简单点说就是向数据库中写入了一条客户端应用记录,如果存在的话就不做处理。应用id和密钥对应的是oauth2-client123456

如上所述,我们可以组装的授权链接格式如下:

http://localhost:8080/oauth2/authorize?client_id=oauth2-client&grant_type=code&scope=user&redirect_uri=https://www.baidu.com

在浏览器访问上述链接,可以看到能正常跳转到登录页面。

登录页面002

输入用户名密码并登录成功后,会跳转到授权页面,

在这里插入图片描述

默认授权界面并不美观,后面我们会研究下如何自定义授权页面。

点击提交后会生成一条授权记录,后续就不再需要授权。

授权成功后会携带code跳转到配置的redirect_uri地址,示例中就是https://www.baidu.com?code=xxxxxxxxxxxxxxxxxxxx

默认授权页面

至此我们的授权和跳转就完成了,接下来就是使用code换取token,再用token获取用户信息了,这部分我们使用接口测试。

获取token接口测试

获取token参数

获取token参数
获取token结果

请求及响应说明

  1. 请求地址: /oauth2/token 对应的过滤器是OAuth2TokenEndpointFilter
  2. 请求头参数中Authorization值为Bearer base64(username:password)
  3. 请求体中参数grant_type为authorization_code,表示授权类型是授权码方式,code即上一步跳转时携带的code参数,redirect_uri需要和应用注册时的uri保持一致。
  4. 请求响应返回了access_token,refresh_token和expires_in这些常用数据,不做解释。
  5. 需要注意code和token的有效期

到此获取token就完成了,接下来就是通过token获取用户信息了。

写不动了,留到下一篇吧。

小结

本文在之前文章的基础上实现了sso服务端,并完成了部分测试。

针对以上内容,有任何疑问或者建议欢迎留言。

创作不易,欢迎一键三连~~~

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

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

相关文章

python自动化笔记:操作mysql数据库

操作mysql数据库常见方法 1、第三方库&#xff1a;pymysql1.1、安装pymysql1.2、连接数据库1.3、连接指定数据库1.4 创建数据库、创建表1.5、表中插入数据1.6、批量插入数据1.7、获取查询结果数据1.8、防sql注入&#xff0c;sql语句中一般用占位符传值 2、标准库 &#xff1a;m…

【《Kafka 入门指南:从零基础到精通》】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索KAFKA&#xff0c;本篇文章主要讲述了&#xff1a;消息队列的基础知识&#xff0c;KAFKA消息队列等等。欢迎大家一起探索讨论&#xff01;&#xff01;&#x…

rocketMQ5.0事务消息实战

事务消息逻辑 docker部署容器&#xff0c;并且创建消息 dd首先我们来docker 部署rocketMQ与rocketMQDashBoard docker ps查看rocketMQ 容器名称 docker ps 进入容器内部 docker exec -it rmqnamesrv /bin/bash 创建事务消息 MeessageType: TRANSACTION sh mqadmin upda…

Linux驱动.之I2C,iic驱动层(二)

一、 Linux下IIC驱动架构 本篇只分析&#xff0c;一个整体框架。 1、首先说说&#xff0c;单片机&#xff0c;的i2c硬件接口图&#xff0c;一个i2c接口&#xff0c;通过sda和scl总线&#xff0c;外接了多个设备device&#xff0c;通过单片机&#xff0c;来控制i2c的信号发生&…

解锁数据“智能”背后的秘密

在这个被数据洪流包围的时代&#xff0c;每一秒都有无数的信息在生成、传递、分析。但你是否曾好奇&#xff0c;这些数据是如何从简单的数字、文字转化为推动社会进步、改变生活方式的“智能”力量的&#xff1f;今天&#xff0c;就让我们一起揭开数据“智能”背后的神秘面纱&a…

C语言文达学院班级管理系统-计算机毕业设计源码03499

摘 要 本文阐述了一个C语言文达学院班级管理系统的设计与实现过程。该系统充分利用ASP.NET的轻量级、灵活性和可扩展性&#xff0c;旨在为文达学院提供高效、便捷的班级管理系统。通过详细的需求分析、技术选型、系统设计、开发实现、测试与调试以及部署与上线等步骤&#xff0…

通过python管理mysql

打开防火墙端口&#xff1a; 使用 firewall-cmd 命令在防火墙的 public 区域中永久添加 TCP 端口 7500&#xff08;FRP 控制台面板端口&#xff09;、7000&#xff08;FRP 服务端端口&#xff09;以及端口范围 6000-6100&#xff08;一组客户端端口&#xff09;。这些端口是 FR…

Unity补完计划 之 动态控制TileMap

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 1.TileMap &TileBase Unity - Scripting API: Tilemap &#xff0c;看手册内容太多了故介绍几个常用的公共方法 首…

【凌鸥学园】电机电控课程学习,挑战自我!

电控达人集结号&#xff01;凌鸥学园精心打造的电机电控课程&#xff0c;现面向全体爱好者及专业人士免费开放&#xff01; 课程内容从基础原理到高级应用&#xff0c;全方位助力你快速掌握电机电控精髓&#xff0c;实现技能飞跃&#xff01; 挑战成功&#xff0c;双重奖励等…

12. 矩阵中的路径

comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9812.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84/README.md 面试题 12. 矩阵中的路径 题目描述 给定一个 m x n 二维字符网格 board…

Java的反射原理

反射允许程序在运行时检查或修改其类、接口、字段和方法的行为。反射主要通过java.lang.reflect包中的类和接口实现&#xff0c;它主要用于以下目的&#xff1a; 在运行时分析类的能力&#xff1a;通过反射&#xff0c;可以在运行时检查类的结构&#xff0c;比如它的方法、构造…

【RAG检索增强生成】Ollama+AnythingLLM本地搭建RAG大模型私有知识库

目录 前言一、Ollama&#xff1a;革新性的本地LLM服务工具1.核心优势2.技术亮点 二、AnythingLLM 概览1.核心特性2.技术生态支持 三、搭建本地智能知识库1. Ollama的安装启航2. AnythingLLM的安装对接3. AnythingLLM的配置精调4. 工作区与文档管理5. 聊天与检索的智能交互 四、…

计算机毕业设计 校园失物招领网站 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

封装el-table 基于element封装可配置JSON表格组件

基于element封装可配置JSON表格组件 话不多说直接贴代码&#xff0c;复制运行即可查看效果 子组件全部代码 <template><div class"custom-table"><el-table:data"tableData"borderstyle"width: 100%"size"mini"max-h…

负载均衡之HAProxy超全内容!!!

一、负载均衡 1.1 负载均衡概念 负载均衡&#xff08;Load Balance&#xff0c;简称 LB&#xff09;是高并发、高可用系统必不可少的关键组件&#xff0c;目标是尽力将网络流量平均分发到多个服务器上&#xff0c;以提高系统整体的响应速度和可用性。 1.2 软件负载均衡 软件…

做报表用什么工具?不想再用Excel了!!!

一、什么是中国式报表&#xff1f; 不知道大家现在还是使用Excel来制作报表&#xff0c;然后跟领导汇报工作吗&#xff1f;虽然Excel功能很强大&#xff0c;但是用Excel做过中国式报表的小伙伴一定知道它的制作过程有多复杂。 中国式报表可以用一句话简单概括&#xff1a;格式…

Mozilla Firefox侧边栏和垂直标签在131 Nightly版本中开始试用

垂直选项卡和全新的侧边栏体验现已在Mozilla Firefox Nightly 131 中提供。这一更新备受社区期待和要求&#xff0c;我们期待看到它如何提高您的浏览效率和工作效率。如果您想体验一下这项正在进行中的工作&#xff0c;请这样操作&#xff1a; 更新到最新的Nightly版 转到设置…

uniapp本地打包app安装说明

uniapp本地打包app安装说明 目录 uniapp本地打包app安装说明一、打包说明1.HBuilder X 生成本地打包资源2.Android Studio和App离线SDK环境准备2.1 下载Android Studio和 App离线SDK2.2 资源替换2.3 id属性值修改。2.4 添加provider信息到AndroidManifest.xml中的<applicati…

使用Hugging Face构建大型语言模型应用

在本文中&#xff0c;我们将介绍如何使用Hugging Face的大型语言模型&#xff08;LLM&#xff09;构建一些常见的应用&#xff0c;包括摘要&#xff08;Summarization&#xff09;、情感分析&#xff08;Sentiment analysis&#xff09;、翻译&#xff08;Translation&#xff…

Leetcode JAVA刷刷站(14)最长公共前缀

一、题目概述 二、思路方向 在Java中&#xff0c;要编写一个函数来查找字符串数组中的最长公共前缀&#xff0c;我们可以遵循以下步骤&#xff1a; 处理边界条件&#xff1a;如果数组为空或长度为0&#xff0c;直接返回空字符串。初始化最长公共前缀&#xff1a;将数组的第一个…