Spring Authorization Server介绍
Spring Authorization Server 是一个框架,它提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。
它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 身份提供者和 OAuth2 授权服务器产品提供了一个安全、轻量级和可定制的基础
因为随着网络和设备的发展,原先的 OAuth 2.0 已经不能满足现今的需求了,OAuth 社区对 OAuth 2.0 中的几种授权模式进行了取舍和优化,并增加一些新的特性, 于是推出了 OAuth 2.1,
而原先的 Spring Security OAuth 2.0 使用的是 OAuth 2.0 协议,为满足新的变化,Spring Security 团队重新写了一套叫 Spring Authorization Server 的认证授权框架来替换原先的 Spring Security OAuth 2.0
实现方式的不同
Springboot2.x Oauth2实现:
- Spring Security Oauth2 支持搭建授权服务器和资源服务器
Springboot3.x Oauth2实现:
- Spring Security6 自身提供资源和客户端类库支持
- Spring Authorization Server支持授权服务器搭建
OAuth2.1协议
OAuth 2.1去掉了OAuth2.0中的密码模式、简化模式,增加了设备授权码模式,同时也对授权码模式增加了PKCE扩展
OAuth2.1协议 官网:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07
OAuth 2.1 的变化 有道云笔记:https://note.youdao.com/s/DRnLx34U
授权码模式+PKCE扩展
在OAuth2.0版本的授权码的基础上,进行了扩展。下面的之前授权码模式的流程:
- 1、用户访问客户端,后者将前者导向授权服务器。
- 2、用户选择是否给予客户端授权。
- 3、假设用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
- 4、客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- 5、授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
在第3个步骤中,授权服务器通过重定向URL,将授权码Code发送给客户端,此时可能会被黑客拦截,他得到了code,他去利用code进行申请token,进而去访问资源服务器 为了减轻这种攻击,官方增加PKCE扩展
- 1. 客户端通过“/oauth2/authorize”地址向认证服务器发起获取授权码请求的时候增加两个参数,即 code_challenge 和 code_challenge_method
- 2. 认证服务器给客户端返回授权码,同时记录下 code_challenge、code_challenge_method 的值。
- 3. 客户端使用 code 向认证服务器获取 Access Token 的时候,带上 code_verifier 参数,其值为步骤A加密前的初始值。
- 4. 认证服务器收到步骤 C 的请求时,将 code_verifier 的值使用 code_challenge_method 的方法进行加密,然后将加密后的值与步骤A中的 code_challenge 进行比较,看看是否一致
code_challenge_method 是加密方法(例如:S256 或 plain(废弃))code_challenge 是使用code_challenge_method加密方法加密后的值
搭建授权服务
版本要求
spring boot 3.x
jdk版本 17
Spring Authorization Server重要组件
- SecurityFilterChain -> authorizationServerSecurityFilterChain Spring Security的过滤器链,用于协议端点的。
- SecurityFilterChain -> defaultSecurityFilterChain Spring Security的过滤器链,用于Spring Security的身份认证
- UserDetailsService 主要进行用户身份验证(存入库中)
- RegisteredClientRepository 主要用于管理客户端
- JWKSource 用于签名访问令牌
- KeyPair 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource
- JwtDecoder JwtDecoder的一个实例,用于解码已签名的访问令牌
- AuthorizationServerSettings 用于配置Spring Authorization Server的AuthorizationServerSettings实例
准备sql
授权相关的表
-- 用户授权确认表
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)
);
-- 用户认证信息表
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 DATETIME DEFAULT NULL,authorization_code_expires_at DATETIME DEFAULT NULL,authorization_code_metadata blob DEFAULT NULL,access_token_value blob DEFAULT NULL,access_token_issued_at DATETIME DEFAULT NULL,access_token_expires_at DATETIME 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 DATETIME DEFAULT NULL,oidc_id_token_expires_at DATETIME DEFAULT NULL,oidc_id_token_metadata blob DEFAULT NULL,refresh_token_value blob DEFAULT NULL,refresh_token_issued_at DATETIME DEFAULT NULL,refresh_token_expires_at DATETIME DEFAULT NULL,refresh_token_metadata blob DEFAULT NULL,user_code_value blob DEFAULT NULL,user_code_issued_at DATETIME DEFAULT NULL,user_code_expires_at DATETIME DEFAULT NULL,user_code_metadata blob DEFAULT NULL,device_code_value blob DEFAULT NULL,device_code_issued_at DATETIME DEFAULT NULL,device_code_expires_at DATETIME DEFAULT NULL,device_code_metadata blob DEFAULT NULL,PRIMARY KEY (id)
);
-- 客户端表
CREATE TABLE oauth2_registered_client
(id varchar(100) NOT NULL,client_id varchar(100) NOT NULL,client_id_issued_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,client_secret varchar(200) DEFAULT NULL,client_secret_expires_at DATETIME 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)
);
用户表
CREATE TABLE `sys_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '用户名',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '密码',`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '姓名',`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '描述',`status` tinyint DEFAULT NULL COMMENT '状态(1:正常 0:停用)',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `idx_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户表';INSERT INTO sys_user (`id`, `username`, `password`, `name`, `description`, `status`) VALUES (1, 'admin', '$2a$10$8fyY0WbNAr980e6nLcPL5ugmpkLLH3serye5SJ3UcDForTW5b0Sx.', '测试用户', 'Spring Security 测试用户', 1);
pom文件
<?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>3.1.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>oauthServer</artifactId><version>0.0.1-SNAPSHOT</version><name>oauthServer</name><description>oauthServer</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
yml配置文件
spring:application:name: oauthServerdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://39.102.208.240:3306/auth?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8username: rootpassword: root123456
server:port: 9800
在config下建配置类AuthorizationConfig
package org.example.oauthserver.config;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 org.example.oauthserver.util.SecurityUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
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.RegisteredClient;
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.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
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.util.matcher.MediaTypeRequestMatcher;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;/*** 认证配置* {@link EnableWebSecurity} 注解有两个作用:* 1. 加载了WebSecurityConfiguration配置类, 配置安全认证策略。* 2. 加载了AuthenticationConfiguration, 配置了认证信息。** ==============================================重要组件===============================================* SecurityFilterChain -> authorizationServerSecurityFilterChain Spring Security的过滤器链,用于协议端点的。* SecurityFilterChain -> defaultSecurityFilterChain Spring Security的过滤器链,用于Spring Security的身份认证* UserDetailsService 主要进行用户身份验证* RegisteredClientRepository 主要用于管理客户端* JWKSource 用于签名访问令牌* KeyPair 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource* JwtDecoder JwtDecoder的一个实例,用于解码已签名的访问令牌* AuthorizationServerSettings 用于配置Spring Authorization Server的AuthorizationServerSettings实例**/
@Configuration
@EnableWebSecurity
public class AuthorizationConfig {/*** 配置端点的过滤器链** @param http spring security核心配置类* @return 过滤器链* @throws Exception 抛出*/@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,RegisteredClientRepository registeredClientRepository,AuthorizationServerSettings authorizationServerSettings) throws Exception {// 配置默认的设置,忽略认证端点的csrf校验OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)// 开启OpenID Connect 1.0协议相关端点.oidc(Customizer.withDefaults());http//将需要认证的请求,重定向到login进行登录认证。.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),new MediaTypeRequestMatcher(MediaType.TEXT_HTML)));http// 处理使用access token访问用户信息端点和客户端注册端点.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}/*** 配置认证相关的过滤器链** @param http spring security核心配置类* @return 过滤链配置* @throws Exception 抛出*/@Bean@Order(2)public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize// 所有请求都需要认证.anyRequest().authenticated()).formLogin(Customizer.withDefaults());// 由Spring Security过滤链中UsernamePasswordAuthenticationFilter过滤器拦截处理“login”页面提交的登录信息 和异常处理http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()).accessDeniedHandler(SecurityUtils::exceptionHandler).authenticationEntryPoint(SecurityUtils::exceptionHandler));return http.build();}/*** 自定义jwt,将权限信息放至jwt中** @return OAuth2TokenCustomizer的实例*/@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer() {return context -> {// 检查登录用户信息是不是UserDetails,排除掉没有用户参与的流程if (context.getPrincipal().getPrincipal() instanceof UserDetails user) {// 获取申请的scopesSet<String> scopes = context.getAuthorizedScopes();// 获取用户的权限Collection<? extends GrantedAuthority> authorities = user.getAuthorities();// 提取权限并转为字符串Set<String> authoritySet = Optional.ofNullable(authorities).orElse(Collections.emptyList()).stream()// 获取权限字符串.map(GrantedAuthority::getAuthority)// 去重.collect(Collectors.toSet());// 合并scope与用户信息authoritySet.addAll(scopes);JwtClaimsSet.Builder claims = context.getClaims();// 将权限信息放入jwt的claims中(也可以生成一个以指定字符分割的字符串放入)claims.claim("authorities", authoritySet);}};}/*** 自定义jwt解析器,设置解析出来的权限信息的前缀与在jwt中的key** @return jwt解析器 JwtAuthenticationConverter*/@Beanpublic JwtAuthenticationConverter jwtAuthenticationConverter() {JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();// 设置解析权限信息的前缀,设置为空是去掉前缀grantedAuthoritiesConverter.setAuthorityPrefix("");// 设置权限信息在jwt claims中的keygrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);return jwtAuthenticationConverter;}/*** 配置密码解析器,使用BCrypt的方式对密码进行加密和验证** @return BCryptPasswordEncoder*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** Spring Security的配置* 设置用户信息,校验用户名、密码* 正常的流程是自定义一个service类,实现UserDetailsService接口,去查询DB 查询用户信息,封装为一个UserDetails对象返回* 这里就直接写一个user存入内存中进行测试* @return*/
// @Bean
// public UserDetailsService users(PasswordEncoder passwordEncoder) {
// UserDetails user = User.withUsername("admin")
// .password(passwordEncoder.encode("123456"))
// .roles("admin", "normal")
// .authorities("app", "web")
// .build();
// return new InMemoryUserDetailsManager(user);
// }/*** 配置客户端Repository** @param jdbcTemplate db 数据源信息* @param passwordEncoder 密码解析器* @return 基于数据库的repository 对应表:oauth2_registered_client*/@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())// 客户端id.clientId("oidc-client")// 加密 客户端秘钥{noop}开头,表示“secret”以明文存储.clientSecret(passwordEncoder.encode("123456"))// 客户端认证方式,就使用默认的认证方式基于请求头的认证.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 配置资源服务器使用该客户端获取授权时支持的方式.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)// 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问.redirectUri("http://127.0.0.1:9700/login/oauth2/code/messaging-client-oidc").redirectUri("https://www.baidu.com")// 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken.scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE)// 自定scope.scope("read").scope("write")// 客户端设置,设置用户需要确认授权.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();// 基于db存储客户端,还有一个基于内存的实现 InMemoryRegisteredClientRepositoryJdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);// 初始化客户端RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());if (repositoryByClientId == null) {registeredClientRepository.save(registeredClient);}// PKCE客户端RegisteredClient pkceClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("pkce-oidc-client")// 公共客户端.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)// 授权码模式,因为是扩展授权码流程,所以流程还是授权码的流程,改变的只是参数.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)// 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问.redirectUri("http://oauthClient:9700/login/oauth2/code/messaging-client-oidc").redirectUri("https://www.baidu.com").clientSettings(ClientSettings.builder().requireProofKey(Boolean.TRUE).build())// 自定scope.scope("read").scope("write").build();RegisteredClient findPkceClient = registeredClientRepository.findByClientId(pkceClient.getClientId());if (findPkceClient == null) {registeredClientRepository.save(pkceClient);}return registeredClientRepository;}/*** 授权信息* 对应表:oauth2_authorization*/@Beanpublic OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {// 基于db的oauth2认证服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationServicereturn new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);}/*** 授权确认*对应表:oauth2_authorization_consent*/@Beanpublic OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {// 基于db的授权确认管理服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationConsentServicereturn new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}/*** 配置jwk源,使用非对称加密,公开用于检索匹配指定选择器的JWK的方法** @return JWKSource*/@Beanpublic JWKSource<SecurityContext> jwkSource() {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);}/*** 生成rsa密钥对,提供给jwk** @return 密钥对*/private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();} catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}/*** 配置jwt解析器** @param jwkSource jwk源* @return JwtDecoder*/@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}/*** 添加授权服务器配置,配置授权服务器请求地址* @return AuthorizationServerSettings*/@Beanpublic AuthorizationServerSettings authorizationServerSettings() {// 什么都不配置,则使用默认地址return AuthorizationServerSettings.builder().build();}}
添加用户信息
实体信息如下
package org.example.oauthserver.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class SysUserEntity implements Serializable {/*** 主键*/@TableId(type = IdType.AUTO)private Integer id;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 名字*/private String name;/*** 描述*/private String description;/*** 状态*/private Integer status;}
mapper如下
package org.example.oauthserver.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.oauthserver.entity.SysUserEntity;@Mapper
public interface SysUserMapper extends BaseMapper<SysUserEntity> {}
service如下
package org.example.oauthserver.service;import org.example.oauthserver.entity.SysUserEntity;public interface SysUserService {/**** 根据用户名查询用户信息*/SysUserEntity selectByUsername(String username);}
package org.example.oauthserver.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.oauthserver.entity.SysUserEntity;
import org.example.oauthserver.mapper.SysUserMapper;
import org.example.oauthserver.service.SysUserService;
import org.springframework.stereotype.Service;@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserEntity> implements SysUserService {@Overridepublic SysUserEntity selectByUsername(String username) {LambdaQueryWrapper<SysUserEntity> lambdaQueryWrapper = new LambdaQueryWrapper();lambdaQueryWrapper.eq(SysUserEntity::getUsername,username);return this.getOne(lambdaQueryWrapper);}
}
UserDetailsService如下
package org.example.oauthserver.service.impl;import jakarta.annotation.Resource;
import org.example.oauthserver.entity.SysUserEntity;
import org.example.oauthserver.service.SysUserService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Resourceprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUserEntity sysUserEntity = sysUserService.selectByUsername(username);List<SimpleGrantedAuthority> grantedAuthorityList = Arrays.asList("USER").stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return new User(username,sysUserEntity.getPassword(),grantedAuthorityList);}
}
访问测试授权码模式
访问授权端点
http://127.0.0.1:9800/oauth2/authorize?client_id=oidc-client&response_type=code&scope=read&redirect_uri=https://www.baidu.com
http://127.0.0.1:9800/oauth2/authorize?client_id=oidc-client&response_type=code&scope=read&redirect_uri=https://www.baidu.com
- response_type=code 表示当前是授权码模式
- client_id、scope这两个请求参数要和客户端往授权服务器注册信息对应上,如果客户端注册时没有profile openid中的某一个,那么授权服务器就不会返回code
- redirect_uri 重定向地址,也就是用户授权之后 code应该发给谁,目前我们还没有编写客户端的接口,所以这里就直接写百度,然后从浏览器拿code进行测试
拿到授权码
申请token端点/oauth/token ,别忘记填写客户端信息
刷新token
访问测试客户端模式
客户端模式高度信任,所以很简单,直接输入客户端信息和授权类型即可