OAuth2生成token代码备忘实现过程示例
一、登录接口(用户名+密码)
1、前端请求auth服务
http://127.0.0.1:72/oauth/pwdLogin
2、请求数据
{ "mobile": "134178101xx", "password": "123456" }
3、Controller方法
@SneakyThrows @PostMapping("pwdLogin") @SignMemberLoginLog(value = "APP_PWD", desc = "密码登录") @ApiOperation(value = "会员登录(密码登录)") public Result<Oauth2TokenDto> pwdLogin(@RequestBody MemberLoginPwdVO vo, HttpServletRequest request) { if (StringUtil.isEmpty(vo.getClientId())) { vo.setClientId("app"); } vo.setIp(IpUtils.ip(request)); Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_PWD.getCode()); params.put("mobile", vo.getMobile()); params.put("password", vo.getPassword()); List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); Oauth2TokenDto oauth2TokenDto = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params); return Result.success(oauth2TokenDto); } private Map<String, String> getMemberBaseParam(MemberLoginBaseVO vo, String grantType) { Map<String, String> params = new HashMap<>(); params.put("client_id", vo.getClientId()); params.put("client_secret", "app"); params.put("grant_type", grantType); params.put("scope", "all"); params.put("platform", vo.getPlatform()); //附加信息 params.put("version", vo.getVersion()); params.put("device", vo.getDevice()); params.put("iemi", vo.getIemi()); params.put("location", vo.getLocation()); params.put("ip", vo.getIp()); params.put("recommendCode", vo.getRecommendCode()); return params; }
二、授权接口调用逻辑
2.1 AuthTokenComponent类
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; @Component public class AuthTokenComponent { @Autowired private TokenEndpoint tokenEndpoint; public Oauth2TokenDto getAccessToken(String clientId, String clientSecurity , List<GrantedAuthority> grantedAuthorities, Map<String, String> params) throws HttpRequestMethodNotSupportedException { User principle = new User(clientId,clientSecurity,true,true,true,true,grantedAuthorities); return getAccessToken(principle,params); } public Oauth2TokenDto getAccessToken(User principle, Map<String, String> params) throws HttpRequestMethodNotSupportedException { UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(principle,null,principle.getAuthorities()); OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, params).getBody(); Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder() .token(oAuth2AccessToken.getValue()) .refreshToken(oAuth2AccessToken.getRefreshToken().getValue()) .expiresIn(oAuth2AccessToken.getExpiresIn()) .tokenHead("Bearer ").build(); return oauth2TokenDto; } }
调用tokenEndpoint.postAccessToken
生成token时,接口调用逻辑:
- 1、调用AuthenticationProvider接口(AdminAuthenticationProvider实现类)密码校验
- 2、调用
UserDetailsService
接口(MyUserDetailsService实现类)获取用户信息 - 3、调用
DefaultTokenServices
接口(CustomTokenServices实现类)生成token
2.2 AuthenticationProvider接口
1、MobilePasswordAuthenticationProvider实现类
@Setter public class MobilePasswordAuthenticationProvider implements AuthenticationProvider { private QmUserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) { MobilePasswordAuthenticationToken authenticationToken = (MobilePasswordAuthenticationToken) authentication; String mobile = (String) authenticationToken.getPrincipal(); String password = (String) authenticationToken.getCredentials(); SecurityUser user = userDetailsService.loadUserByMobile(mobile); if (user == null) { throw new QiMiaoException(ResultCode.JWT_USER_INVALID); } if(userDetailsService.checkBlock(mobile)) { throw new QiMiaoException(ResultCode.JWT_USER_BLOCK); } if (!passwordEncoder.matches(password, user.getPassword())) { userDetailsService.inc(mobile); throw new QiMiaoException(ResultCode.JWT_USER_INVALID_PWD); } Map<String, String> parameters = (Map<String, String>)authenticationToken.getDetails(); if(null != parameters.get("platform")) { user.setPlatform(parameters.get("platform")); } MobilePasswordAuthenticationToken authenticationResult = new MobilePasswordAuthenticationToken(user, password, user.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { return MobilePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
2、MobileAuthenticationSecurityConfig配置类
@Component public class MobileAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private QmUserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SmsVcodeRdsHelper smsVcodeRdsHelper; @Override public void configure(HttpSecurity http) { MobilePasswordAuthenticationProvider provider = new MobilePasswordAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder); http.authenticationProvider(provider); MobileSmsAuthenticationProvider smsProvider = new MobileSmsAuthenticationProvider(); smsProvider.setUserDetailsService(userDetailsService); smsProvider.setSmsVcodeRdsHelper(smsVcodeRdsHelper); http.authenticationProvider(smsProvider); MobileOneKeyAuthenticationProvider oneKeyProvider = new MobileOneKeyAuthenticationProvider(); oneKeyProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(oneKeyProvider); VisitorAuthenticationProvider visitorAuthenticationProvider = new VisitorAuthenticationProvider(); visitorAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(visitorAuthenticationProvider); QRCodeAuthenticationProvider qrCodeAuthenticationProvider = new QRCodeAuthenticationProvider(); qrCodeAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(qrCodeAuthenticationProvider); SocialAuthenticationProvider socialAuthenticationProvider = new SocialAuthenticationProvider(); socialAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(socialAuthenticationProvider); } }
3、WebSecurityConfig配置类
@Configuration @EnableWebSecurity @Import(DefaultPasswordConfig.class) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //管理系统登录 @Autowired private AdminAuthenticationSecurityConfig adminAuthenticationSecurityConfig; //App登录 @Autowired private MobileAuthenticationSecurityConfig mobileAuthenticationSecurityConfig; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .antMatchers("/rsa/publicKey", "/actuator/**").permitAll() .antMatchers("/").permitAll() .antMatchers(HttpMethod.POST, "/oauth/**").permitAll() // swagger .antMatchers("/swagger-ui.html").permitAll() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/images/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/v2/api-docs").permitAll() .antMatchers("/configuration/ui").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .anyRequest() //授权服务器关闭basic认证 .permitAll() .and() .logout() .logoutUrl(SecurityConstants.LOGOUT_URL) .logoutSuccessHandler(oauthLogoutSuccessHandler) .addLogoutHandler(oauthLogoutHandler) .clearAuthentication(true) .and() .apply(mobileAuthenticationSecurityConfig) .and() .apply(adminAuthenticationSecurityConfig) .and() .csrf().disable() // 解决不允许显示在iframe的问题 .headers().frameOptions().disable().cacheControl(); // 基于密码 等模式可以无session,不支持授权码模式 if (authenticationEntryPoint != null) { http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } else { // 授权码模式单独处理,需要session的支持,此模式可以支持所有oauth2的认证 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } } }
2.3 MyUserDetailsService接口 & MyUserDetailServiceImpl类
public interface MyUserDetailsService extends UserDetailsService { SecurityUser loadUserByMobile(String mobile); SecurityUser loadUserById(Long id); SecurityUser loadUserByOpenId(String openId, String platform); SecurityUser loadUserByImei(String im); SecurityUser loadAdminUser(String mobileOrUserName); SecurityUser createUserByMobile(String mobile); SecurityUser createUserByMobile(String globalCode, String mobile); SecurityUser createVisitor(Map<String, String> map); }
@Slf4j public class MyUserDetailServiceImpl implements MyUserDetailsService{ @Override public SecurityUser loadAdminUser(String mobileOrUserName) { SecurityUser securityUser = null; Result<UserDTO> userDTOResult = userFeignService.selectByMobile(mobileOrUserName); if (ResultCode.SUCCESS.getCode() == userDTOResult.getCode()) { if (userDTOResult.getData() == null) { throw new Exception(ResultCode.JWT_USER_INVALID); } UserDTO userDTO = userDTOResult.getData(); //是否被禁用 if (!userDTO.getStatus()) { throw new Exception(ResultCode.JWT_USER_ENABLED); } securityUser = new SecurityUser(); securityUser.setUserType(SecurityUserTypeEnum.ADMIN); securityUser.setGrantType(SecurityLoginTypeEnum.ADMIN_PWD); securityUser.setId(userDTO.getId()); securityUser.setUsername(userDTO.getLoginName()); securityUser.setPassword(userDTO.getLoginPwd()); securityUser.setEnabled(true); Collection<SimpleGrantedAuthority> authorities = new HashSet<>(); //基于权限控制 Result<List<PermDTO>> dtoResult = permFeignService.permListByUserId(userDTO.getId()); if (ResultCode.SUCCESS.getCode() == dtoResult.getCode()) { if (dtoResult.getData() != null) { for (PermDTO dto : dtoResult.getData()) { authorities.add(new SimpleGrantedAuthority(dto.getPermApiHttpMethod() + dto.getPermValue())); } } } securityUser.setAuthorities(authorities); } return securityUser; } }
2.4 CustomTokenServices类
@Slf4j public class CustomTokenServices extends DefaultTokenServices { private TokenStore tokenStore; private TokenEnhancer accessTokenEnhancer; //是否登录同应用同账号互踢 private boolean isSingleLogin; public CustomTokenServices(boolean isSingleLogin) { this.isSingleLogin = isSingleLogin; } @Override @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); log.info("createAccessToken,start existingAccessToken={}",existingAccessToken); OAuth2RefreshToken refreshToken = null; if (existingAccessToken != null) { if (isSingleLogin) { if (existingAccessToken.getRefreshToken() != null) { tokenStore.removeRefreshToken(existingAccessToken.getRefreshToken()); log.info("createAccessToken,removeRefreshToken A={}",existingAccessToken); log.info("createAccessToken,getRefreshToken A1={}",existingAccessToken.getRefreshToken()); } } else if (existingAccessToken.isExpired()) { if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); log.info("createAccessToken,isExpired B={}",existingAccessToken); } else { // oidc每次授权都刷新id_token existingAccessToken = refreshIdToken(existingAccessToken, authentication); tokenStore.storeAccessToken(existingAccessToken, authentication); log.info("createAccessToken,isExpired C={}",existingAccessToken); return existingAccessToken; } } if (refreshToken == null) { refreshToken = createRefreshToken(authentication); } else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = createRefreshToken(authentication); } } OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } log.info("createAccessToken,end accessToken={}",accessToken); return accessToken; } }
三、AuthenticationProvider详解
3.1 AuthenticationProvider多个实现类
如果项目中定义了多个AuthenticationProvider实现类,那登录时,怎么判断用哪个AuthenticationProvider实现类?我们可以通过源码来找到答案
1、MobileAuthenticationSecurityConfig
@Component public class MobileAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private MyUserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SmsVcodeRdsHelper smsVcodeRdsHelper; @Override public void configure(HttpSecurity http) { MobilePasswordAuthenticationProvider provider = new MobilePasswordAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder); http.authenticationProvider(provider); MobileSmsAuthenticationProvider smsProvider = new MobileSmsAuthenticationProvider(); smsProvider.setUserDetailsService(userDetailsService); smsProvider.setSmsVcodeRdsHelper(smsVcodeRdsHelper); http.authenticationProvider(smsProvider); MobileOneKeyAuthenticationProvider oneKeyProvider = new MobileOneKeyAuthenticationProvider(); oneKeyProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(oneKeyProvider); VisitorAuthenticationProvider visitorAuthenticationProvider = new VisitorAuthenticationProvider(); visitorAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(visitorAuthenticationProvider); QRCodeAuthenticationProvider qrCodeAuthenticationProvider = new QRCodeAuthenticationProvider(); qrCodeAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(qrCodeAuthenticationProvider); SocialAuthenticationProvider socialAuthenticationProvider = new SocialAuthenticationProvider(); socialAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(socialAuthenticationProvider); } }
2、管理系统登录实现类
@Setter public class AdminAuthenticationProvider implements AuthenticationProvider { private QmUserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) { UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication; String userName = (String) authenticationToken.getPrincipal(); String password = (String) authenticationToken.getCredentials(); UserDetails user = userDetailsService.loadAdminUser(userName); if (user == null) { throw new Exception(ResultCode.JWT_USER_INVALID); } if(userDetailsService.checkBlock(userName)) { throw new Exception(ResultCode.JWT_USER_BLOCK); } if (!passwordEncoder.matches(password, user.getPassword())) { userDetailsService.inc(userName); throw new Exception(ResultCode.JWT_USER_INVALID); } UsernamePasswordAuthenticationToken authenticationResult = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } // 重点看这里 @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
3、手机号+密码登录实现类
@Setter public class MobilePasswordAuthenticationProvider implements AuthenticationProvider { private QmUserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) { MobilePasswordAuthenticationToken authenticationToken = (MobilePasswordAuthenticationToken) authentication; String mobile = (String) authenticationToken.getPrincipal(); String password = (String) authenticationToken.getCredentials(); SecurityUser user = userDetailsService.loadUserByMobile(mobile); if (user == null) { throw new QiMiaoException(ResultCode.JWT_USER_INVALID); } if(userDetailsService.checkBlock(mobile)) { throw new QiMiaoException(ResultCode.JWT_USER_BLOCK); } if (!passwordEncoder.matches(password, user.getPassword())) { userDetailsService.inc(mobile); throw new QiMiaoException(ResultCode.JWT_USER_INVALID_PWD); } Map<String, String> parameters = (Map<String, String>)authenticationToken.getDetails(); if(null != parameters.get("platform")) { user.setPlatform(parameters.get("platform")); } MobilePasswordAuthenticationToken authenticationResult = new MobilePasswordAuthenticationToken(user, password, user.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } // 重点看这里 @Override public boolean supports(Class<?> authentication) { return MobilePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
4、MobilePasswordAuthenticationToken
package com.alanchen.ac; public class MobilePasswordAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; private Object credentials; public MobilePasswordAuthenticationToken(String mobile, String password) { super(null); this.principal = mobile; this.credentials = password; setAuthenticated(false); } public MobilePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) { if (isAuthenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); } }
3.2 AuthenticationProvider源码
首先进入到AuthenticationProvider源码中可以看到它只是个简单的接口里面也只有两个方法:
public interface AuthenticationProvider { // 具体认证流程 Authentication authenticate(Authentication authentication) throws AuthenticationException; // supports函数用来指明该Provider是否适用于该类型的认证,如果不合适,则寻找另一个Provider进行验证处理。 boolean supports(Class<?> authentication); }
3.3 ProviderManager源码
ProviderManager提供了一个list对AuthenticationProvider进行统一管理,即一个认证处理器链来支持同一个应用中的多个不同身份认证机制,ProviderManager将会根据顺序来进行验证。
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var8.next(); //这里调用的就是AuthenticationProvider的方法supports(),如果项目中定义了多个AuthenticationProvider,则是通过这里判断来取哪一个AuthenticationProvider实现类 if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (InternalAuthenticationServiceException | AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (AuthenticationException var14) { lastException = var14; } } } //省略代码 }
3.4 手机号+密码Granter
重点关注该类中的:
1、MobilePasswordAuthenticationToken
2、SecurityGrantType.APP_PWD.getCode(),和Controller.pwdLogin里的grant_type是同一个类型。
public class MobilePwdGranter extends AbstractTokenGranter { private final AuthenticationManager authenticationManager; public MobilePwdGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { super(tokenServices, clientDetailsService, requestFactory, SecurityGrantType.APP_PWD.getCode()); this.authenticationManager = authenticationManager; } @Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); String mobile = parameters.get("mobile"); String password = parameters.get("password"); // Protect from downstream leaks of password parameters.remove("password"); Authentication userAuth = new MobilePasswordAuthenticationToken(mobile, password); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); userAuth = authenticationManager.authenticate(userAuth); if (userAuth == null || !userAuth.isAuthenticated()) { throw new InvalidGrantException("Could not authenticate mobile: " + mobile); } OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, userAuth); } }
3.5 TokenGranter配置
@Configuration public class TokenGranterConfig { @Autowired private ClientDetailsService clientDetailsService; @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; @Autowired(required = false) private List<TokenEnhancer> tokenEnhancer; @Autowired private RandomValueAuthorizationCodeServices authorizationCodeServices; private boolean reuseRefreshToken = true; private AuthorizationServerTokenServices tokenServices; private TokenGranter tokenGranter; /** * 授权模式 */ @Bean public TokenGranter tokenGranter() { if (tokenGranter == null) { tokenGranter = new TokenGranter() { private CompositeTokenGranter delegate; @Override public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { if (delegate == null) { delegate = new CompositeTokenGranter(getAllTokenGranters()); } return delegate.grant(grantType, tokenRequest); } }; } return tokenGranter; } /** * 所有授权模式:默认的5种模式 + 自定义的模式 */ private List<TokenGranter> getAllTokenGranters() { AuthorizationServerTokenServices tokenServices = tokenServices(); AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices(); OAuth2RequestFactory requestFactory = requestFactory(); //获取默认的授权模式 List<TokenGranter> tokenGranters = getDefaultTokenGranters(tokenServices, authorizationCodeServices, requestFactory); if (authenticationManager != null) { // 添加social模式 tokenGranters.add(new SocialGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); // 添加手机号加密码授权模式 tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); // 添加手机号加密码授权模式 tokenGranters.add(new MobileSmsGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); tokenGranters.add(new AdminPwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); tokenGranters.add(new MobileOneKeyGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); tokenGranters.add(new VisitorGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); tokenGranters.add(new QRCodeGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); } return tokenGranters; } /** * 默认的授权模式 */ private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerTokenServices tokenServices , AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) { List<TokenGranter> tokenGranters = new ArrayList<>(); // 添加授权码模式 tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory)); // 添加刷新令牌的模式 tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory)); // 添加隐士授权模式 tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory)); // 添加客户端模式 tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory)); if (authenticationManager != null) { // 添加密码模式 tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); } return tokenGranters; } private AuthorizationServerTokenServices tokenServices() { if (tokenServices != null) { return tokenServices; } this.tokenServices = createDefaultTokenServices(); return tokenServices; } private AuthorizationCodeServices authorizationCodeServices() { if (authorizationCodeServices == null) { authorizationCodeServices = new InMemoryAuthorizationCodeServices(); } return authorizationCodeServices; } private OAuth2RequestFactory requestFactory() { return new DefaultOAuth2RequestFactory(clientDetailsService); } private DefaultTokenServices createDefaultTokenServices() { //token互踢 DefaultTokenServices tokenServices = new CustomTokenServices(true); tokenServices.setTokenStore(tokenStore); tokenServices.setSupportRefreshToken(true); tokenServices.setReuseRefreshToken(reuseRefreshToken); tokenServices.setClientDetailsService(clientDetailsService); tokenServices.setTokenEnhancer(tokenEnhancer()); addUserDetailsService(tokenServices, this.userDetailsService); return tokenServices; } private TokenEnhancer tokenEnhancer() { if (tokenEnhancer != null) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(tokenEnhancer); return tokenEnhancerChain; } return null; } private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) { if (userDetailsService != null) { PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService)); tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider))); } } }
四、生成toekn详解
生成token前,先从tokenStore里去toeken,看是否已经存在,如果存在则执行挤下线逻辑。tokenStore
对应的是实现类com.auth.store.CustomRedisTokenStore
。
4.1 TokenStore配置类
@Configuration public class AuthRedisTokenStore { @Bean public TokenStore tokenStore(RedisConnectionFactory connectionFactory, RedisSerializer<Object> redisValueSerializer) { return new CustomRedisTokenStore(connectionFactory, redisValueSerializer); } }
4.2 TokenStore实现类
/** * 优化自Spring Security的RedisTokenStore * 1. 支持redis所有集群模式包括cluster模式 * 2. 使用pipeline减少连接次数,提升性能 * 3. 自动续签token */ @Slf4j public class CustomRedisTokenStore implements TokenStore { private static final String ACCESS = "{auth}access:"; private static final String AUTH_TO_ACCESS = "{auth}auth_to_access:"; private static final String REFRESH_AUTH = "{auth}refresh_auth:"; private static final String ACCESS_TO_REFRESH = "{auth}access_to_refresh:"; private static final String REFRESH = "{auth}refresh:"; private static final String REFRESH_TO_ACCESS = "{auth}refresh_to_access:"; private static final String RELATION_ID_TOKEN = "{auth}relation_id_token:"; private static final boolean springDataRedis_2_0 = ClassUtils.isPresent( "org.springframework.data.redis.connection.RedisStandaloneConfiguration", RedisTokenStore.class.getClassLoader()); private final RedisConnectionFactory connectionFactory; private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator(); private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); private String prefix = ""; private Method redisConnectionSet_2_0; /** * 业务redis的value序列化 */ private RedisSerializer<Object> redisValueSerializer; public CustomRedisTokenStore(RedisConnectionFactory connectionFactory, RedisSerializer<Object> redisValueSerializer) { this.connectionFactory = connectionFactory; this.redisValueSerializer = redisValueSerializer; if (springDataRedis_2_0) { this.loadRedisConnectionMethods_2_0(); } } public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) { this.authenticationKeyGenerator = authenticationKeyGenerator; } public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { this.serializationStrategy = serializationStrategy; } public void setPrefix(String prefix) { this.prefix = prefix; } private void loadRedisConnectionMethods_2_0() { this.redisConnectionSet_2_0 = ReflectionUtils.findMethod( RedisConnection.class, "set", byte[].class, byte[].class); } private RedisConnection getConnection() { return connectionFactory.getConnection(); } private byte[] serialize(Object object) { return serializationStrategy.serialize(object); } private byte[] serializeKey(String object) { return serialize(prefix + object); } private OAuth2AccessToken deserializeAccessToken(byte[] bytes) { return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class); } private OAuth2Authentication deserializeAuthentication(byte[] bytes) { return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); } private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) { return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class); } private ClientDetails deserializeClientDetails(byte[] bytes) { return (ClientDetails)redisValueSerializer.deserialize(bytes); } private byte[] serialize(String string) { return serializationStrategy.serialize(string); } private String deserializeString(byte[] bytes) { return serializationStrategy.deserializeString(bytes); } @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { String key = authenticationKeyGenerator.extractKey(authentication); byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key); byte[] bytes; RedisConnection conn = getConnection(); try { bytes = conn.get(serializedKey); } finally { conn.close(); } OAuth2AccessToken accessToken = deserializeAccessToken(bytes); if (accessToken != null) { OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue()); if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) { // Keep the stores consistent (maybe the same user is // represented by this authentication but the details have // changed) storeAccessToken(accessToken, authentication); } } return accessToken; } @Override public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { OAuth2Authentication auth2Authentication = readAuthentication(token.getValue()); //是否开启token续签 boolean isRenew = true; if (isRenew && auth2Authentication != null) { OAuth2Request clientAuth = auth2Authentication.getOAuth2Request(); //判断当前应用是否需要自动续签 if (checkRenewClientId(clientAuth.getClientId())) { //获取过期时长 int validitySeconds = 2592000; double expiresRatio = token.getExpiresIn() / (double)validitySeconds; //判断是否需要续签,当前剩余时间小于过期时长的50%则续签 if (expiresRatio <= 0.5) { //更新AccessToken过期时间 DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token; oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); storeAccessToken(oAuth2AccessToken, auth2Authentication, true); } } } return auth2Authentication; } /** * 判断应用自动续签是否满足白名单和黑名单的过滤逻辑 后期看需求是否需要 * @param clientId 应用id * @return 是否满足 */ private boolean checkRenewClientId(String clientId) { boolean result = true; return result; } public String getToken(String id){ byte[] relationIdTokenKey = getRelationIdTokenKey(id); RedisConnection conn = getConnection(); try { byte[] bytes = conn.get(relationIdTokenKey); return deserializeString(bytes); } finally { conn.close(); } } /** * 获取token的总有效时长 * @param clientId 应用id */ private int getAccessTokenValiditySeconds(String clientId) { RedisConnection conn = getConnection(); byte[] bytes; try { bytes = conn.get(serializeKey(SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId)); } finally { conn.close(); } if (bytes != null) { ClientDetails clientDetails = deserializeClientDetails(bytes); if (clientDetails.getAccessTokenValiditySeconds() != null) { return clientDetails.getAccessTokenValiditySeconds(); } } //返回默认值 return SecurityConstants.ACCESS_TOKEN_VALIDITY_SECONDS; } @Override public OAuth2Authentication readAuthentication(String token) { byte[] bytes; RedisConnection conn = getConnection(); try { bytes = conn.get(serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token)); } finally { conn.close(); } return deserializeAuthentication(bytes); } @Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { return readAuthenticationForRefreshToken(token.getValue()); } public OAuth2Authentication readAuthenticationForRefreshToken(String token) { RedisConnection conn = getConnection(); try { byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token)); return deserializeAuthentication(bytes); } finally { conn.close(); } } @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { storeAccessToken(token, authentication, false); } private byte[] getRelationIdTokenKey(String id){ return serializeKey(RELATION_ID_TOKEN + id); } private byte[] getRelationIdTokenKey(OAuth2Authentication authentication){ byte[] relationIdTokenKey = null; Object obj = authentication.getPrincipal(); Object details = authentication.getUserAuthentication().getDetails(); if(obj!=null && details!=null){ if(obj instanceof SecurityUser && details instanceof HashMap){ SecurityUser user = (SecurityUser)obj; Map map = (Map)details; String clientId = String.valueOf(map.get("client_id")); Long userId = user.getId(); relationIdTokenKey = serializeKey(RELATION_ID_TOKEN +clientId+":"+userId); } }else{ log.error("storeAccessToken 没有取到principal"); } return relationIdTokenKey; } public SecurityUser getSecurityUser(String token){ SecurityUser user = null; OAuth2Authentication auth2Authentication = readAuthentication(token); if (auth2Authentication != null) { Object obj = auth2Authentication.getPrincipal(); if (obj!=null && obj instanceof SecurityUser) { user = (SecurityUser) obj; }else{ log.error("getSecurityUser:解析User失败,"+obj); } }else{ log.error("getSecurityUser:auth2Authentication 为nuLl"); } return user; } /** * 存储token * @param isRenew 是否续签 */ private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isRenew) { byte[] serializedAccessToken = serialize(token); byte[] serializedAuth = serialize(authentication); byte[] serializedToken = serialize(token.getValue()); byte[] accessKey = serializeKey(ACCESS + token.getValue()); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue()); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); byte[] relationIdTokenKey = getRelationIdTokenKey(authentication); RedisConnection conn = getConnection(); try { byte[] oldAccessToken = conn.get(accessKey); //如果token已存在,并且不是续签的话直接返回 if (!isRenew && oldAccessToken != null) { return; } conn.openPipeline(); if (springDataRedis_2_0) { try { this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); if(relationIdTokenKey!=null){ this.redisConnectionSet_2_0.invoke(conn, relationIdTokenKey, serializedToken); } } catch (Exception ex) { throw new RuntimeException(ex); } } else { conn.set(accessKey, serializedAccessToken); conn.set(authKey, serializedAuth); conn.set(authToAccessKey, serializedAccessToken); if(relationIdTokenKey!=null){ conn.set(relationIdTokenKey, serializedToken); } } //如果是续签token,需要先删除集合里旧的值 if (oldAccessToken != null) { if (!authentication.isClientOnly()) { conn.lRem(approvalKey, 1, oldAccessToken); } conn.lRem(clientId, 1, oldAccessToken); } if (!authentication.isClientOnly()) { conn.rPush(approvalKey, serializedAccessToken); } conn.rPush(clientId, serializedAccessToken); if (token.getExpiration() != null) { int seconds = token.getExpiresIn(); conn.expire(accessKey, seconds); conn.expire(authKey, seconds); conn.expire(authToAccessKey, seconds); conn.expire(clientId, seconds); conn.expire(approvalKey, seconds); } OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null && refreshToken.getValue() != null) { byte[] refresh = serialize(token.getRefreshToken().getValue()); byte[] auth = serialize(token.getValue()); byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue()); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue()); if (springDataRedis_2_0) { try { this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth); this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh); } catch (Exception ex) { throw new RuntimeException(ex); } } else { conn.set(refreshToAccessKey, auth); conn.set(accessToRefreshKey, refresh); } expireRefreshToken(refreshToken, conn, refreshToAccessKey, accessToRefreshKey); } conn.closePipeline(); } finally { conn.close(); } } private static String getApprovalKey(OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication() == null ? "" : authentication.getUserAuthentication().getName(); return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); } private static String getApprovalKey(String clientId, String userName) { return clientId + (userName == null ? "" : ":" + userName); } @Override public void removeAccessToken(OAuth2AccessToken accessToken) { removeAccessToken(accessToken.getValue()); } @Override public OAuth2AccessToken readAccessToken(String tokenValue) { byte[] key = serializeKey(ACCESS + tokenValue); byte[] bytes; RedisConnection conn = getConnection(); try { bytes = conn.get(key); } finally { conn.close(); } return deserializeAccessToken(bytes); } public void removeAccessToken(String tokenValue) { byte[] accessKey = serializeKey(ACCESS + tokenValue); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); RedisConnection conn = getConnection(); try { byte[] access = conn.get(accessKey); byte[] auth = conn.get(authKey); conn.openPipeline(); conn.del(accessKey); conn.del(accessToRefreshKey); // Don't remove the refresh token - it's up to the caller to do that conn.del(authKey); conn.closePipeline(); OAuth2Authentication authentication = deserializeAuthentication(auth); if (authentication != null) { String key = authenticationKeyGenerator.extractKey(authentication); byte[] relationIdTokenKey = getRelationIdTokenKey(authentication); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); conn.openPipeline(); conn.del(authToAccessKey); conn.lRem(unameKey, 1, access); conn.lRem(clientId, 1, access); conn.del(serialize(ACCESS + key)); if(relationIdTokenKey!=null){ conn.del(relationIdTokenKey); } conn.closePipeline(); } } catch (Exception e){ e.printStackTrace(); log.error("removeAccessToken 失败:{}",e.getMessage()); }finally { conn.close(); } } @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { RedisConnection conn = getConnection(); try { byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue()); byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue()); byte[] serializedRefreshToken = serialize(refreshToken); conn.openPipeline(); if (springDataRedis_2_0) { try { this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken); this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication)); } catch (Exception ex) { throw new RuntimeException(ex); } } else { conn.set(refreshKey, serializedRefreshToken); conn.set(refreshAuthKey, serialize(authentication)); } expireRefreshToken(refreshToken, conn, refreshKey, refreshAuthKey); conn.closePipeline(); } catch (Exception e){ e.printStackTrace(); log.error("storeRefreshToken 失败:{}",e.getMessage()); }finally { conn.close(); } } private void expireRefreshToken(OAuth2RefreshToken refreshToken, RedisConnection conn, byte[] refreshKey, byte[] refreshAuthKey) { if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; Date expiration = expiringRefreshToken.getExpiration(); if (expiration != null) { int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) .intValue(); conn.expire(refreshKey, seconds); conn.expire(refreshAuthKey, seconds); } } } @Override public OAuth2RefreshToken readRefreshToken(String tokenValue) { byte[] key = serializeKey(REFRESH + tokenValue); byte[] bytes; RedisConnection conn = getConnection(); try { bytes = conn.get(key); } finally { conn.close(); } return deserializeRefreshToken(bytes); } @Override public void removeRefreshToken(OAuth2RefreshToken refreshToken) { removeRefreshToken(refreshToken.getValue()); } public void removeRefreshToken(String tokenValue) { RedisConnection conn = getConnection(); try { byte[] refreshKey = serializeKey(REFRESH + tokenValue); byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue); byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue); byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); conn.openPipeline(); conn.del(refreshKey); conn.del(refreshAuthKey); conn.del(refresh2AccessKey); conn.del(access2RefreshKey); conn.closePipeline(); } catch (Exception e){ e.printStackTrace(); log.error("removeRefreshToken 失败:{}",e.getMessage()); }finally { conn.close(); } } @Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { removeAccessTokenUsingRefreshToken(refreshToken.getValue()); } private void removeAccessTokenUsingRefreshToken(String refreshToken) { byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken); RedisConnection conn = getConnection(); byte[] bytes = null; try { bytes = conn.get(key); conn.del(key); } finally { conn.close(); } if (bytes == null) { return; } String accessToken = deserializeString(bytes); if (accessToken != null) { removeAccessToken(accessToken); } } @Override public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) { byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); List<byte[]> byteList; RedisConnection conn = getConnection(); try { byteList = conn.lRange(approvalKey, 0, -1); } finally { conn.close(); } return getTokenCollections(byteList); } @Override public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) { byte[] key = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + clientId); List<byte[]> byteList; RedisConnection conn = getConnection(); try { byteList = conn.lRange(key, 0, -1); } finally { conn.close(); } return getTokenCollections(byteList); } private Collection<OAuth2AccessToken> getTokenCollections(List<byte[]> byteList) { if (byteList == null || byteList.size() == 0) { return Collections.emptySet(); } List<OAuth2AccessToken> accessTokens = new ArrayList<>(byteList.size()); for (byte[] bytes : byteList) { OAuth2AccessToken accessToken = deserializeAccessToken(bytes); accessTokens.add(accessToken); } return Collections.unmodifiableCollection(accessTokens); } }
五、token续期
方式:后端自动续期,APP前端不用处理。
在gateway服务中,每次校验token时,如果token离过期时间小于24小时,则自动续期。
5.1 gateway服务代码
public class CustomAuthenticationManager implements ReactiveAuthenticationManager { private TokenStore tokenStore; public CustomAuthenticationManager(TokenStore tokenStore) { this.tokenStore = tokenStore; } @Override public Mono<Authentication> authenticate(Authentication authentication) { return Mono.justOrEmpty(authentication) .filter(a -> a instanceof BearerTokenAuthenticationToken) .cast(BearerTokenAuthenticationToken.class) .map(BearerTokenAuthenticationToken::getToken) .flatMap((accessTokenValue -> { OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); if (accessToken == null) { throw new AlanChenException(ResultCode.UNAUTHORIZED,"登录状态失效"); } else if (accessToken.isExpired()) { throw new AlanChenException(ResultCode.UNAUTHORIZED,"登录状态失效"); } else { OAuth2RefreshToken refreshToken= tokenStore.readRefreshToken(accessToken.getRefreshToken().getValue()); if(null == refreshToken) { throw new AlanChenException(ResultCode.JWT_OFFLINE,"账号在其他设备登录了"); } } // token续期代码在readAuthentication方法里 OAuth2Authentication result = tokenStore.readAuthentication(accessToken); if (result == null) { throw new AlanChenException(ResultCode.FORBIDDEN,"没有权限"); } return Mono.just(result); })) .cast(Authentication.class); } }
注意:gateway这里取token不是直接用的App前端传过来的token,而是通过Authentication来取的,这有这样后端token续期了才会有效。
5.2 CustomRedisTokenStore代码
@Slf4j public class CustomRedisTokenStore implements TokenStore { //其他代码省略 @Override public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { OAuth2Authentication auth2Authentication = readAuthentication(token.getValue()); if (auth2Authentication != null) { //获取过期时长 int validitySeconds = 2592000; //2592000 int expiresRatio = validitySeconds - token.getExpiresIn(); //判断是否需要续签,当前剩余时间小于过期时长的24小时则续签 if (expiresRatio >= 24 * 3600) { //更新AccessToken过期时间 DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token; Date expiration = new Date(System.currentTimeMillis() + (validitySeconds * 1000L)); oAuth2AccessToken.setExpiration(expiration); storeAccessToken(oAuth2AccessToken, auth2Authentication, true); OAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(token.getRefreshToken().getValue(), expiration); storeRefreshToken(refreshToken, auth2Authentication); } } return auth2Authentication; } }
以上就是OAuth2生成token代码备忘实现过程示例的详细内容,更多关于OAuth2生成token代码备忘的资料请关注脚本之家其它相关文章!
相关文章
IDEA快速搭建spring boot项目教程(Spring initializr)
这篇文章主要介绍了IDEA快速搭建spring boot项目教程(Spring initializr),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2022-01-01RocketMQ Broker实现高可用高并发的消息中转服务
RocketMQ消息代理(Broker)是一种高可用、高并发的消息中转服务,能够接收并存储生产者发送的消息,并将消息发送给消费者。它具有多种消息存储模式和消息传递模式,支持水平扩展和故障转移等特性,可以为分布式应用提供可靠的消息传递服务2023-04-04详解spring-boot actuator(监控)配置和使用
本篇文章主要介绍了spring-boot actuator(监控)配置和使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-09-09java.io.IOException:你的主机中的软件中止了一个已建立的连接踩坑实战
最近在工作中遇到了个问题,分享给同样遇到问题的同学,这篇文章主要给大家介绍了关于java.io.IOException:你的主机中的软件中止了一个已建立的连接的踩坑实战记录,需要的朋友可以参考下2023-03-03
最新评论