Spring Security OAuth2实现多用户类型认证、刷新Token
原本的OAuth2登录支持用户名密码登录,现在还想支持另外用id号码和密码登录。但是OAuth2默认提供的UserDetailsService只允许传入一个参数:
想要实现多种用户登录,是不是可以考虑loadUserByUsername方法携带多个参数呢?
接下来记录一下实现步骤:
新增interface CustomUserDetailsServiceInter 继承原来的UserDetailsService,新增自定义方法loadUserByUsername
public interface CustomUserDetailsServiceInter extends UserDetailsService {CustomUser loadUserByUsername(String var1, String var2) throws UsernameNotFoundException;}
然后根据自己需要实现CustomUserDetailsServiceInter接口的方法,这里就不放出来了
@Slf4j
@Component
public class CustomUserDetailsService implements CustomUserDetailsServiceInter {@Overridepublic CustomUser loadUserByUsername(String username, String idCard) throws UsernameNotFoundException {// 根据自己需要进行实现// 1.获取用户// 2.获取用户可访问权限信息// 3.构造UserDetails信息并返回return userDetails;}
}
从现在开始,所有需要用到userDetailsService的,全部都要替换成自定义CustomUserDetailsService。比如WebSecurityConfigurer、AuthServerConfigurer
复制org.springframework.security.authentication.dao.DaoAuthenticationProvider的代码,自定义 CustomerDaoAuthenticationProvider,然后进行修改retrieveUser()方法,其他不需要动
public class CustomerDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";private PasswordEncoder passwordEncoder;private volatile String userNotFoundEncodedPassword;private CustomUserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;public CustomerDaoAuthenticationProvider() {setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());}@Override@SuppressWarnings("deprecation")protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();String password = userDetails.getPassword();boolean matches = this.passwordEncoder.matches(presentedPassword, password);if (!matches) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}@Overrideprotected void doAfterPropertiesSet() {Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");}/*@Overrideprotected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username, authentication.getDetails());if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;} catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;} catch (InternalAuthenticationServiceException ex) {throw ex;} catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}*/@Overrideprotected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {boolean upgradeEncoding = this.userDetailsPasswordService != null&& this.passwordEncoder.upgradeEncoding(user.getPassword());if (upgradeEncoding) {String presentedPassword = authentication.getCredentials().toString();String newPassword = this.passwordEncoder.encode(presentedPassword);user = this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}private void prepareTimingAttackProtection() {if (this.userNotFoundEncodedPassword == null) {this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);}}private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials().toString();this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);}}/*** Sets the PasswordEncoder instance to be used to encode and validate passwords. If* not set, the password will be compared using* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}** @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}* types.*/public void setPasswordEncoder(PasswordEncoder passwordEncoder) {Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");this.passwordEncoder = passwordEncoder;this.userNotFoundEncodedPassword = null;}protected PasswordEncoder getPasswordEncoder() {return this.passwordEncoder;}public void setUserDetailsService(CustomUserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}protected CustomUserDetailsService getUserDetailsService() {return this.userDetailsService;}public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {this.userDetailsPasswordService = userDetailsPasswordService;}protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {this.prepareTimingAttackProtection();Map<String, String> map = (Map<String, String>) authentication.getDetails(); // 自定义添加try {String userIdCard = map.get("idCard"); // 自定义添加UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username, userIdCard);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");} else {return loadedUser;}} catch (UsernameNotFoundException var4) {this.mitigateAgainstTimingAttack(authentication);throw var4;} catch (InternalAuthenticationServiceException var5) {throw var5;} catch (Exception var6) {throw new InternalAuthenticationServiceException(var6.getMessage(), var6);}}
}
记得将自定义的CustomerDaoAuthenticationProvider中的userDetailsService替换成自定义的CustomUserDetailsService
到WebSecurityConfig配置上面的CustomAuthenticationProvider
@Bean(name="customAuthenticationProvider")public AuthenticationProvider customAuthenticationProvider() {CustomerDaoAuthenticationProvider customAuthenticationProvider= new CustomerDaoAuthenticationProvider();customAuthenticationProvider.setUserDetailsService(userDetailsService);customAuthenticationProvider.setHideUserNotFoundExceptions(false);customAuthenticationProvider.setPasswordEncoder(passwordEncoder);return customAuthenticationProvider;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(customAuthenticationProvider());}
可以去获取token试试了
获取access_token请求(/oauth/token)
请求所需参数:client_id、client_secret、grant_type、username、password 这些是默认的,可以用需要的idCard替换username
现在去请求刷新Token
刷新token请求(/oauth/token)
请求所需参数:grant_type、refresh_token、client_id、client_secret其中grant_type为固定值:grant_type=refresh_token
会发现怎样也刷新不了,因为刷新Token时用的还是原版的UserDetailsService,执行的还是单个参数的loadUserByUsername,所以下面要重新自定义相关类来替换掉
复制org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper 自定义 CustomUserDetailsByNameServiceWrapper 在 loadUserDetails() 方法添加自定义的idCard
public class CustomUserDetailsByNameServiceWrapper<T extends Authentication> implementsAuthenticationUserDetailsService<T>, InitializingBean {private CustomUserDetailsService userDetailsService = null;/*** Constructs an empty wrapper for compatibility with Spring Security 2.0.x's method* of using a setter.*/public CustomUserDetailsByNameServiceWrapper() {// constructor for backwards compatibility with 2.0}/*** Constructs a new wrapper using the supplied* {@link org.springframework.security.core.userdetails.UserDetailsService} as the* service to delegate to.** @param userDetailsService the UserDetailsService to delegate to.*/public CustomUserDetailsByNameServiceWrapper(final CustomUserDetailsService userDetailsService) {Assert.notNull(userDetailsService, "userDetailsService cannot be null.");this.userDetailsService = userDetailsService;}/*** Check whether all required properties have been set.** @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()*/public void afterPropertiesSet() {Assert.notNull(this.userDetailsService, "UserDetailsService must be set");}/*** Get the UserDetails object from the wrapped UserDetailsService implementation*/public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {// ----------添加自定义的内容----------AbstractAuthenticationToken principal = (AbstractAuthenticationToken) authentication.getPrincipal();Map<String,String> map = (Map<String, String>) principal.getDetails();String userIdCard = map.get("idCard");// ----------添加自定义的内容----------return this.userDetailsService.loadUserByUsername(authentication.getName(), userIdCard); // 使用自定义的userDetailsService}/*** Set the wrapped UserDetailsService implementation** @param aUserDetailsService The wrapped UserDetailsService to set*/public void setUserDetailsService(CustomUserDetailsService aUserDetailsService) {this.userDetailsService = aUserDetailsService;}
}
复制 org.springframework.security.oauth2.provider.token.DefaultTokenServices 到自定义的 CustomTokenServices 然后修改refreshAccessToken() 方法的if (this.authenticationManager != null && !authentication.isClientOnly()) 这里面的内容
public class CustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,ConsumerTokenServices, InitializingBean {private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.private boolean supportRefreshToken = false;private boolean reuseRefreshToken = true;private TokenStore tokenStore;private ClientDetailsService clientDetailsService;private TokenEnhancer accessTokenEnhancer;private AuthenticationManager authenticationManager;/*** Initialize these token services. If no random generator is set, one will be created.*/public void afterPropertiesSet() throws Exception {Assert.notNull(tokenStore, "tokenStore must be set");}@Transactionalpublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {if (existingAccessToken.isExpired()) {if (existingAccessToken.getRefreshToken() != null) {refreshToken = existingAccessToken.getRefreshToken();// The token store could remove the refresh token when the// access token is removed, but we want to// be sure...tokenStore.removeRefreshToken(refreshToken);}tokenStore.removeAccessToken(existingAccessToken);}else {// Re-store the access token in case the authentication has changedtokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}}// Only create a new refresh token if there wasn't an existing one// associated with an expired access token.// Clients might be holding existing refresh tokens, so we re-use it in// the case that the old access token// expired.if (refreshToken == null) {refreshToken = createRefreshToken(authentication);}// But the refresh token itself might need to be re-issued if it has// expired.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);// In case it was modifiedrefreshToken = accessToken.getRefreshToken();if (refreshToken != null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)throws AuthenticationException {if (!supportRefreshToken) {throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);}OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);if (refreshToken == null) {throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);}OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);if (this.authenticationManager != null && !authentication.isClientOnly()) {/*// The client has already been authenticated, but the user authentication might be old now, so give it a// chance to re-authenticate.Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());user = authenticationManager.authenticate(user);Object details = authentication.getDetails();authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);authentication.setDetails(details);*/// OAuth2Authentication 中的 Authentication userAuthentication 丢失了 Detail的信息,需要补上// 1.从tokenRequest中获取请求的信息,并重新构造成 UsernamePasswordAuthenticationToken// 2.设置好了Detail的信息再传入构造 PreAuthenticatedAuthenticationToken 交由后面的验证//tokenRequest.getRequestParameters();Object details = tokenRequest.getRequestParameters();UsernamePasswordAuthenticationToken userAuthentication = (UsernamePasswordAuthenticationToken) authentication.getUserAuthentication();userAuthentication.setDetails(details);// 去掉原来的,使用自己重新构造的 userAuthentication
// Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());Authentication user = new PreAuthenticatedAuthenticationToken(userAuthentication, "", authentication.getAuthorities());user = this.authenticationManager.authenticate(user);authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);authentication.setDetails(details);}String clientId = authentication.getOAuth2Request().getClientId();if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);}// clear out any access tokens already associated with the refresh// token.tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);if (isExpired(refreshToken)) {tokenStore.removeRefreshToken(refreshToken);throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);}authentication = createRefreshedAuthentication(authentication, tokenRequest);if (!reuseRefreshToken) {tokenStore.removeRefreshToken(refreshToken);refreshToken = createRefreshToken(authentication);}OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);if (!reuseRefreshToken) {tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);}return accessToken;}public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {return tokenStore.getAccessToken(authentication);}/*** Create a refreshed authentication.** @param authentication The authentication.* @param request The scope for the refreshed token.* @return The refreshed authentication.* @throws InvalidScopeException If the scope requested is invalid or wider than the original scope.*/private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {OAuth2Authentication narrowed = authentication;Set<String> scope = request.getScope();OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);if (scope != null && !scope.isEmpty()) {Set<String> originalScope = clientAuth.getScope();if (originalScope == null || !originalScope.containsAll(scope)) {throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope+ ".", originalScope);}else {clientAuth = clientAuth.narrowScope(scope);}}narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());return narrowed;}protected boolean isExpired(OAuth2RefreshToken refreshToken) {if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;return expiringToken.getExpiration() == null|| System.currentTimeMillis() > expiringToken.getExpiration().getTime();}return false;}public OAuth2AccessToken readAccessToken(String accessToken) {return tokenStore.readAccessToken(accessToken);}public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,InvalidTokenException {OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);if (accessToken == null) {throw new InvalidTokenException("Invalid access token: " + accessTokenValue);}else if (accessToken.isExpired()) {tokenStore.removeAccessToken(accessToken);throw new InvalidTokenException("Access token expired: " + accessTokenValue);}OAuth2Authentication result = tokenStore.readAuthentication(accessToken);if (result == null) {// in case of race conditionthrow new InvalidTokenException("Invalid access token: " + accessTokenValue);}if (clientDetailsService != null) {String clientId = result.getOAuth2Request().getClientId();try {clientDetailsService.loadClientByClientId(clientId);}catch (ClientRegistrationException e) {throw new InvalidTokenException("Client not valid: " + clientId, e);}}return result;}public String getClientId(String tokenValue) {OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue);if (authentication == null) {throw new InvalidTokenException("Invalid access token: " + tokenValue);}OAuth2Request clientAuth = authentication.getOAuth2Request();if (clientAuth == null) {throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);}return clientAuth.getClientId();}public boolean revokeToken(String tokenValue) {OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);if (accessToken == null) {return false;}if (accessToken.getRefreshToken() != null) {tokenStore.removeRefreshToken(accessToken.getRefreshToken());}tokenStore.removeAccessToken(accessToken);return true;}private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {if (!isSupportRefreshToken(authentication.getOAuth2Request())) {return null;}int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());String value = UUID.randomUUID().toString();if (validitySeconds > 0) {return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()+ (validitySeconds * 1000L)));}return new DefaultOAuth2RefreshToken(value);}private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());if (validitySeconds > 0) {token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));}token.setRefreshToken(refreshToken);token.setScope(authentication.getOAuth2Request().getScope());return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;}/*** The access token validity period in seconds** @param clientAuth the current authorization request* @return the access token validity period in seconds*/protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {if (clientDetailsService != null) {ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());Integer validity = client.getAccessTokenValiditySeconds();if (validity != null) {return validity;}}return accessTokenValiditySeconds;}/*** The refresh token validity period in seconds** @param clientAuth the current authorization request* @return the refresh token validity period in seconds*/protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {if (clientDetailsService != null) {ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());Integer validity = client.getRefreshTokenValiditySeconds();if (validity != null) {return validity;}}return refreshTokenValiditySeconds;}/*** Is a refresh token supported for this client (or the global setting if* {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.** @param clientAuth the current authorization request* @return boolean to indicate if refresh token is supported*/protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {if (clientDetailsService != null) {ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());return client.getAuthorizedGrantTypes().contains("refresh_token");}return this.supportRefreshToken;}/*** An access token enhancer that will be applied to a new token before it is saved in the token store.** @param accessTokenEnhancer the access token enhancer to set*/public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {this.accessTokenEnhancer = accessTokenEnhancer;}/*** The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be* non-expiring.** @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.*/public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;}/*** The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client* details service is set the validity period will be read from the client, defaulting to this value if not defined* by the client.** @param accessTokenValiditySeconds The validity (in seconds) of the access token.*/public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {this.accessTokenValiditySeconds = accessTokenValiditySeconds;}/*** Whether to support the refresh token.** @param supportRefreshToken Whether to support the refresh token.*/public void setSupportRefreshToken(boolean supportRefreshToken) {this.supportRefreshToken = supportRefreshToken;}/*** Whether to reuse refresh tokens (until expired).** @param reuseRefreshToken Whether to reuse refresh tokens (until expired).*/public void setReuseRefreshToken(boolean reuseRefreshToken) {this.reuseRefreshToken = reuseRefreshToken;}/*** The persistence strategy for token storage.** @param tokenStore the store for access and refresh tokens.*/public void setTokenStore(TokenStore tokenStore) {this.tokenStore = tokenStore;}/*** An authentication manager that will be used (if provided) to check the user authentication when a token is* refreshed.** @param authenticationManager the authenticationManager to set*/public void setAuthenticationManager(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;}/*** The client details service to use for looking up clients (if necessary). Optional if the access token expiry is* set globally via {@link #setAccessTokenValiditySeconds(int)}.** @param clientDetailsService the client details service*/public void setClientDetailsService(ClientDetailsService clientDetailsService) {this.clientDetailsService = clientDetailsService;}}
到认证服务器配置类 AuthorizationServerConfig 配置刚刚自定义的两个类 CustomUserDetailsByNameServiceWrapper 和 CustomTokenServices
@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.tokenStore(jwtTokenStore()) // 根据自己需要.tokenEnhancer(jwtAccessTokenConverter()).reuseRefreshTokens(true).authenticationManager(authenticationManager).userDetailsService(userDetailsService).tokenServices(customTokenServices(endpoints)); // 自定义TokenServices}public CustomTokenServices customTokenServices(AuthorizationServerEndpointsConfigurer endpoints){CustomTokenServices tokenServices = new CustomTokenServices();tokenServices.setTokenStore(endpoints.getTokenStore());tokenServices.setSupportRefreshToken(true); tokenServices.setReuseRefreshToken(true);tokenServices.setClientDetailsService(clientDetails());tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());// 设置自定义的CustomUserDetailsByNameServiceWrapperif (userDetailsService != null) {PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();provider.setPreAuthenticatedUserDetailsService(new CustomUserDetailsByNameServiceWrapper(userDetailsService));tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));}return tokenServices;}
至此,可以刷新token试试了。
在获取Token的时候,像client_id,client_secret等默认的请求参数,框架会自动放到details中,但是在刷新token的时候不知什么原因,details中并没有放入请求的参数,所以需要自己重新构造,还好这些信息都保存在tokenRequest中,所以new一个UsernamePasswordAuthenticationToken,把它们都放进details中,再传入 PreAuthenticatedAuthenticationToken,后续就交由框架去验证就可以了 。这一步操作就是CustomTokenServices类refreshAccessToken方法
相关文章:
Spring Security OAuth2实现多用户类型认证、刷新Token
原本的OAuth2登录支持用户名密码登录,现在还想支持另外用id号码和密码登录。但是OAuth2默认提供的UserDetailsService只允许传入一个参数:想要实现多种用户登录,是不是可以考虑loadUserByUsername方法携带多个参数呢?接下来记录一…...
云计算介绍,让你更了解云计算
同学们好! 第一次接触IT行业吗?没关系,看完这篇文章肯定会让你不再陌生。给自己几分钟时间,认真看完哦! 1、不知道什么是云计算? 网络计算云计算 官方定义是:通过网络提供可伸缩的分布式计算…...
阿里大佬翻遍全网Java面试文章,总结出这份1658页文档,GitHub收获25K+点赞
就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。这不又到了面试跳槽的黄金段,成功升职加薪,不成功饱受打击。当然也要注意&…...
【JDK1.8 新特性】Lambda表达式
1. 什么是Lambda表达式? Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达…...
【Vue.js】Vuex核心概念
文章目录全局状态管理模式Vuexvuex是什么?什么是“状态管理模式”?vuex的应用场景Vuex安装开始核心概念一、State1、单一状态树2、在 Vue 组件中获得 Vuex 状态3、mapState辅助函数二、Getter三、Mutation1、提交载荷(Payload)2、…...
react router零基础使用教程
安装既然学习 react router 就免不了运行 react安装 reactnpx create-react-app my-appcd my-appnpm start安装 react routernpm install react-router-dom如果一切正常,就让我们打开 index.js 文件。配置路由引入 react-router-dom 的 RouterProviderimport {Route…...
IOC三种依赖注入的方式,以及区别
目录构造方法注入(constructor injection)setter 方法注入(setter injection)接口注入(interface injection)三种方式比较构造方法注入(constructor injection) 构造方法中声明依赖…...
Ubuntu18安装新版本PCL-1.13,并和ROS自带PCL-1.8共存
文章目录1.安装新版本PCL-1.132.在工程中使用新版本的PCL-1.133.pcl-1.13误装到/usr/local/下如何卸载1.安装新版本PCL-1.13 下载PCL-1.13代码: 修改CMakeLists.txt文件,不编译vtk相关的代码:vtk的问题比较难解决,但是一般我们安…...
计算机图形学08:中点BH算法绘制抛物线(100x = y^2)
作者:非妃是公主 专栏:《计算机图形学》 博客地址:https://blog.csdn.net/myf_666 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、算法原理二、…...
基于java的高校辅导员工作管理系统
摘 要网络技术的快速发展给各行各业带来了很大的突破,也给各行各业提供了一种新的管理模块,对于高校辅导员工作管理将是又一个传统管理到智能化信息管理的改革,设计高校辅导员工作管理系统的目的就是借助计算机让复杂的班级信息、学籍信息等管…...
字节3次都没裁掉的7年老测试。掌握设计业务与技术方案,打开上升通道!
前言职场中的那些魔幻操作,研发最烦的是哪个?“面对业务需求的时候,可能都听过这样一句话:这个很简单,直接开发,三天内上线;”朋友说:“产品听了流泪,测试见了崩溃&#…...
详细介绍关于链表【数据结构】
文章目录链表单链表尾插头插尾删第一种方式删除第二种头删查找pos之前插入pos位置删除pos后面插入pos位置后面删除链表 顺序表缺点: 空间不够了 需要扩容,但是扩容是有消耗的头部或中间位置需要插入或删除,需要挪动 ,但是挪动是…...
2.3 二分搜索技术
二分搜索算法是运用分治策略的典型例子。给定己排好府的 n个元素a10:n-1],现要在这n个元素中找出一特定元素3。首先较容易想到的是用顺序搜索方法,逐个比较a10:1-1]中元素,直至找出元素,或搜索遍整个数组后确定,不在其…...
RWEQ模型的土壤风蚀模数估算、其变化归因分析
土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一,土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2,占国土总面积的16.7%,严重影响这些地区的资源开发和社会经…...
学习streamlit-1
Streamlit A faster way to build and share data apps streamlit在几分钟内就可以将数据脚本转换为可共享的web应用程序,并且是纯python编程,无需前端经验。 快速开始 streamlit非常容易上手,运行demo只需2行代码: pip install…...
GPS定位知识介绍
GPS定位和网络定位 GPS定位需要卫星参与,设备借助搜到的卫星讯号,计算出设备的位置。网络定位是指利用基站、WIFI MAC,获取一个粗略的位置。3D定位和2D 定位 3D一般是指使用至少4颗以上卫星完成的定位。2D一般使用3颗卫星完成的定位过程。...
【Linux】理解Linux环境变量
🍎作者:阿润菜菜 📖专栏:Linux系统编程 初识环境变量 什么是环境变量 我们平常所用的Linux指令其实也是可执行程序,和我们自己写的二进制程序没什么两样,那么为什么我们在执行自己的程序的时候需要加上 ./…...
ISCSI块存储-集群
ISCSI块存储-集群 1、ISCSI概述 ISCSI与SCSI原理对比 底层都是硬盘 ISCSI第二层就是通过gateway/网络获取–>SCSI:是直连获取 常见的用于ISCSI服务的网络拓扑类型 SAN:Storage Area Network:存储区域网络; 多采用告诉光纤…...
11.Maxwell 部署
Maxwell 部署 一、环境准备 1、确保服务器上已经安装好了zookeeper、kafka、MySQL软件; (1)启动zookeeper: /usr/app/zookeeper3.4/bin/zkServer.sh start(2)启动三台主题的kafka 启动:bin/kafka-serv…...
一文速学-GBDT模型算法原理以及实现+Python项目实战
目录 前言 一、GBDT算法概述 1.决策树 2.Boosting 3.梯度提升 使用梯度上升找到最佳参数 二、GBDT算法原理 1.计算原理 2.预测原理 三、实例算法实现 1.模型训练阶段 1)初始化弱学习器 2)对于建立M棵分类回归树: 四、Python实现 …...
前端——2.HTML基本结构标签
这篇文章我们从0来介绍一下HTML的相关标签内容 目录 1.HTML语法规范 1.1基本语法概述 1.2标签关系 2.HTML的基本结构标签 2.1第一个HTML网页 2.2基本结构标签总结 1.HTML语法规范 下面,我们来看一下HTML的语法规范的内容 1.1基本语法概述 首先,…...
OAK深度相机使用不同镜头和本地视频流进行模型推理
编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多👍⭐️✍ 内容可能会不定期更新,官网内容都是最新的,请查看首发地址链接。 ▌前言 Hello,大家好,这里是OAK中国,我是助手…...
[项目] Boost搜索引擎
目录 1.项目相关背景 2.项目宏观原理 3.技术栈和项目环境 4.正排索引&&倒排索引 5.去标签与数据清洗 6.构建索引模块Index 6.1正排索引 6.2 建立倒排 jiebacpp使用 建立分词 7.搜索引擎模块Searcher Jsoncpp -- 通过jsoncpp进行序列化和反序列化 处理Cont…...
解决新版QGIS找不到Georeferencer插件
目录1. 问题分析1.1 去 Raster 找,没找到1.2 去插件搜,未搜到1.3 插件库里也搜不到2. 解决办法在 QGIS 3.30中,按常规办法,找不到 Georeferencer插件,它并没有被安装,在库中也找不到它, 请问问题…...
c---冒泡排序模拟qsort
一、冒泡排序 二、冒泡排序优化排各种类型数据 文章目录一、冒泡排序二、冒泡排序优化排各种类型数据冒泡排序 冒泡排序原理:两两相邻元素进行比较 初级版 void bulle_sort(int* a, int sz) {int i 0;for (int i 0; i < sz-1; i){int j 0; for (j 0; j…...
Java知识复习(十四)JS
1、数据类型 基本数据类型:null、undefinde、boolean、string、number、symbol(ES6新增)引用数据类型:Function、Array、Object、Map和Set(ES6新增) 2、let、var和const的区别 var定义的变量,…...
代码随想录刷题-数组-移除元素
文章目录写在前面习题我的想法暴力解法双指针写在前面 本节对应代码随想录中:代码随想录 习题 题目链接: 27. 移除元素- 力扣(LeetCode) 给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素&a…...
聚观早报 |拼多多跨境电商业务正式登陆澳洲;中国加快6G网络研发
今日要闻:拼多多跨境电商业务正式登陆澳洲;全球自动驾驶公司排名特斯拉垫底;中国将加快 6G 网络研发;B站再次“崩”上热搜!已闪电修复;微软将必应AI聊天每次对话上限增加至8条拼多多跨境电商业务正式登陆澳…...
MDK Keil5 创建Stm32工程-理论篇(这里以Stm32F103Zet6为例)
一、文件夹创建与文件说明整个工程可以粗略的划分为几个文件夹:BSP底层驱动比如GPIO\Timer等驱动文件CMSIS内核相关的文件Firmware生成的固件下载文件Mycode用户编写的相关文件,主要编写的文件都在这个文件夹里Project工程文件startup芯片启动文件STM32F…...
应届大学生学什么技术好?哪些技术适合年轻人?
到了毕业季,应届大学生面临的就是就业问题,很多专业的大学生难以找到对口的工作,或是不得已随便就业,或者是学个技术高薪就业,那么,问题来了,应届大学生学什么技术好?哪些技术适合年…...
做网站编码/seo推广技术
1, Java的基本部分 1.1 java中int数据占几个字节 1.2 有了基本类型, 为什么还需要包装类型? 1.3 说一下""和equals方法的区别? 1.4 讲一下String和StringBuilder的区别(final)?StringBuffer和StringBuilder的区别? 1.5, 讲一下java中的集合? 1.6 Ar…...
丹东谁做微网站/深圳seo优化培训
小伙伴们,我们分享继续哦第一节进程和线程https://www.bilibili.com/video/BV137411V7Y1/?p1951.1 进程和线程程序Program是一段静态的代码,它是应用程序执行的蓝本进程Process是指一种正在运行的程序,有自己的地址空间进程的特点动态性并发…...
美容医疗 网站建设/有链接的网站
所谓常量即只能读取不能编辑(删除,修改)的变量。 js并没有原始的常量说法(即自定义的,原生态的),但是可以用一些偏僻的路子去创建。 1:const es6中的声明关键词。 上面声明了两个变量…...
wordpress插件域名限制/企业网站如何优化
原文链接:Generative Learning Algorithm in Chinese最新的版本在原文链接里可以找到。原博客会不断更新。这篇笔记主要梳理了吴恩达教授在斯坦福的CS229课程的内容,并结合了哥伦比亚大学几个教授相关笔记内容一并总结。请注意: 本文是翻译的一份学习资料…...
企业网站可以免费做吗/汕头网站建设平台
通过虚拟地址访问内存有以下优势: 1 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。 2 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常…...
网站建设包含哪些/营销型网站建设服务
(1)mall-swarm是一套微服务商城系统,采用了 Spring Cloud Hoxton & Alibaba、Spring Boot 2.3、Oauth2、MyBatis、Docker、Elasticsearch、Kubernetes等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。mall-swarm在电…...