当前位置: 首页 > news >正文

山东港基建设集团网站中企动力全球邮箱

山东港基建设集团网站,中企动力全球邮箱,免费网站建设解决方案,免费软件网站有哪些目录 前言无图无真相创建数据库授权服务器maven 依赖application.yml授权服务器配置AuthorizationServierConfigDefaultSecutiryConfig 密码模式扩展PasswordAuthenticationTokenPasswordAuthenticationConverterPasswordAuthenticationProvider JWT 自定义字段自定义认证响应认… 目录 前言无图无真相创建数据库授权服务器maven 依赖application.yml授权服务器配置AuthorizationServierConfigDefaultSecutiryConfig 密码模式扩展PasswordAuthenticationTokenPasswordAuthenticationConverterPasswordAuthenticationProvider JWT 自定义字段自定义认证响应认证成功响应认证失败响应配置自定义处理器 密码模式测试单元测试Postman 测试 资源服务器maven 依赖application.yml资源服务器配置 认证流程测试登录认证授权获取用户信息 结语源码参考文档 前言 Spring Security OAuth2 的最终版本是2.5.2并于2022年6月5日正式宣布停止维护。Spring 官方为此推出了新的替代产品即 Spring Authorization Server。然而出于安全考虑Spring Authorization Server 不再支持密码模式因为密码模式要求客户端直接处理用户的密码。但对于受信任的第一方系统(自有APP和管理系统等)许多情况下需要使用密码模式。在这种情况下需要在 Spring Authorization Server 的基础上扩展密码模式的支持。本文基于开源微服务商城项目 youlai-mall、Spring Boot 3 和 Spring Authorization Server 1.1 版本演示了如何扩展密码模式以及如何将其应用于 Spring Cloud 微服务实战。 无图无真相 通过 Spring Cloud Gateway 访问认证中心认证成功获取到访问令牌。完整源码youlai-mall 创建数据库 Spring Authorization Server 官方提供的授权服务器示例 demo-authorizationserver 初始化数据库所使用的3个SQL脚本路径如下 根据路径找到3张表的SQL脚本 令牌发放记录表: oauth2-authorization-schema.sql授权记录表: oauth2-authorization-consent-schema.sql客户端信息表: oauth2-registered-client-schema.sql 整合后的完整数据库 SQL 脚本如下 -- ---------------------------- -- 1. 创建数据库 -- ---------------------------- CREATE DATABASE IF NOT EXISTS oauth2_server DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;-- ---------------------------- -- 2. 创建表 -- ---------------------------- use oauth2_server;SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; -- ---------------------------- -- 2.1 oauth2_authorization 令牌发放记录表 -- ---------------------------- 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) );-- ---------------------------- -- 2.2 oauth2_authorization_consent 授权记录表 -- ---------------------------- 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) );-- ---------------------------- -- 2.3 oauth2-registered-client OAuth2 客户端信息表 -- ---------------------------- 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) ); 授权服务器 youlai-auth 模块作为认证授权服务器 maven 依赖 在 youlai-auth 模块的 pom.xml 添加授权服务器依赖 !-- Spring Authorization Server 授权服务器依赖 -- dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-oauth2-authorization-server/artifactIdversion1.1.1/version /dependencyapplication.yml 认证中心配置 oauth2_server 数据库连接信息 spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/oauth2_server?zeroDateTimeBehaviorconvertToNulluseUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiautoReconnecttrueusername: rootpassword: 123456授权服务器配置 参考 Spring Authorization Server 官方示例 demo-authorizationserver AuthorizationServierConfig 参考: Spring Authorization Server 官方示例 demo-authorizationserver 下的 AuthorizationServerConfig.java 进行授权服务器配置 ​ package com.youlai.auth.config;/*** 授权服务器配置** author haoxr* since 3.0.0*/ Configuration RequiredArgsConstructor Slf4j public class AuthorizationServerConfig {private final OAuth2TokenCustomizerJwtEncodingContext jwtCustomizer;/*** 授权服务器端点配置*/BeanOrder(Ordered.HIGHEST_PRECEDENCE)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,AuthenticationManager authenticationManager,OAuth2AuthorizationService authorizationService,OAuth2TokenGenerator? tokenGenerator) throws Exception {OAuth2AuthorizationServerConfigurer authorizationServerConfigurer new OAuth2AuthorizationServerConfigurer();authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -tokenEndpoint.accessTokenRequestConverters(authenticationConverters -// 1authenticationConverters.addAll(// 自定义授权模式转换器(Converter)List.of(new PasswordAuthenticationConverter()))).authenticationProviders(authenticationProviders -// 2authenticationProviders.addAll(// 自定义授权模式提供者(Provider)List.of(new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator)))).accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定义成功响应.errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定义失败响应);RequestMatcher endpointsMatcher authorizationServerConfigurer.getEndpointsMatcher();http.securityMatcher(endpointsMatcher).authorizeHttpRequests(authorizeRequests - authorizeRequests.anyRequest().authenticated()).csrf(csrf - csrf.ignoringRequestMatchers(endpointsMatcher)).apply(authorizationServerConfigurer);return http.build();}Bean // 5public JWKSourceSecurityContext jwkSource() {KeyPair keyPair generateRsaKey();RSAPublicKey publicKey (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey (RSAPrivateKey) keyPair.getPrivate();// formatter:offRSAKey rsaKey new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();// formatter:onJWKSet jwkSet new JWKSet(rsaKey);return new ImmutableJWKSet(jwkSet);}private static KeyPair generateRsaKey() { // 6KeyPair keyPair;try {KeyPairGenerator keyPairGenerator KeyPairGenerator.getInstance(RSA);keyPairGenerator.initialize(2048);keyPair keyPairGenerator.generateKeyPair();} catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}Beanpublic JwtDecoder jwtDecoder(JWKSourceSecurityContext jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {JdbcRegisteredClientRepository registeredClientRepository new JdbcRegisteredClientRepository(jdbcTemplate);// 初始化 OAuth2 客户端initMallAppClient(registeredClientRepository);initMallAdminClient(registeredClientRepository);return registeredClientRepository;}Beanpublic OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,RegisteredClientRepository registeredClientRepository) {JdbcOAuth2AuthorizationService service new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);rowMapper.setLobHandler(new DefaultLobHandler());ObjectMapper objectMapper new ObjectMapper();ClassLoader classLoader JdbcOAuth2AuthorizationService.class.getClassLoader();ListModule securityModules SecurityJackson2Modules.getModules(classLoader);objectMapper.registerModules(securityModules);objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());// 使用刷新模式需要从 oauth2_authorization 表反序列化attributes字段得到用户信息(SysUserDetails)objectMapper.addMixIn(SysUserDetails.class, SysUserMixin.class);objectMapper.addMixIn(Long.class, Object.class);rowMapper.setObjectMapper(objectMapper);service.setAuthorizationRowMapper(rowMapper);return service;}Beanpublic OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,RegisteredClientRepository registeredClientRepository) {// Will be used by the ConsentControllerreturn new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}BeanOAuth2TokenGenerator? tokenGenerator(JWKSourceSecurityContext jwkSource) {JwtGenerator jwtGenerator new JwtGenerator(new NimbusJwtEncoder(jwkSource));jwtGenerator.setJwtCustomizer(jwtCustomizer);OAuth2AccessTokenGenerator accessTokenGenerator new OAuth2AccessTokenGenerator();OAuth2RefreshTokenGenerator refreshTokenGenerator new OAuth2RefreshTokenGenerator();return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);}Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}/*** 初始化创建商城管理客户端** param registeredClientRepository*/private void initMallAdminClient(JdbcRegisteredClientRepository registeredClientRepository) {String clientId mall-admin;String clientSecret 123456;String clientName 商城管理客户端;/*如果使用明文客户端认证时会自动升级加密方式换句话说直接修改客户端密码所以直接使用 bcrypt 加密避免不必要的麻烦官方ISSUE https://github.com/spring-projects/spring-authorization-server/issues/1099*/String encodeSecret passwordEncoder().encode(clientSecret);RegisteredClient registeredMallAdminClient registeredClientRepository.findByClientId(clientId);String id registeredMallAdminClient ! null ? registeredMallAdminClient.getId() : UUID.randomUUID().toString();RegisteredClient mallAppClient RegisteredClient.withId(id).clientId(clientId).clientSecret(encodeSecret).clientName(clientName).clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 验证码模式.redirectUri(http://127.0.0.1:8080/authorized).postLogoutRedirectUri(http://127.0.0.1:8080/logged-out).scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build()).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();registeredClientRepository.save(mallAppClient);}/*** 初始化创建商城APP客户端** param registeredClientRepository*/private void initMallAppClient(JdbcRegisteredClientRepository registeredClientRepository) {String clientId mall-app;String clientSecret 123456;String clientName 商城APP客户端;// 如果使用明文在客户端认证的时候会自动升级加密方式直接使用 bcrypt 加密避免不必要的麻烦String encodeSecret passwordEncoder().encode(clientSecret);RegisteredClient registeredMallAppClient registeredClientRepository.findByClientId(clientId);String id registeredMallAppClient ! null ? registeredMallAppClient.getId() : UUID.randomUUID().toString();RegisteredClient mallAppClient RegisteredClient.withId(id).clientId(clientId).clientSecret(encodeSecret).clientName(clientName).clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) // 微信小程序模式.authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE) // 短信验证码模式.redirectUri(http://127.0.0.1:8080/authorized).postLogoutRedirectUri(http://127.0.0.1:8080/logged-out).scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build()).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();registeredClientRepository.save(mallAppClient);} }DefaultSecutiryConfig 参考 Spring Authorization Server 官方示例 demo-authorizationserver 下的 DefaultSecurityConfig.java 进行安全配置 package com.youlai.auth.config;/*** 授权服务器安全配置** author haoxr* since 3.0.0*/ EnableWebSecurity Configuration(proxyBeanMethods false) public class DefaultSecurityConfig {/*** Spring Security 安全过滤器链配置*/BeanOrder(0)SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(requestMatcherRegistry -{requestMatcherRegistry.anyRequest().authenticated();}).csrf(AbstractHttpConfigurer::disable).formLogin(Customizer.withDefaults());return http.build();}/*** Spring Security 自定义安全配置*/Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) -// 不走过滤器链(场景静态资源js、css、html)web.ignoring().requestMatchers(/webjars/**,/doc.html,/swagger-resources/**,/v3/api-docs/**,/swagger-ui/**);} }密码模式扩展 PasswordAuthenticationToken package com.youlai.auth.authentication.password;/*** 密码授权模式身份验证令牌(包含用户名和密码等)** author haoxr* since 3.0.0*/ public class PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {public static final AuthorizationGrantType PASSWORD new AuthorizationGrantType(password);/*** 令牌申请访问范围*/private final SetString scopes;/*** 密码模式身份验证令牌** param clientPrincipal 客户端信息* param scopes 令牌申请访问范围* param additionalParameters 自定义额外参数(用户名和密码)*/public PasswordAuthenticationToken(Authentication clientPrincipal,SetString scopes,Nullable MapString, Object additionalParameters) {super(PASSWORD, clientPrincipal, additionalParameters);this.scopes Collections.unmodifiableSet(scopes ! null ? new HashSet(scopes) : Collections.emptySet());}/*** 用户凭证(密码)*/Overridepublic Object getCredentials() {return this.getAdditionalParameters().get(OAuth2ParameterNames.PASSWORD);}public SetString getScopes() {return scopes;} }PasswordAuthenticationConverter package com.youlai.auth.authentication.password;/*** 密码模式参数解析器* p* 解析请求参数中的用户名和密码并构建相应的身份验证(Authentication)对象** author haoxr* see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter* since 3.0.0*/ public class PasswordAuthenticationConverter implements AuthenticationConverter {Overridepublic Authentication convert(HttpServletRequest request) {// 授权类型 (必需)String grantType request.getParameter(OAuth2ParameterNames.GRANT_TYPE);if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {return null;}// 客户端信息Authentication clientPrincipal SecurityContextHolder.getContext().getAuthentication();// 参数提取验证MultiValueMapString, String parameters OAuth2EndpointUtils.getParameters(request);// 令牌申请访问范围验证 (可选)String scope parameters.getFirst(OAuth2ParameterNames.SCOPE);if (StringUtils.hasText(scope) parameters.get(OAuth2ParameterNames.SCOPE).size() ! 1) {OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,OAuth2ParameterNames.SCOPE,OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);}SetString requestedScopes null;if (StringUtils.hasText(scope)) {requestedScopes new HashSet(Arrays.asList(StringUtils.delimitedListToStringArray(scope, )));}// 用户名验证(必需)String username parameters.getFirst(OAuth2ParameterNames.USERNAME);if (StrUtil.isBlank(username)) {OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,OAuth2ParameterNames.USERNAME,OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);}// 密码验证(必需)String password parameters.getFirst(OAuth2ParameterNames.PASSWORD);if (StrUtil.isBlank(password)) {OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,OAuth2ParameterNames.PASSWORD,OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);}// 附加参数(保存用户名/密码传递给 PasswordAuthenticationProvider 用于身份认证)MapString, Object additionalParameters parameters.entrySet().stream().filter(e - !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) !e.getKey().equals(OAuth2ParameterNames.SCOPE)).collect(Collectors.toMap(Map.Entry::getKey, e - e.getValue().get(0)));return new PasswordAuthenticationToken(clientPrincipal,requestedScopes,additionalParameters);}}PasswordAuthenticationProvider package com.youlai.auth.authentication.password;/*** 密码模式身份验证提供者* p* 处理基于用户名和密码的身份验证** author haoxr* since 3.0.0*/ Slf4j public class PasswordAuthenticationProvider implements AuthenticationProvider {private static final String ERROR_URI https://datatracker.ietf.org/doc/html/rfc6749#section-5.2;private final AuthenticationManager authenticationManager;private final OAuth2AuthorizationService authorizationService;private final OAuth2TokenGenerator? extends OAuth2Token tokenGenerator;/*** Constructs an {code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters.** param authenticationManager the authentication manager* param authorizationService the authorization service* param tokenGenerator the token generator* since 0.2.3*/public PasswordAuthenticationProvider(AuthenticationManager authenticationManager,OAuth2AuthorizationService authorizationService,OAuth2TokenGenerator? extends OAuth2Token tokenGenerator) {Assert.notNull(authorizationService, authorizationService cannot be null);Assert.notNull(tokenGenerator, tokenGenerator cannot be null);this.authenticationManager authenticationManager;this.authorizationService authorizationService;this.tokenGenerator tokenGenerator;}Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {PasswordAuthenticationToken resourceOwnerPasswordAuthentication (PasswordAuthenticationToken) authentication;OAuth2ClientAuthenticationToken clientPrincipal OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient(resourceOwnerPasswordAuthentication);RegisteredClient registeredClient clientPrincipal.getRegisteredClient();// 验证客户端是否支持授权类型(grant_typepassword)if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);}// 生成用户名密码身份验证令牌MapString, Object additionalParameters resourceOwnerPasswordAuthentication.getAdditionalParameters();String username (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);String password (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken new UsernamePasswordAuthenticationToken(username, password);// 用户名密码身份验证成功后返回带有权限的认证信息Authentication usernamePasswordAuthentication;try {usernamePasswordAuthentication authenticationManager.authenticate(usernamePasswordAuthenticationToken);} catch (Exception e) {// 需要将其他类型的异常转换为 OAuth2AuthenticationException 才能被自定义异常捕获处理逻辑源码 OAuth2TokenEndpointFilter#doFilterInternalthrow new OAuth2AuthenticationException(e.getCause() ! null ? e.getCause().getMessage() : e.getMessage());}// 验证申请访问范围(Scope)SetString authorizedScopes registeredClient.getScopes();SetString requestedScopes resourceOwnerPasswordAuthentication.getScopes();if (!CollectionUtils.isEmpty(requestedScopes)) {SetString unauthorizedScopes requestedScopes.stream().filter(requestedScope - !registeredClient.getScopes().contains(requestedScope)).collect(Collectors.toSet());if (!CollectionUtils.isEmpty(unauthorizedScopes)) {throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);}authorizedScopes new LinkedHashSet(requestedScopes);}// 访问令牌(Access Token) 构造器DefaultOAuth2TokenContext.Builder tokenContextBuilder DefaultOAuth2TokenContext.builder().registeredClient(registeredClient).principal(usernamePasswordAuthentication) // 身份验证成功的认证信息(用户名、权限等信息).authorizationServerContext(AuthorizationServerContextHolder.getContext()).authorizedScopes(authorizedScopes).authorizationGrantType(AuthorizationGrantType.PASSWORD) // 授权方式.authorizationGrant(resourceOwnerPasswordAuthentication) // 授权具体对象;// 生成访问令牌(Access Token)OAuth2TokenContext tokenContext tokenContextBuilder.tokenType((OAuth2TokenType.ACCESS_TOKEN)).build();OAuth2Token generatedAccessToken this.tokenGenerator.generate(tokenContext);if (generatedAccessToken null) {OAuth2Error error new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,The token generator failed to generate the access token., ERROR_URI);throw new OAuth2AuthenticationException(error);}OAuth2AccessToken accessToken new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());// 权限数据(perms)比较多通过反射移除不随令牌一起持久化至数据库ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), perms, null);OAuth2Authorization.Builder authorizationBuilder OAuth2Authorization.withRegisteredClient(registeredClient).principalName(usernamePasswordAuthentication.getName()).authorizationGrantType(AuthorizationGrantType.PASSWORD).authorizedScopes(authorizedScopes).attribute(Principal.class.getName(), usernamePasswordAuthentication); // attribute 字段if (generatedAccessToken instanceof ClaimAccessor) {authorizationBuilder.token(accessToken, (metadata) -metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));} else {authorizationBuilder.accessToken(accessToken);}// 生成刷新令牌(Refresh Token)OAuth2RefreshToken refreshToken null;if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) // Do not issue refresh token to public client!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {tokenContext tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();OAuth2Token generatedRefreshToken this.tokenGenerator.generate(tokenContext);if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {OAuth2Error error new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,The token generator failed to generate the refresh token., ERROR_URI);throw new OAuth2AuthenticationException(error);}refreshToken (OAuth2RefreshToken) generatedRefreshToken;authorizationBuilder.refreshToken(refreshToken);}OAuth2Authorization authorization authorizationBuilder.build();// 持久化令牌发放记录到数据库this.authorizationService.save(authorization);additionalParameters Collections.emptyMap();return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);}/*** 判断传入的 authentication 类型是否与当前认证提供者(AuthenticationProvider)相匹配--模板方法* p* ProviderManager#authenticate 遍历 providers 找到支持对应认证请求的 provider-迭代器模式** param authentication* return*/Overridepublic boolean supports(Class? authentication) {return PasswordAuthenticationToken.class.isAssignableFrom(authentication);}} JWT 自定义字段 参考官方 ISSUE :Adds how-to guide on adding authorities to access tokens package com.youlai.auth.config;/*** JWT 自定义字段** author haoxr* since 3.0.0*/ Configuration RequiredArgsConstructor public class JwtTokenClaimsConfig {private final RedisTemplate redisTemplate;Beanpublic OAuth2TokenCustomizerJwtEncodingContext jwtTokenCustomizer() {return context - {if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {// Customize headers/claims for access_tokenOptional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal - {JwtClaimsSet.Builder claims context.getClaims();if (principal instanceof SysUserDetails userDetails) { // 系统用户添加自定义字段Long userId userDetails.getUserId();claims.claim(user_id, userId); // 添加系统用户ID// 角色集合存JWTvar authorities AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities()).stream().collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));claims.claim(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY, authorities);// 权限集合存Redis(数据多)SetString perms userDetails.getPerms();redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX userId, perms);} else if (principal instanceof MemberDetails userDetails) { // 商城会员添加自定义字段claims.claim(member_id, String.valueOf(userDetails.getId())); // 添加会员ID}});}};}}自定义认证响应 如何自定义 OAuth2 认证成功或失败的响应数据结构符合当前系统统一的规范 下图左侧部份是 OAuth2 原生返回(⬅️ )大多数情况下我们希望返回带有业务码的数据(➡️)以方便前端进行处理。 OAuth2 处理认证成功或失败源码坐标 OAuth2TokenEndpointFilter#doFilterInternal 如下图 根据源码阅读发现只要重写✅ AuthenticationSuccessHandler 和❌ AuthenticationFailureHandler 的逻辑就能够自定义认证成功和认证失败时的响应数据格式。 认证成功响应 package com.youlai.auth.handler;/*** 认证成功处理器** author haoxr* since 3.0.0*/ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {/*** MappingJackson2HttpMessageConverter 是 Spring 框架提供的一个 HTTP 消息转换器用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换*/private final HttpMessageConverterObject accessTokenHttpResponseConverter new MappingJackson2HttpMessageConverter();private ConverterOAuth2AccessTokenResponse, MapString, Object accessTokenResponseParametersConverter new DefaultOAuth2AccessTokenResponseMapConverter();/*** 自定义认证成功响应数据结构** param request the request which caused the successful authentication* param response the response* param authentication the ttAuthentication/tt object which was created during* the authentication process.* throws IOException* throws ServletException*/Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {OAuth2AccessTokenAuthenticationToken accessTokenAuthentication (OAuth2AccessTokenAuthenticationToken) authentication;OAuth2AccessToken accessToken accessTokenAuthentication.getAccessToken();OAuth2RefreshToken refreshToken accessTokenAuthentication.getRefreshToken();MapString, Object additionalParameters accessTokenAuthentication.getAdditionalParameters();OAuth2AccessTokenResponse.Builder builder OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()).tokenType(accessToken.getTokenType());if (accessToken.getIssuedAt() ! null accessToken.getExpiresAt() ! null) {builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));}if (refreshToken ! null) {builder.refreshToken(refreshToken.getTokenValue());}if (!CollectionUtils.isEmpty(additionalParameters)) {builder.additionalParameters(additionalParameters);}OAuth2AccessTokenResponse accessTokenResponse builder.build();MapString, Object tokenResponseParameters this.accessTokenResponseParametersConverter.convert(accessTokenResponse);ServletServerHttpResponse httpResponse new ServletServerHttpResponse(response);this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);} } 认证失败响应 package com.youlai.auth.handler;/*** 认证失败处理器** author haoxr* since 2023/7/6*/ Slf4j public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {/*** MappingJackson2HttpMessageConverter 是 Spring 框架提供的一个 HTTP 消息转换器用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换*/private final HttpMessageConverterObject accessTokenHttpResponseConverter new MappingJackson2HttpMessageConverter();Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {OAuth2Error error ((OAuth2AuthenticationException) exception).getError();ServletServerHttpResponse httpResponse new ServletServerHttpResponse(response);Result result Result.failed(error.getErrorCode());accessTokenHttpResponseConverter.write(result, null, httpResponse);} }配置自定义处理器 AuthorizationServierConfig public SecurityFilterChain authorizationServerSecurityFilterChain() throws Exception {// ...authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -tokenEndpoint// ....accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定义成功响应.errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定义失败响应);}密码模式测试 单元测试 启动 youlai-system 模块需要从其获取系统用户信息(用户名、密码)进行认证 package com.youlai.auth.authentication;/*** OAuth2 密码模式单元测试*/ SpringBootTest AutoConfigureMockMvc Slf4j public class PasswordAuthenticationTests {Autowiredprivate MockMvc mvc;/*** 测试密码模式登录*/Testvoid testPasswordLogin() throws Exception {HttpHeaders headers new HttpHeaders();// 客户端ID和密钥headers.setBasicAuth(mall-admin, 123456);this.mvc.perform(post(/oauth2/token).param(OAuth2ParameterNames.GRANT_TYPE, password) // 密码模式.param(OAuth2ParameterNames.USERNAME, admin) // 用户名.param(OAuth2ParameterNames.PASSWORD, 123456) // 密码.headers(headers)).andDo(print()).andExpect(status().isOk()).andExpect(jsonPath($.data.access_token).isNotEmpty());} }单元测试通过打印响应数据可以看到返回的 access_token 和 refresh_token Postman 测试 请求参数 认证参数 Authorization Type 选择 Basic Auth , 填写客户端ID(mall-admin)和密钥(123456) 资源服务器 youlai-system 系统管理模块也作为资源服务器 maven 依赖 !-- Spring Authorization Server 授权服务器依赖 -- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-oauth2-resource-server/artifactId /dependencyapplication.yml 通过 Feign 请求 youlai-system 服务以获取系统用户认证信息用户名和密码在用户尚未登录的情况下需要将此请求的路径配置到白名单中以避免拦截。 security:# 允许无需认证的路径列表whitelist-paths:# 获取系统用户的认证信息用于账号密码判读- /api/v1/users/{username}/authInfo资源服务器配置 配置 ResourceServerConfig 位于资源服务器公共模块 common-security 中 package com.youlai.common.security.config;import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.json.JSONUtil; import com.youlai.common.constant.SecurityConstants; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; 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.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain;import java.util.List;/*** 资源服务器配置** author haoxr* since 3.0.0*/ ConfigurationProperties(prefix security) Configuration EnableWebSecurity Slf4j public class ResourceServerConfig {/*** 白名单路径列表*/Setterprivate ListString ignoreUrls;Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {log.info(whitelist path:{}, JSONUtil.toJsonStr(ignoreUrls));http.authorizeHttpRequests(requestMatcherRegistry -{if (CollectionUtil.isNotEmpty(ignoreUrls)) {requestMatcherRegistry.requestMatchers(Convert.toStrArray(ignoreUrls)).permitAll();}requestMatcherRegistry.anyRequest().authenticated();}).csrf(AbstractHttpConfigurer::disable);http.oauth2ResourceServer(resourceServerConfigurer -resourceServerConfigurer.jwt(jwtConfigurer - jwtAuthenticationConverter())) ;return http.build();}/*** 不走过滤器链的放行配置*/Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) - web.ignoring().requestMatchers(/webjars/**,/doc.html,/swagger-resources/**,/v3/api-docs/**,/swagger-ui/**);}/*** 自定义JWT Converter** return Converter* see JwtAuthenticationProvider#setJwtAuthenticationConverter(Converter)*/Beanpublic ConverterJwt, ? extends AbstractAuthenticationToken jwtAuthenticationConverter() {JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter new JwtGrantedAuthoritiesConverter();jwtGrantedAuthoritiesConverter.setAuthorityPrefix(Strings.EMPTY);jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY);JwtAuthenticationConverter jwtAuthenticationConverter new JwtAuthenticationConverter();jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);return jwtAuthenticationConverter;} }认证流程测试 分别启动 youlai-mall 的 youai-auth (认证中心)、youlai-system(系统管理模块)、youali-gateway(网关) 登录认证授权 请求参数 认证参数 Authorization Type 选择 Basic Auth , 填写客户端ID(mall-admin)和密钥(123456) 成功响应 认证成功获取到访问令牌(access_token ) 获取用户信息 使用已获得的访问令牌 (access_token) 向资源服务器发送请求以获取登录用户信息 成功地获取登录用户信息的响应而不是出现未授权的401错误。 结语 关于 Spring Authorization Server 1.1 版本的密码模式扩展和在 Spring Cloud 中使用新的授权方式可以说与 Spring Security OAuth2 的代码相似度极高。如果您已经熟悉 Spring Security OAuth2那么学习 Spring Authorization Server 将变得轻而易举。后续文章会更新其他常见授权模式的扩展敬请期待~ 源码 本文完整源码 youlai-mall 参考文档 Spring Security 弃用 授权服务器和资源服务器 Spring Security OAuth 生命周期终止通知 Spring Security OAuth 2.0 更新路线图
http://www.zqtcl.cn/news/679255/

相关文章:

  • 网站改版工作方案网站设计技能培训
  • 佳木斯市网站建设淄博网站开发招聘
  • 学习软件的网站先备案先建网站
  • 建立网站 知乎常州网站制作机构
  • 洛阳建设网站上海高端室内设计事务所
  • 做高清图的网站wordpress分类自定义文字
  • 创建站点如何做网站如何利用分类信息网站做推广
  • wordpress 拍卖插件找文网优化的技术团队
  • 建站素材网自助餐火锅网站建设
  • 企业型网站建设方案农村电商网站设计与发展现状
  • 建站快车凡科企业网站建设合同(一)
  • 阜平网站建设在广州做seo找哪家公司
  • 怎么做农家乐联盟网站六安建设机械网站
  • 网站开发行业标准江苏网站开发公司
  • 服装技术支持东莞网站建设如何加强企业网站建设论文
  • 中英双语网站怎么做深圳勘察设计协会
  • 用dw做网站维护教程梧州网站建设制作
  • 网站代运营公司有哪些深圳小区封闭最新通知
  • 江西网站设计服务网站开发所需费用明细
  • 深圳网站建设公司jm3q编程网站免费中文版
  • 泉州专门制作网站如何在小红书上做推广
  • 网站改版活动微网站开发一般费用多少钱
  • 网站关键词挖掘顺德网站制作案例价位
  • 广广东网站建设企业网站无锡
  • 广州网站备案号wordpress模板专题页
  • 西安做网站哪里价格低综合查询
  • 电商需要多少投入沈阳网站关键词优化
  • 速拓科技是做网站百度推广登陆入口官网
  • 十大高端网站设计网站开发培训达内
  • 河北云网站建设怎么让别人找你做网站