maven版本(有部分没有改)
gradle版本(全部最新修改)
前言
注意:我本地没有生成公钥和私钥,所以每次启动项目jwkSource都会重新生成,导致之前认证的token都会失效,具体如何生成私钥和公钥以及怎么配置到授权服务器中,网上有很多方法自行实现即可
之前有个项目用的0.0.3的,正好最近想研究研究,所以就去了官网看文档研究了一下,1.1.1基于的事security6.x的版本, security6与5.7之前的版本有很大的差别,废话不多说,直接上代码(代码中也有一些注释)
最基础的配置官网都有,这里不去体现,主要体现功能:
- 自定义认证和授权
- 自定义端点拦截器
- 持久化到数据库
版本
依赖项版本springboot3.1.2spring-authorization-server1.1.1jdk17dynamic-datasource-spring-boot3-starter4.1.2mybatis-plus3.5.3
sql文件
代码实操
目录结构
pom文件
需要额外引入spring security cas包原因是启动时(logging等级:org.springframework.security: trace)会报错:java.lang.ClassNotFoundException:org.springframework.security.cas.jackson2.CasJackson2Module错误。
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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.2</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>demo</description><properties><java.version>17</java.version></properties><dependencyManagement><!-- <dependencies>--><!-- <!– SpringBoot的依赖配置–>--><!-- <dependency>--><!-- <groupId>org.springframework.boot</groupId>--><!-- <artifactId>spring-boot-dependencies</artifactId>--><!-- <version>2.5.14</version>--><!-- <type>pom</type>--><!-- <scope>import</scope>--><!-- </dependency>--><!-- </dependencies>--></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency><!-- 添加spring security cas支持
这里需添加spring-security-cas依赖,
否则启动时报java.lang.ClassNotFoundException: org.springframework.security.cas.jackson2.CasJackson2Module错误。
--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.34</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version></dependency><!-- 阿里数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><!-- SpringBoot Web容器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot3-starter</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></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>
授权服务器配置(包名:authenticationServer )
CustomAuthorizationServerConfiguration
importcom.example.demo.config.security.provider.WeChatMiniAppAuthenticationProvider;importcom.example.demo.config.security.provider.converter.WeChatMiniAppAuthenticationConverter;importcom.example.demo.utils.OAuth2ConfigurerUtils;importcom.nimbusds.jose.jwk.source.JWKSource;importcom.nimbusds.jose.proc.SecurityContext;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;importorg.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;importorg.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;importorg.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;importorg.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;importorg.springframework.security.web.util.matcher.RequestMatcher;publicclassCustomAuthorizationServerConfiguration{publicstaticvoidapplyDefaultSecurity(HttpSecurity http,JWKSource<SecurityContext> jwkSource)throwsException{OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =newOAuth2AuthorizationServerConfigurer();OAuth2AuthorizationService authorizationService =OAuth2ConfigurerUtils.getBean(http,OAuth2AuthorizationService.class);// 认证过滤器链
authorizationServerConfigurer.tokenEndpoint(oAuth2TokenEndpointConfigurer ->{
oAuth2TokenEndpointConfigurer.accessTokenRequestConverters( customJwtAuthenticationToken ->{
customJwtAuthenticationToken.add(newOAuth2AuthorizationCodeAuthenticationConverter());
customJwtAuthenticationToken.add(newOAuth2RefreshTokenAuthenticationConverter());
customJwtAuthenticationToken.add(newOAuth2ClientCredentialsAuthenticationConverter());
customJwtAuthenticationToken.add(newWeChatMiniAppAuthenticationConverter());})// 返回accessToken的后置处理器 https://docs.spring.io/spring-authorization-server/docs/current/reference/html/protocol-endpoints.html#oauth2-token-endpoint// .accessTokenResponseHandler()// 异常返回处理器// .errorResponseHandler().authenticationProviders((customProviders)->{// 自定义认证提供者
customProviders.add(newWeChatMiniAppAuthenticationProvider(jwkSource,authorizationService));});});// 端点匹配器RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http.securityMatcher(endpointsMatcher)// .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())// csrf.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)).apply(authorizationServerConfigurer);}}
认证提供者(包名:provider)
WeChatMiniAppAuthenticationProvider
后来在进一步研究的时候,发现还有另一种比较好的实现方式,比下面的要简单,代码我就不上传了,有兴趣可以看看官网
传送门
packagecom.example.demo.config.security.provider;importcom.alibaba.fastjson2.JSONObject;importcom.example.demo.domain.dto.UserDto;importcom.example.demo.config.security.provider.token.WeChatMiniAppAuthenticationToken;importcom.example.demo.utils.JwtUtils;importcom.nimbusds.jose.jwk.source.JWKSource;importcom.nimbusds.jose.proc.SecurityContext;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.AnonymousAuthenticationToken;importorg.springframework.security.authentication.AuthenticationProvider;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.crypto.keygen.Base64StringKeyGenerator;importorg.springframework.security.crypto.keygen.StringKeyGenerator;importorg.springframework.security.oauth2.core.*;importorg.springframework.security.oauth2.jwt.*;importorg.springframework.security.oauth2.server.authorization.OAuth2Authorization;importorg.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;importorg.springframework.security.oauth2.server.authorization.OAuth2TokenType;importorg.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;importorg.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;importorg.springframework.security.oauth2.server.authorization.client.RegisteredClient;importorg.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;importorg.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;importjava.security.Principal;importjava.time.Duration;importjava.time.Instant;importjava.util.Base64;importjava.util.Objects;importjava.util.Set;importjava.util.function.Supplier;importstaticcom.example.demo.config.security.provider.type.AuthorizationGrantTypes.WECHAT_MINIAPP;/**
* 微信小程序
*/publicfinalclassWeChatMiniAppAuthenticationProviderimplementsAuthenticationProvider{publicstaticfinalStringROLE_MOBILE="ROLE_MOBILE";privatefinalLogger logger =LoggerFactory.getLogger(WeChatMiniAppAuthenticationProvider.class);privateAuthorizationServerSettings authorizationServerSettings;@Autowired(required =false)voidsetAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings){this.authorizationServerSettings = authorizationServerSettings;}JWKSource<SecurityContext> jwkSource;privatefinalJwtEncoder jwtEncoder;publicWeChatMiniAppAuthenticationProvider(JWKSource<SecurityContext> jwkSource,OAuth2AuthorizationService authorizationService){this.jwkSource = jwkSource;
jwtEncoder =newNimbusJwtEncoder(jwkSource);this.authorizationService = authorizationService;}privatefinalOAuth2AuthorizationService authorizationService;@OverridepublicAuthenticationauthenticate(Authentication authentication)throwsAuthenticationException{// 转为微信小程序认证tokenWeChatMiniAppAuthenticationToken authenticationToken =(WeChatMiniAppAuthenticationToken) authentication;// 获取经过身份验证的客户端否则抛出无效的客户端OAuth2ClientAuthenticationToken clientPrincipal =getAuthenticatedClientElseThrowInvalidClient(
authenticationToken);RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
logger.info("小程序认证 authenticate -> authenticationToken:{}",authenticationToken);
logger.info("小程序认证 clientPrincipal -> clientPrincipal:{}",clientPrincipal);JwsHeader.Builder headersBuilder =JwtUtils.headers();Set<String> authorizedScopes =null;if(registeredClient !=null){
authorizedScopes = registeredClient.getScopes();}// 发行者 保持与客户端一致String issuer =this.authorizationServerSettings !=null?this.authorizationServerSettings.getIssuer():null;JwtClaimsSet.Builder claimsBuilder =JwtUtils.accessTokenClaims(Objects.requireNonNull(registeredClient), issuer, clientPrincipal.getName(),
authorizedScopes);// @formatter:offJwtEncodingContext context =JwtEncodingContext.with(headersBuilder, claimsBuilder)//注册客户端.registeredClient(registeredClient)//资源所有者.principal(clientPrincipal)// 授权范围.authorizedScopes(authorizedScopes)//token类型.tokenType(OAuth2TokenType.ACCESS_TOKEN)// 微信小程序.authorizationGrantType(WECHAT_MINIAPP)//设置代表授权许可.authorizationGrant(authenticationToken).build();// @formatter:onJwsHeader headers = context.getJwsHeader().build();JwtClaimsSet.Builder claims = context.getClaims();//@formatter:offUserDto data =newUserDto().setId("123456").setHosId("147258");//@formatter:on
claims.claim("data",JSONObject.toJSONString(data));// JWT 定制器// this.jwtCustomizer.customize(context);// JWT编码器Jwt jwtAccessToken =this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claims.build()));// access tokenOAuth2AccessToken accessToken =newOAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwtAccessToken.getTokenValue(),
jwtAccessToken.getIssuedAt(), jwtAccessToken.getExpiresAt(),
authorizedScopes);OAuth2RefreshToken refreshToken =null;if(registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)&&!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)){
refreshToken =generateRefreshToken(
registeredClient.getTokenSettings().getRefreshTokenTimeToLive());}OAuth2Authorization.Builder authorizationBuilder =OAuth2Authorization.withRegisteredClient(registeredClient).principalName(clientPrincipal.getName()).authorizationGrantType(WECHAT_MINIAPP).authorizedScopes(authorizedScopes).token(accessToken,(metadata)-> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
jwtAccessToken.getClaims())).attribute(Principal.class.getName(),newAnonymousAuthenticationToken(clientPrincipal.getName(),
clientPrincipal.getPrincipal(),AuthorityUtils.createAuthorityList(ROLE_MOBILE)));if(refreshToken !=null){
authorizationBuilder.refreshToken(refreshToken);}OAuth2Authorization authorization = authorizationBuilder.build();// @formatter:on// 保存入库this.authorizationService.save(authorization);// 构造返回returnnewOAuth2AccessTokenAuthenticationToken(registeredClient,
authentication, accessToken, refreshToken);// todo// throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR);}@Overridepublicbooleansupports(Class<?> authentication){returnWeChatMiniAppAuthenticationToken.class.isAssignableFrom(authentication);}privatestaticfinalStringKeyGeneratorDEFAULT_REFRESH_TOKEN_GENERATOR=newBase64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(),96);privatefinalSupplier<String> refreshTokenGenerator =DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;privateOAuth2RefreshTokengenerateRefreshToken(Duration tokenTimeToLive){Instant issuedAt =Instant.now();Instant expiresAt = issuedAt.plus(tokenTimeToLive);returnnewOAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);}staticOAuth2ClientAuthenticationTokengetAuthenticatedClientElseThrowInvalidClient(Authentication authentication){OAuth2ClientAuthenticationToken clientPrincipal =null;if(OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())){
clientPrincipal =(OAuth2ClientAuthenticationToken)authentication.getPrincipal();}if(clientPrincipal !=null&& clientPrincipal.isAuthenticated()){return clientPrincipal;}else{thrownewOAuth2AuthenticationException("invalid_client");}}}
预处理(包名: Converter)
WeChatMiniAppAuthenticationConverter
可以说是预处理类转换token信息
importcom.example.demo.config.security.provider.token.WeChatMiniAppAuthenticationToken;importcom.example.demo.config.security.provider.type.AuthorizationGrantTypes;importjakarta.servlet.http.HttpServletRequest;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;importorg.springframework.security.web.authentication.AuthenticationConverter;importorg.springframework.util.LinkedMultiValueMap;importorg.springframework.util.MultiValueMap;importjava.util.LinkedHashMap;importjava.util.Map;publicfinalclassWeChatMiniAppAuthenticationConverterimplementsAuthenticationConverter{privatefinalLogger logger =LoggerFactory.getLogger(WeChatMiniAppAuthenticationConverter.class);/**
* 参数对象
*
* @param request {@link HttpServletRequest}
* @return {@link Authentication}
*/@OverridepublicAuthenticationconvert(HttpServletRequest request){String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);if(!AuthorizationGrantTypes.WECHAT_MINIAPP.getValue().equals(grantType)){returnnull;}// 获取参数MultiValueMap<String,String> parameters =getParameters(request);
logger.info("微信小程序授权入参:{}", parameters);// 微信CODE// 其他参数Map<String,Object> additionalParameters =getOtherParameters(parameters);// clientPrincipalAuthentication clientPrincipal =SecurityContextHolder.getContext().getAuthentication();
logger.info("小程序授权全部参数:{}", additionalParameters);returnnewWeChatMiniAppAuthenticationToken(clientPrincipal, additionalParameters,"code","appid","encryptedData","ivStr");}/**
* 获取其他参数
*
* @param parameters {@link MultiValueMap}
* @return {@link Map}
*/privateMap<String,Object>getOtherParameters(MultiValueMap<String,String> parameters){Map<String,Object> additionalParameters =newLinkedHashMap<>(16);
parameters.forEach((key, value)->{if(!key.equals(OAuth2ParameterNames.GRANT_TYPE)&&!key.equals(OAuth2ParameterNames.SCOPE)){
additionalParameters.put(key, value.get(0));}});return additionalParameters;}staticMultiValueMap<String,String>getParameters(HttpServletRequest request){Map<String,String[]> parameterMap = request.getParameterMap();MultiValueMap<String,String> parameters =newLinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values)->{for(String value : values){
parameters.add(key, value);}});return parameters;}}
自定义token (包名: token)
WeChatMiniAppAuthenticationToken
@Setter@GetterpublicclassWeChatMiniAppAuthenticationTokenextendsOAuth2AuthorizationGrantAuthenticationToken{/**
* appId 应用ID
*/privatefinalString appId;/**
* code 小程序CODE
*/privatefinalString code;privatefinalString encryptedData;privatefinalString ivStr;/**
* Sub-class constructor.
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
* @param code {@link String } 小程序code
* @param appId {@link String } 平台appid
* @param encryptedData {@link String } encryptedData
* @param ivStr {@link String } ivStr
*/publicWeChatMiniAppAuthenticationToken(Authentication clientPrincipal,Map<String,Object> additionalParameters,String code,String appId,String encryptedData,String ivStr){super(AuthorizationGrantTypes.WECHAT_MINIAPP, clientPrincipal, additionalParameters);this.code = code;this.appId = appId;this.encryptedData = encryptedData;this.ivStr = ivStr;}}
定义认证type (包名:type)
AuthorizationGrantTypes
人获取access_token时,会用到grantType
publicclassAuthorizationGrantTypes{publicstaticfinalAuthorizationGrantTypeWECHAT_MINIAPP=newAuthorizationGrantType("wechat_miniapp");}
资源服务器 (包名:resourceServer)
CustomAuthenticationTokenConverter
自定义jwt 预处理器
publicclassCustomAuthenticationTokenimplementsConverter<Jwt,AbstractAuthenticationToken>{privatefinalConverter<Jwt,Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter =newJwtGrantedAuthoritiesConverter();privatestaticfinalStringPRINCIPAL_CLAIM_NAME="data";privatefinalLogger logger =LoggerFactory.getLogger(CustomAuthenticationToken.class);publicCustomAuthenticationToken(){}publicAbstractAuthenticationTokenconvert(@NonNullJwt jwt){this.logger.info("convert ->jwt:{}",JSONObject.toJSONString(jwt));Collection<GrantedAuthority> authorities =this.extractAuthorities(jwt);// 获取个性化的token信息String principalClaimValue = jwt.getClaimAsString(PRINCIPAL_CLAIM_NAME);if(principalClaimValue ==null){returnnewJwtAuthenticationToken(jwt, authorities);}else{UserDto user =this.extractUserInfo(jwt);
user.setToken(jwt.getTokenValue());CustomJwtAuthenticationToken jwtAuthenticationToken =newCustomJwtAuthenticationToken(jwt, user, authorities);SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);return jwtAuthenticationToken;}}protectedCollection<GrantedAuthority>extractAuthorities(Jwt jwt){returnthis.jwtGrantedAuthoritiesConverter.convert(jwt);}protectedUserDtoextractUserInfo(Jwt jwt){String principalClaimValue = jwt.getClaimAsString("data");returnJSONObject.parseObject(principalClaimValue,UserDto.class);}}
CustomJwtAuthenticationToken
自定义的jwt
@TransientpublicclassCustomJwtAuthenticationTokenextendsAbstractOAuth2TokenAuthenticationToken<Jwt>{privatestaticfinallong serialVersionUID =560L;privatefinalString name;publicCustomJwtAuthenticationToken(Jwt jwt,Object principal,Collection<?extendsGrantedAuthority> authorities){super(jwt, principal,"", authorities);this.setAuthenticated(true);this.name = jwt.getSubject();}publicMap<String,Object>getTokenAttributes(){return((Jwt)this.getToken()).getClaims();}publicStringgetName(){returnthis.name;}}
CustomOauth2AuthenticationEntryPoint
自定义协议端点
publicclassCustomOauth2AuthenticationEntryPointimplementsAuthenticationEntryPoint{privatestaticfinalLogger logger =LoggerFactory.getLogger(CustomOauth2AuthenticationEntryPoint.class);privateString realmName;publicCustomOauth2AuthenticationEntryPoint(){}publicvoidcommence(HttpServletRequest request,HttpServletResponse response,AuthenticationException e)throwsIOException{
logger.error(e.getLocalizedMessage(), e);HttpStatus status =HttpStatus.UNAUTHORIZED;Map<String,String> parameters =newLinkedHashMap<>();if(Objects.nonNull(this.realmName)){
parameters.put("realm",this.realmName);}if(e instanceofOAuth2AuthenticationException oAuth2AuthenticationException){OAuth2Error error = oAuth2AuthenticationException.getError();
parameters.put("error", error.getErrorCode());if(StringUtils.hasText(error.getDescription())){String errorMessage = error.getDescription();
parameters.put("error_description", errorMessage);}if(StringUtils.hasText(error.getUri())){
parameters.put("error_uri", error.getUri());}if(error instanceofBearerTokenError bearerTokenError){if(StringUtils.hasText(bearerTokenError.getScope())){
parameters.put("scope", bearerTokenError.getScope());}
status =((BearerTokenError)error).getHttpStatus();}}ResponseEntity<String> unauthenticated =newResponseEntity<String>("Unauthenticated",HttpStatusCode.valueOf(status.value()));String message =JSON.toJSONString(unauthenticated);String wwwAuthenticate =WwwAuthenticateHeaderBuilder.computeWwwAuthenticateHeaderValue(parameters);
response.addHeader("WWW-Authenticate", wwwAuthenticate);
response.setStatus(status.value());
response.setContentType("application/json");
response.getWriter().write(message);}}
WwwAuthenticateHeaderBuilder
publicfinalclassWwwAuthenticateHeaderBuilder{publicWwwAuthenticateHeaderBuilder(){}publicstaticStringcomputeWwwAuthenticateHeaderValue(Map<String,String> parameters){StringJoiner wwwAuthenticate =newStringJoiner(", ","Bearer ","");if(!parameters.isEmpty()){
parameters.forEach((k, v)->{
wwwAuthenticate.add(k +"=\""+ v +"\"");});}return wwwAuthenticate.toString();}}
Oauth2ResourceServerConfigurer
资源服务器配置
publicfinalclassOauth2ResourceServerConfigurer{publicOauth2ResourceServerConfigurer(){}publicstaticvoidapplyDefaultSecurity(HttpSecurity http)throwsException{
http.oauth2ResourceServer((oauth2ResourceServerConfigurer)->
oauth2ResourceServerConfigurer
// 无权限处理器// .accessDeniedHandler()// 自定义协议端点.authenticationEntryPoint(newCustomOauth2AuthenticationEntryPoint()).jwt((jwtConfigurer)-> jwtConfigurer.jwtAuthenticationConverter(newCustomAuthenticationToken())));}}
SecurityConfig
实现授权及资源服务器
packagecom.example.demo.config.security;importcom.example.demo.config.security.authenticationServer.CustomAuthorizationServerConfiguration;importcom.example.demo.config.security.resourceServer.Oauth2ResourceServerConfigurer;importcom.example.demo.service.CustomUserDetailsService;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.nimbusds.jose.jwk.JWKSet;importcom.nimbusds.jose.jwk.RSAKey;importcom.nimbusds.jose.jwk.source.ImmutableJWKSet;importcom.nimbusds.jose.jwk.source.JWKSource;importcom.nimbusds.jose.proc.SecurityContext;importlombok.extern.slf4j.Slf4j;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.annotation.Order;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.jackson2.CoreJackson2Module;importorg.springframework.security.jackson2.SecurityJackson2Modules;importorg.springframework.security.oauth2.core.*;importorg.springframework.security.oauth2.core.oidc.OidcScopes;importorg.springframework.security.oauth2.jwt.JwtDecoder;importorg.springframework.security.oauth2.server.authorization.*;importorg.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;importorg.springframework.security.oauth2.server.authorization.client.RegisteredClient;importorg.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;importorg.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;importorg.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;importorg.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;importorg.springframework.security.oauth2.server.authorization.settings.ClientSettings;importorg.springframework.security.oauth2.server.authorization.settings.TokenSettings;importorg.springframework.security.oauth2.server.authorization.token.*;importorg.springframework.security.web.SecurityFilterChain;importjava.security.KeyPair;importjava.security.KeyPairGenerator;importjava.security.interfaces.RSAPrivateKey;importjava.security.interfaces.RSAPublicKey;importjava.time.Duration;importjava.util.UUID;/***
*
* @author qb
* @since 2023/7/21 15:14
* @version 1.0
*/@Slf4j@Configuration@EnableWebSecuritypublicclassSecurityConfig{// 协议端点过滤器链@Bean@Order(1)publicSecurityFilterChainauthorizationServerSecurityFilterChain(HttpSecurity http)throwsException{CustomAuthorizationServerConfiguration.applyDefaultSecurity(http,jwkSource());// // 开启oidc connect 1.0// http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());// // 重定向到未通过身份验证的登录页面 授权终结点// http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(// new LoginUrlAuthenticationEntryPoint("/login"),// new MediaTypeRequestMatcher(MediaType.TEXT_HTML)// ))// // 授权服务 接受的用户信息和/或客户端注册的访问令牌// .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}// 协议认证筛选器@Bean@Order(2)publicSecurityFilterChaindefaultSecurityFilterChain(HttpSecurity http)throwsException{Oauth2ResourceServerConfigurer.applyDefaultSecurity(http);
http
.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth ->
auth.requestMatchers("/login","/callback","/oauth2/client/**").permitAll().anyRequest().authenticated());return http.build();}// 自定义认证service@BeanpublicUserDetailsServiceuserDetailsService(){returnnewCustomUserDetailsService();}// 密码加密@BeanpublicPasswordEncoderpasswordEncoder(){// return PasswordEncoderFactories.createDelegatingPasswordEncoder();returnnewBCryptPasswordEncoder();}/**
* 管理客户端
* @return /
*/@BeanpublicRegisteredClientRepositoryregisteredClientRepository(JdbcTemplate jdbcTemplate){// RegisteredClient oidcClient = defaultClient();// 配置模式// JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);// if (null == jdbcRegisteredClientRepository.findByClientId("client")) {// jdbcRegisteredClientRepository.save(oidcClient);// }returnnewJdbcRegisteredClientRepository(jdbcTemplate);}privatestaticRegisteredClientdefaultClient(){// 方便测试,先注册一个测试客户端RegisteredClient oidcClient =RegisteredClient.withId(UUID.randomUUID().toString()).clientId("client")// 123456.clientSecret("$2a$10$gJUJo9Ad3wDIhBGVH.8/i.Ox82tSCR4.UkbiDWEDUVQnIzcTMPjKK")// 可以基于 basic 的方式和授权服务器进行认证.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 授权码.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)// 刷新token.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)// 客户端模式.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)// 密码模式.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)// 重定向url.redirectUri("http://127.0.0.1:9000/callback").postLogoutRedirectUri("http://127.0.0.1:9000/")// 客户端申请的作用域,也可以理解这个客户端申请访问用户的哪些信息,比如:获取用户信息,获取用户照片等.scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).clientSettings(ClientSettings.builder()// 是否需要用户确认一下客户端需要获取用户的哪些权限// 比如:客户端需要获取用户的 用户信息、用户照片 但是此处用户可以控制只给客户端授权获取 用户信息。.requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder()// accessToken 的有效期.accessTokenTimeToLive(Duration.ofHours(1))// refreshToken 的有效期.refreshTokenTimeToLive(Duration.ofDays(3))// 是否可重用刷新令牌.reuseRefreshTokens(true).build()).build();return oidcClient;}/**
* 自定义授权service
* @return /
*/@BeanpublicOAuth2AuthorizationServiceauthorizationService(JdbcTemplate jdbcTemplate,RegisteredClientRepository registeredClientRepository){// JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);// JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);// ClassLoader classLoader = JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper.class.getClassLoader();// ObjectMapper objectMapper = new ObjectMapper();// objectMapper.registerModules(new CoreJackson2Module());// objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));// objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());// rowMapper.setObjectMapper(objectMapper);// authorizationService.setAuthorizationRowMapper(rowMapper);// return authorizationService;returnnewJdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);}/**
* 自定义确认授权 service 配置
*
*/@BeanpublicOAuth2AuthorizationConsentServiceauthorizationConsentService(JdbcTemplate jdbcTemplate,RegisteredClientRepository registeredClientRepository){returnnewJdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}/**
* 签名实例
* 此处注意,目前重启项目就会导致之前已存在的token失效,需要改为固定私钥和公钥,生成到项目目录下
* @return /
*/@BeanpublicJWKSource<SecurityContext>jwkSource(){var keyPair =generateRsaKeyPair();// 公钥RSAPublicKey publicKey =(RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey =(RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey =newRSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();var jwkSet =newJWKSet(rsaKey);returnnewImmutableJWKSet<>(jwkSet);}// 解析JWKSource访问令牌,构建 JwtDecoder 实例@BeanpublicJwtDecoderjwtDecoder(JWKSource<SecurityContext> jwkSource){returnOAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}// 启动时生成的 with 密钥的实例,用于创建上述内容。java.security.KeyPairJWKSourceprivatestaticKeyPairgenerateRsaKeyPair(){KeyPair keyPair ;try{KeyPairGenerator keyPairGenerator =KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();}catch(Exception e){thrownewIllegalStateException(e);}return keyPair;}/**
* 自定义jwt信息,全局(自定义jwt实现例外)
* @return /
*/@BeanpublicOAuth2TokenCustomizer<JwtEncodingContext>jwtCustomizer(){return context ->{
context.getJwsHeader().header("client-id", context.getRegisteredClient().getClientId());
context.getClaims().claim("test","哈哈哈").build();
log.info("jwtCustomizer -> getJwsHeader:{}",context.getJwsHeader());
log.info("jwtCustomizer -> claim:{}", context.getClaims());// Customize claims};}/**
* 自定义token属性 预留
* @return /
*/@BeanpublicOAuth2TokenCustomizer<OAuth2TokenClaimsContext>accessTokenCustomizer(){return context ->{OAuth2TokenClaimsSet.Builder claims = context.getClaims();
claims.claim("test200","哈哈哈哈哈").build();
log.info("accessTokenCustomizer -> claims:{}",claims);// Customize claims};}// Jwt编码上下文 拓展token// @Bean// public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {// return context -> {// JwsHeader.Builder headers = context.getJwsHeader();// JwtClaimsSet.Builder claims = context.getClaims();// headers.header("client-id", context.getRegisteredClient().getClientId());// log.info("jwtCustomizer headers:{}",headers);// if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {// // Customize headers/claims for access_token//// } else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {// // Customize headers/claims for id_token// }// };// }// 自定义 授权服务器的实例,设置用于配置spring授权服务器@BeanpublicAuthorizationServerSettingsauthorizationServerSettings(){// 总之server服务,例如:个性化认证及授权相关的路径returnAuthorizationServerSettings.builder().build();}// 会话管理// @Bean// public SessionRegistry sessionRegistry() {// return new SessionRegistryImpl();// }// @Bean// public HttpSessionEventPublisher httpSessionEventPublisher() {// return new HttpSessionEventPublisher();// }}
MybatisPlusConfig
@ConfigurationpublicclassMybatisPlusConfig{/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptor interceptor =newMybatisPlusInterceptor();
interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));return interceptor;}}
controller包
ClientController
注册客户端
@RequestMapping("/oauth2/client")@RestController@RequiredArgsConstructorpublicclassClientController{privatefinalRegisteredClientRepository registeredClientRepository;privatefinalPasswordEncoder passwordEncoder;/**
* 注册客户端
*/@PostMappingpublicResponseEntity<Boolean>registeredClientRepository(@RequestBodyRegisteredOauth2Client registeredClient){// @formatter:offRegisteredClient entity =RegisteredClient.withId(UUID.randomUUID().toString())// ID.clientId(registeredClient.getClientId())// 秘钥.clientSecret(passwordEncoder.encode(registeredClient.getClientSecret()))// POST 请求方法.clientAuthenticationMethods(clientAuthenticationMethods ->
clientAuthenticationMethods.addAll(registeredClient.getClientAuthenticationMethods()))// CLIENT_CREDENTIALS.authorizationGrantTypes(authorizationGrantTypes ->
authorizationGrantTypes.addAll(registeredClient.getAuthorizationGrantTypes()))// 范围.scopes(strings ->{// 超级
strings.addAll(registeredClient.getScopes());}).tokenSettings(registeredClient.getTokenSettings())// 客户端配置.clientSettings(registeredClient.getClientSettings()).build();// Save registered client in db
registeredClientRepository.save(entity);returnResponseEntity.ok(true);}}
InfoController
测试 当前认证上下文信息
@RestController@RequestMapping("/info")publicclassInfoController{@GetMapping("/token")publicObjecttoken(){returnSecurityContextHolder.getContext().getAuthentication().getPrincipal();}}
domain
BaseResponseDto
@Data@AllArgsConstructor@NoArgsConstructor@BuilderpublicclassBaseResponseDto{privateString code;privateString message;}
LoginRequest
@DatapublicclassLoginRequest{privateString username;privateString password;}
UserDto
@Data@Accessors(chain =true)publicclassUserDto{privateString id;privateString hosId;privateString token;}
RegisteredOauth2Client 重要
这个类可以着重看一下
@Data@NoArgsConstructorpublicclassRegisteredOauth2ClientimplementsSerializable{/**
* 客户端ID
*/privateString clientId;/**
* 客户端秘钥
*/privateString clientSecret;/**
* 客户端名称
*/privateString clientName;/**
* 权限范围
*/privateSet<String> scopes;privateInstant clientIdIssuedAt;privateInstant clientSecretExpiresAt;privateSet<ClientAuthenticationMethod> clientAuthenticationMethods;privateSet<AuthorizationGrantType> authorizationGrantTypes;privateSet<String> redirectUris;privateClientSettings clientSettings =ClientSettings.builder()// 是否需要用户确认一下客户端需要获取用户的哪些权限// 比如:客户端需要获取用户的 用户信息、用户照片 但是此处用户可以控制只给客户端授权获取 用户信息。.requireAuthorizationConsent(true).build();privateTokenSettings tokenSettings =TokenSettings.builder()// accessToken 的有效期.accessTokenTimeToLive(Duration.ofHours(1))// refreshToken 的有效期.refreshTokenTimeToLive(Duration.ofDays(3))// 是否可重用刷新令牌.reuseRefreshTokens(true).build();}
UserEntity
@Data@TableName("user")publicclassUserEntity{@TableId("id_")privateLong id;@TableField("username")privateString username;@TableField("password")privateString password;}
mapper
UserMapper
publicinterfaceUserMapperextendsBaseMapper<UserEntity>{}
service
IUserService
publicinterfaceIUserServiceextendsIService<UserEntity>{}
UserServiceImpl
@Service@RequiredArgsConstructorpublicclassUserServiceImplextendsServiceImpl<UserMapper,UserEntity>implementsIUserService{}
utils
HelperUtils
publicclassHelperUtils{publicstaticfinalObjectWriterJSON_WRITER=newObjectMapper().writer().withDefaultPrettyPrinter();}
JwtUtils
publicfinalclassJwtUtils{privateJwtUtils(){}publicstaticJwsHeader.Builderheaders(){returnJwsHeader.with(SignatureAlgorithm.RS256);}publicstaticJwtClaimsSet.BuilderaccessTokenClaims(RegisteredClient registeredClient,String issuer,String subject,Set<String> authorizedScopes){Instant issuedAt =Instant.now();Instant expiresAt = issuedAt
.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());/**
* iss (issuer):签发人/发行人
* sub (subject):主题
* aud (audience):用户
* exp (expiration time):过期时间
* nbf (Not Before):生效时间,在此之前是无效的
* iat (Issued At):签发时间
* jti (JWT ID):用于标识该 JWT
*/// @formatter:offJwtClaimsSet.Builder claimsBuilder =JwtClaimsSet.builder();if(StringUtils.hasText(issuer)){
claimsBuilder.issuer(issuer);}
claimsBuilder
.subject(subject).audience(Collections.singletonList(registeredClient.getClientId())).issuedAt(issuedAt).expiresAt(expiresAt).notBefore(issuedAt);if(!CollectionUtils.isEmpty(authorizedScopes)){
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);}// @formatter:onreturn claimsBuilder;}publicstaticJwtClaimsSet.BuilderidTokenClaims(RegisteredClient registeredClient,String issuer,String subject,String nonce){Instant issuedAt =Instant.now();// TODO Allow configuration for ID Token time-to-liveInstant expiresAt = issuedAt.plus(30,ChronoUnit.MINUTES);// @formatter:offJwtClaimsSet.Builder claimsBuilder =JwtClaimsSet.builder();if(StringUtils.hasText(issuer)){
claimsBuilder.issuer(issuer);}
claimsBuilder
.subject(subject).audience(Collections.singletonList(registeredClient.getClientId())).issuedAt(issuedAt).expiresAt(expiresAt).claim(IdTokenClaimNames.AZP, registeredClient.getClientId());if(StringUtils.hasText(nonce)){
claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce);}// TODO Add 'auth_time' claim// @formatter:onreturn claimsBuilder;}}
OAuth2ConfigurerUtils
/**
* 复制security底层的工具类
*/publicclassOAuth2ConfigurerUtils{privateOAuth2ConfigurerUtils(){}publicstaticRegisteredClientRepositorygetRegisteredClientRepository(HttpSecurity httpSecurity){RegisteredClientRepository registeredClientRepository =(RegisteredClientRepository)httpSecurity.getSharedObject(RegisteredClientRepository.class);if(registeredClientRepository ==null){
registeredClientRepository =(RegisteredClientRepository)getBean(httpSecurity,RegisteredClientRepository.class);
httpSecurity.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);}return registeredClientRepository;}publicstaticOAuth2AuthorizationServicegetAuthorizationService(HttpSecurity httpSecurity){OAuth2AuthorizationService authorizationService =(OAuth2AuthorizationService)httpSecurity.getSharedObject(OAuth2AuthorizationService.class);if(authorizationService ==null){
authorizationService =(OAuth2AuthorizationService)getOptionalBean(httpSecurity,OAuth2AuthorizationService.class);if(authorizationService ==null){
authorizationService =newInMemoryOAuth2AuthorizationService();}
httpSecurity.setSharedObject(OAuth2AuthorizationService.class, authorizationService);}return(OAuth2AuthorizationService)authorizationService;}publicstaticOAuth2AuthorizationConsentServicegetAuthorizationConsentService(HttpSecurity httpSecurity){OAuth2AuthorizationConsentService authorizationConsentService =(OAuth2AuthorizationConsentService)httpSecurity.getSharedObject(OAuth2AuthorizationConsentService.class);if(authorizationConsentService ==null){
authorizationConsentService =(OAuth2AuthorizationConsentService)getOptionalBean(httpSecurity,OAuth2AuthorizationConsentService.class);if(authorizationConsentService ==null){
authorizationConsentService =newInMemoryOAuth2AuthorizationConsentService();}
httpSecurity.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);}return(OAuth2AuthorizationConsentService)authorizationConsentService;}publicstaticOAuth2TokenGenerator<?extendsOAuth2Token>getTokenGenerator(HttpSecurity httpSecurity){OAuth2TokenGenerator<?extendsOAuth2Token> tokenGenerator =(OAuth2TokenGenerator)httpSecurity.getSharedObject(OAuth2TokenGenerator.class);if(tokenGenerator ==null){
tokenGenerator =(OAuth2TokenGenerator)getOptionalBean(httpSecurity,OAuth2TokenGenerator.class);if(tokenGenerator ==null){JwtGenerator jwtGenerator =getJwtGenerator(httpSecurity);OAuth2AccessTokenGenerator accessTokenGenerator =newOAuth2AccessTokenGenerator();OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer =getAccessTokenCustomizer(httpSecurity);if(accessTokenCustomizer !=null){
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);}OAuth2RefreshTokenGenerator refreshTokenGenerator =newOAuth2RefreshTokenGenerator();if(jwtGenerator !=null){
tokenGenerator =newDelegatingOAuth2TokenGenerator(newOAuth2TokenGenerator[]{jwtGenerator, accessTokenGenerator, refreshTokenGenerator});}else{
tokenGenerator =newDelegatingOAuth2TokenGenerator(newOAuth2TokenGenerator[]{accessTokenGenerator, refreshTokenGenerator});}}
httpSecurity.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);}return(OAuth2TokenGenerator)tokenGenerator;}privatestaticJwtGeneratorgetJwtGenerator(HttpSecurity httpSecurity){JwtGenerator jwtGenerator =(JwtGenerator)httpSecurity.getSharedObject(JwtGenerator.class);if(jwtGenerator ==null){JwtEncoder jwtEncoder =getJwtEncoder(httpSecurity);if(jwtEncoder !=null){
jwtGenerator =newJwtGenerator(jwtEncoder);OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer =getJwtCustomizer(httpSecurity);if(jwtCustomizer !=null){
jwtGenerator.setJwtCustomizer(jwtCustomizer);}
httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator);}}return jwtGenerator;}privatestaticJwtEncodergetJwtEncoder(HttpSecurity httpSecurity){JwtEncoder jwtEncoder =(JwtEncoder)httpSecurity.getSharedObject(JwtEncoder.class);if(jwtEncoder ==null){
jwtEncoder =(JwtEncoder)getOptionalBean(httpSecurity,JwtEncoder.class);if(jwtEncoder ==null){JWKSource<SecurityContext> jwkSource =getJwkSource(httpSecurity);if(jwkSource !=null){
jwtEncoder =newNimbusJwtEncoder(jwkSource);}}if(jwtEncoder !=null){
httpSecurity.setSharedObject(JwtEncoder.class, jwtEncoder);}}return(JwtEncoder)jwtEncoder;}publicstaticJWKSource<SecurityContext>getJwkSource(HttpSecurity httpSecurity){JWKSource<SecurityContext> jwkSource =(JWKSource)httpSecurity.getSharedObject(JWKSource.class);if(jwkSource ==null){ResolvableType type =ResolvableType.forClassWithGenerics(JWKSource.class,newClass[]{SecurityContext.class});
jwkSource =(JWKSource)getOptionalBean(httpSecurity, type);if(jwkSource !=null){
httpSecurity.setSharedObject(JWKSource.class, jwkSource);}}return jwkSource;}privatestaticOAuth2TokenCustomizer<JwtEncodingContext>getJwtCustomizer(HttpSecurity httpSecurity){ResolvableType type =ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class,newClass[]{JwtEncodingContext.class});return(OAuth2TokenCustomizer)getOptionalBean(httpSecurity, type);}privatestaticOAuth2TokenCustomizer<OAuth2TokenClaimsContext>getAccessTokenCustomizer(HttpSecurity httpSecurity){ResolvableType type =ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class,newClass[]{OAuth2TokenClaimsContext.class});return(OAuth2TokenCustomizer)getOptionalBean(httpSecurity, type);}publicstaticAuthorizationServerSettingsgetAuthorizationServerSettings(HttpSecurity httpSecurity){AuthorizationServerSettings authorizationServerSettings =(AuthorizationServerSettings)httpSecurity.getSharedObject(AuthorizationServerSettings.class);if(authorizationServerSettings ==null){
authorizationServerSettings =(AuthorizationServerSettings)getBean(httpSecurity,AuthorizationServerSettings.class);
httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);}return authorizationServerSettings;}publicstatic<T>TgetBean(HttpSecurity httpSecurity,Class<T> type){return((ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class)).getBean(type);}publicstatic<T>TgetBean(HttpSecurity httpSecurity,ResolvableType type){ApplicationContext context =(ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class);String[] names = context.getBeanNamesForType(type);if(names.length ==1){return(T) context.getBean(names[0]);}elseif(names.length >1){thrownewNoUniqueBeanDefinitionException(type, names);}else{thrownewNoSuchBeanDefinitionException(type);}}publicstatic<T>TgetOptionalBean(HttpSecurity httpSecurity,Class<T> type){Map<String,T> beansMap =BeanFactoryUtils.beansOfTypeIncludingAncestors((ListableBeanFactory)httpSecurity.getSharedObject(ApplicationContext.class), type);if(beansMap.size()>1){int var10003 = beansMap.size();String var10004 = type.getName();thrownewNoUniqueBeanDefinitionException(type, var10003,"Expected single matching bean of type '"+ var10004 +"' but found "+ beansMap.size()+": "+StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));}else{return!beansMap.isEmpty()? beansMap.values().iterator().next():null;}}publicstatic<T>TgetOptionalBean(HttpSecurity httpSecurity,ResolvableType type){ApplicationContext context =(ApplicationContext)httpSecurity.getSharedObject(ApplicationContext.class);String[] names = context.getBeanNamesForType(type);if(names.length >1){thrownewNoUniqueBeanDefinitionException(type, names);}else{return names.length ==1?(T) context.getBean(names[0]):null;}}}
效果截图
注册客户端
{"clientId":"123456","clientSecret":"8b30c1482ff973cfc92e51e1ec636966","clientName":"test","scopes":["super"],"clientAuthenticationMethods":["client_secret_post"],"authorizationGrantTypes":["refresh_token","client_credentials","sms_app,wechat_miniapp"]}
客户端授权
grant_type:wechat_miniapp
scope:super
client_id:123456
client_secret:8b30c1482ff973cfc92e51e1ec636966
appId:123456789
不带access_token访问资源
带access_token访问资源
数据库截图
客户端
授权
版权归原作者 离别又见离别 所有, 如有侵权,请联系我们删除。