总结项目中oauth2模块的配置流程及实际业务oauth2认证记录(Spring Security)
文章目录
- 简单示例
- 添加oauth2的依赖
- 配置认证服务器
- 配置资源服务器
- 配置安全
- 使用http或者curl命令测试
- 实际业务中工具类(记录):
- 认证服务器
- 资源服务器、配置安全
- 用户验证
- 登录控制层
- 配置文件application.yml
项目中用过的spring security,拿来温习一下,一个简单版本的笔记。有问题欢迎大佬们指正!
简单示例
添加oauth2的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.0.RELEASE</version>
</dependency>
spring-boot-starter-security是Spring Security基础依赖,spring-security-oauth2-autoconfigure是OAuth2自动配置模块
配置认证服务器
配置OAuth2认证服务器。在Spring Boot中,可以通过创建一个@Configuration类并使用@EnableAuthorizationServer注解来实现。
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("client").secret("{noop}secret") // 设置客户端的密码.authorizedGrantTypes("password", "refresh_token").scopes("read", "write").accessTokenValiditySeconds(3600)// 设置访问令牌有效期为1800秒.refreshTokenValiditySeconds(86400);// 设置刷新令牌有效期为86400秒}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager);}
}
上面代码中:创建了一个名为OAuth2Config的配置类并启用了@EnableAuthorizationServer注解,这表示我们正在创建一个OAuth2认证服务器。
通过configure(ClientDetailsServiceConfigurer clients)方法来配置OAuth2客户端详情服务,这里使用了内存存储客户端信息。configure(AuthorizationServerEndpointsConfigurer endpoints)方法用于配置OAuth2认证服务器的终端点,这里将认证管理器设置为authenticationManager
配置资源服务器
配置资源服务器,以便我们的应用程序能够保护资源并要求访问令牌进行身份验证和授权。
可以通过创建一个@Configuration类并标注@EnableResourceServer注解来实现:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/api/**").authenticated().and().csrf().disable();}
}
创建了一个名为ResourceServerConfig的配置类并启用了@EnableResourceServer注解,这表示我们正在创建一个OAuth2资源服务器。
此外,我们通过configure(HttpSecurity http)方法来配置HTTP安全性,这里我们要求任何访问/api/**的请求都必须经过身份验证才能访问。
配置安全
需要配置安全,以便我们的应用程序可以使用安全协议来保护和管理OAuth2令牌。
可以通过创建一个@Configuration类并标注@EnableWebSecurity注解来实现:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/oauth/token").permitAll().anyRequest().authenticated().and().csrf().disable();}
}
创建了一个名为SecurityConfig的配置类并启用了@EnableWebSecurity注解,这表示我们正在创建一个Spring Security安全配置。
此外,我们通过configure(AuthenticationManagerBuilder auth)方法来配置用户详细信息服务和密码编码器。configure(HttpSecurity http)方法用于配置HTTP安全性,这里我们允许任何人访问/oauth/token端点,但对所有其他请求都要求进行身份验证。
使用http或者curl命令测试
# 获取访问令牌
curl -X POST \http://localhost:8080/oauth/token \-H 'Authorization: Basic Y2xpZW50OnNlY3JldA==' \-H 'Content-Type: application/x-www-form-urlencoded' \-d 'grant_type=password&username=user&password=password'# 使用访问令牌访问受保护的资源
curl -X GET \http://localhost:8080/api/protected \-H 'Authorization: Bearer <access_token>'
上面的命令中的Authorization头部的内容是base64编码的client:secret。在实际使用中,你需要替换成你自己的客户端ID和秘钥。
这是一个简单的使用Spring Boot和OAuth2实现认证和授权的示例。
实际业务中工具类(记录):
认证服务器
package com.youming.shuiku.oauth2.config;import com.youming.shuiku.oauth2.authentication.ding.DingTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** <p>* Description:* </p>** @author wangxihao* @version v1.0.0* @ClassName AuthorizationServerConfiguration* @Date 2022/10/20 15:15*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {@Autowiredprivate BCryptPasswordEncoder passwordEncoder;/*** 注入用于支持 password 模式*/@Autowiredprivate AuthenticationManager authenticationManager;// // 第一处修改,注入 RedisConnectionFactory,用于连接 Redis@Autowiredprivate RedisConnectionFactory redisConnectionFactory;@Lazy@Resource(name = "userDetailsService")private UserDetailsService userDetailsService;@Bean@Primary@ConfigurationProperties(prefix = "spring.datasource")public DataSource dataSource() {// 配置数据源(注意,我使用的是 HikariCP 连接池),以上注解是指定数据源,否则会有冲突return DataSourceBuilder.create().build();}@Beanpublic TokenStore tokenStore() {// 基于 JDBC 实现,令牌保存到数据库// return new JdbcTokenStore(dataSource());return new RedisTokenStore(redisConnectionFactory);}// @Bean// @Primary// public TokenStore jdbcTokenStore(){// return new JdbcTokenStore(dataSource());// }@Beanpublic ClientDetailsService jdbcClientDetailsService() {// 基于 JDBC 实现,需要事先在数据库配置客户端信息return new JdbcClientDetailsService(dataSource());}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {// 用于支持密码模式// endpoints.authenticationManager(authenticationManager)// // 增加 TokenStore 配置// .tokenStore(tokenStore());// endpoints.userDetailsService(userDetailsService);// 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));// 添加钉钉小程序授权模式的授权者granterList.add(new DingTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory(), authenticationManager));CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);endpoints.authenticationManager(authenticationManager).tokenGranter(compositeTokenGranter).userDetailsService(userDetailsService).tokenStore(tokenStore());}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.allowFormAuthenticationForClients()// 允许客户端访问 /oauth/check_token 检查 token// .checkTokenAccess("isAuthenticated()");// .tokenKeyAccess("permitAll()")z.checkTokenAccess("permitAll()");}/*** 配置客户端* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {// 客户端配置clients.withClientDetails(jdbcClientDetailsService());/** clients // 使用内存设置 .inMemory() // client_id .withClient("client") //* client_secret .secret(passwordEncoder.encode("secret")) // 授权类型,密码模式和刷新令牌* .authorizedGrantTypes("password", "refresh_token") // 授权范围 .scopes("backend")* // 可以设置对哪些资源有访问权限,不设置则全部资源都可以访问 .resourceIds("backend-resources") //* 设置访问令牌的有效期,这里是 1 天 .accessTokenValiditySeconds(60 * 60 * 24) // 设置刷新令牌的有效期,这里是* 30 天 .refreshTokenValiditySeconds(60 * 60 * 24 * 30);*/}}
资源服务器、配置安全
package com.youming.shuiku.oauth2.config;import com.youming.shuiku.oauth2.authentication.ding.DingAuthenticationProvider;
import com.youming.shuiku.oauth2.config.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;/*** 认证服务器安全配置* <p>* Description:* </p>** @author wangxihao* @version v1.0.0* @ClassName WebSecurityConfiguration* @Date 2022/10/20 15:11*/
@Configuration
@EnableWebSecurity
// 增加了资源服务器配置
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {@Beanpublic BCryptPasswordEncoder passwordEncoder() {// 配置默认的加密方式return new BCryptPasswordEncoder();}@Bean@Overrideprotected UserDetailsServiceImpl userDetailsService() {return new UserDetailsServiceImpl();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 使用自定义认证与授权// auth.userDetailsService(userDetailsService());auth.authenticationProvider(daoAuthenticationProvider()).authenticationProvider(dingAuthenticationProvider());}/*** 用于支持 password 模式* @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/user/login").antMatchers("/user/refresh").antMatchers("/user/logins")// .antMatchers("/info").antMatchers("/logout").antMatchers("/wx/**");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/login/**").permitAll()// .and()// .authorizeRequests()// 增加了授权访问配置// .antMatchers("/user/info").hasAuthority("USER")// .antMatchers("/user/logout").hasAuthority("USER");}/*** 钉钉小程序认证授权提供者* @return*/@Beanpublic DingAuthenticationProvider dingAuthenticationProvider() {DingAuthenticationProvider provider = new DingAuthenticationProvider();provider.setUserDetailsService(userDetailsService());// provider.setWxMaService(wxMaService);// provider.setMemberFeignClient(memberFeignClient);return provider;}/*** 用户名密码认证授权提供者* @return*/@Beanpublic DaoAuthenticationProvider daoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService());provider.setPasswordEncoder(passwordEncoder());provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常;return provider;}}
用户验证
对于用户验证,你可以实现UserDetailsService接口来定义从数据库或其他数据源中获取用户信息的逻辑
package com.youming.shuiku.oauth2.config.service;import com.youming.shuiku.commons.constant.BaseConstant;
import com.youming.shuiku.commons.domain.SysUser;
import com.youming.shuiku.commons.response.ResponseCode;
import com.youming.shuiku.oauth2.service.ISysUserService;
import com.youming.shuiku.oauth2.userdetails.SysUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate ISysUserService sysUserService;// @Autowired// private IWechatUserRelService wechatUserRelService;// @Autowired// private ITbPermissionService tbPermissionService;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// SysUser sysUser = sysUserService.getByUsername(s);// List<GrantedAuthority> grantedAuthorities = Lists.newArrayList();// grantedAuthorities.add(new SimpleGrantedAuthority("USER"));//// // 用户存在// if (sysUser != null) {// return new User(sysUser.getLoginAcct(), sysUser.getPassword(),// grantedAuthorities);// }//// // 用户不存在// else {// return null;// }SysUserDetails userDetails = null;SysUser sysUser = sysUserService.getByMobile(s);if (sysUser != null) {if (sysUser.getUserType() == BaseConstant.UserType.THREE) {userDetails = new SysUserDetails(sysUser, "THREE");}else {userDetails = new SysUserDetails(sysUser, "USER");}}if (userDetails == null) {throw new UsernameNotFoundException(ResponseCode.USER_NOT_EXIST.message());}else if (!userDetails.isEnabled()) {throw new DisabledException("该账户已被禁用!");}else if (!userDetails.isAccountNonLocked()) {throw new LockedException("该账号已被锁定!");}else if (!userDetails.isAccountNonExpired()) {throw new AccountExpiredException("该账号已过期!");}return userDetails;}/*** userId 认证方式* @param userId* @return*/public UserDetails loadUserByUserId(String userId) {SysUserDetails userDetails = null;SysUser sysUser = sysUserService.getByUserId(userId);if (sysUser != null) {userDetails = new SysUserDetails(sysUser, "USER");}if (userDetails == null) {throw new UsernameNotFoundException(ResponseCode.USER_NOT_EXIST.message());}else if (!userDetails.isEnabled()) {// throw new DisabledException("该账户已被禁用!");throw new DisabledException("用户已离职!");}else if (!userDetails.isAccountNonLocked()) {throw new LockedException("该账号已被锁定!");}else if (!userDetails.isAccountNonExpired()) {throw new AccountExpiredException("该账号已过期!");}return userDetails;}}
登录控制层
package com.youming.shuiku.oauth2.controller;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.youming.shuiku.commons.base.web.Header;
import com.youming.shuiku.commons.domain.LoginInfo;
import com.youming.shuiku.commons.domain.SysUser;
import com.youming.shuiku.commons.exception.BusinessException;
import com.youming.shuiku.commons.response.ResponseCode;
import com.youming.shuiku.commons.response.ResponseResult;
import com.youming.shuiku.commons.util.RedisUtil;
import com.youming.shuiku.oauth2.Vo.LoginParam;
import com.youming.shuiku.oauth2.Vo.RefreshParam;
import com.youming.shuiku.oauth2.constant.OAuth2Constant;
import com.youming.shuiku.oauth2.dto.TokenDto;
import com.youming.shuiku.oauth2.service.ISysUserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;/*** <p>* 前端控制器* </p>** @author wangxihao* @since 2022-11-10*/
@RestController
@RequestMapping("/user")
public class LoginController {@Value("${security.oauth2.client.access-token-uri}")public String accessTokenUri;@Value("${security.oauth2.resource.token-info-uri}")public String tokenInfoUri;@Value("${security.oauth2.client.client-id}")public String clientId;@Value("${security.oauth2.client.client-secret}")public String clientSecret;@Value("${security.oauth2.dingding.client-id}")public String dingdingClientId;@Value("${security.oauth2.dingding.client-secret}")public String dingdingClientSecret;@Resourceprivate HttpServletRequest request;@ResourceISysUserService sysUserService;// @Resource// private IUserDeptService userDeptServicel;@ResourceRedisUtil redisUtil;@Resourcepublic BCryptPasswordEncoder passwordEncoder;/*** 管理员登录** @param loginParam {@code JSON} {@link LoginParam}* @return {@link ResponseResult}*/@PostMapping("login")public ResponseResult admin(@RequestBody LoginParam loginParam) {Integer platformType = loginParam.getPlatformType();if (platformType == OAuth2Constant.LoginType.DINGDIND_APPLET) {return dingdingLogin(loginParam);} else if (platformType == OAuth2Constant.LoginType.WEB) {return pcLogin(loginParam);} else if (platformType == OAuth2Constant.LoginType.THREE) {return threeLogin(loginParam);} else {return ResponseResult.failure();}}/*** 三方登录* 提供三方系统以客户端模式登录** @param loginParam {@code JSON} {@link LoginParam}* @return {@link ResponseResult}*/public ResponseResult threeLogin(LoginParam loginParam) {if (StringUtils.isEmpty(loginParam.getAppId()) || StringUtils.isEmpty(loginParam.getSecret())) {throw new BusinessException(ResponseCode.PARAM_NOT_COMPLETE);}Map<String, Object> authParam = new HashMap<>();// 三方登录客户端和密码接口回传authParam.put("client_id", loginParam.getAppId());authParam.put("client_secret", loginParam.getSecret());authParam.put("grant_type", "client_credentials");String strJson = HttpUtil.post(accessTokenUri, authParam);JSONObject jsonObject = JSONUtil.parseObj(strJson);String token = String.valueOf(jsonObject.get("access_token"));if ("null".equals(token)) {return ResponseResult.failure(String.valueOf(jsonObject.get("error_description")));}return ResponseResult.success(TokenDto.of(token, null));}/*** 钉钉小程序登录** @param loginParam* @return* @throws* @author terry* @date 2022/4/13 20:31*/private ResponseResult dingdingLogin(LoginParam loginParam) {SysUser sysUser = sysUserService.getByUserId(loginParam.getUserId());// 通过 HTTP 客户端请求登录接口Map<String, Object> authParam = getAuthParam(2);authParam.put("userId", sysUser.getUserId());authParam.put("grant_type", "dingding");return loginAccessToken(sysUser, authParam);}/*** pc端登录--** @param loginParam* @return* @throws* @author terry* @date 2022/4/13 20:30*/private ResponseResult pcLogin(LoginParam loginParam) {SysUser sysUser = sysUserService.getByMobile(loginParam.getUsername());if (sysUser == null) {throw new BusinessException(ResponseCode.USER_LOGIN_ERROR);}if (sysUser.getState() != 1) {throw new BusinessException(ResponseCode.User_DISABLED);}if(sysUser.getIsDel() == 1){throw new BusinessException(ResponseCode.USER_DELETE);}// 验证密码是否正确if (!passwordEncoder.matches(loginParam.getPassword(), sysUser.getPassword())) {throw new BusinessException(ResponseCode.USER_LOGIN_ERROR);}// 通过 HTTP 客户端请求登录接口Map<String, Object> authParam = getAuthParam(1);// 三方登录客户端和密码接口回传if (loginParam.getPlatformType() == OAuth2Constant.LoginType.THREE) {authParam.put("client_id", loginParam.getAppId());authParam.put("client_secret", loginParam.getSecret());}authParam.put("username", loginParam.getUsername());authParam.put("password", loginParam.getPassword());authParam.put("grant_type", "password");// 获取accessTokenreturn loginAccessToken(sysUser, authParam);}/*** 登录系统获取token** @param sysUser* @param authParam* @return* @throws* @author terry* @date 2022/4/13 21:25*/private ResponseResult loginAccessToken(SysUser sysUser, Map<String, Object> authParam) {String strJson = HttpUtil.post(accessTokenUri, authParam);JSONObject jsonObject = JSONUtil.parseObj(strJson);String token = String.valueOf(jsonObject.get("access_token"));String refresh = String.valueOf(jsonObject.get("refresh_token"));if ("null".equals(token)) {return ResponseResult.failure(String.valueOf(jsonObject.get("error_description")));}sysUser.setPassword("");
// redisUtil.setex("user:" + token, sysUser, 86400);LoginInfo loginInfo = new LoginInfo();BeanUtil.copyProperties(sysUser, loginInfo);redisUtil.setex("loginInfo:" + token, loginInfo, 86400);return ResponseResult.success(TokenDto.of(token, refresh));}/*** 刷新令牌** @return {@link ResponseResult}*/@PostMapping("refresh")public ResponseResult refresh(@RequestBody RefreshParam refreshParam) {String accessToken = Header.getAuthorization(request.getHeader("Authorization"));Map<String, Object> authParam = getAuthParam(1);authParam.put("grant_type", "refresh_token");authParam.put("refresh_token",refreshParam.getRefresh());String strJson = HttpUtil.post(accessTokenUri, authParam);JSONObject jsonObject = JSONUtil.parseObj(strJson);String token = String.valueOf(jsonObject.get("access_token"));String refresh = String.valueOf(jsonObject.get("refresh_token"));if ("null".equals(token)) {return ResponseResult.failure(String.valueOf(jsonObject.get("error_description")));}
// SysUser sysUser = (SysUser) redisUtil.get("user:" + accessToken);redisUtil.del("user:" + accessToken);
// redisUtil.setex("user:" + token, sysUser, 86400);LoginInfo loginInfo = (LoginInfo) redisUtil.get("loginInfo:" + accessToken);redisUtil.del("loginInfo:" + accessToken);redisUtil.setex("loginInfo:" + token, loginInfo, 86400);return ResponseResult.success(TokenDto.of(token, refresh));/** // AccessToken不存在直接返回null String refreshToken =* refreshTokenMap.get(accessToken); if (StrUtil.isBlank(refreshToken)) { throw* new BusinessException(ResponseCode.USER_NOT_LOGGED_IN); } // 通过HTTP 客户端请求刷新接口* // 通过http 客户端请求登录接口 Map<String, Object> authParam = getAuthParam();* authParam.put("grant_type", "refresh_token"); authParam.put("refresh_token",* refreshToken);** // 获取accessToken String strJson = HttpUtil.post(accessTokenUri, authParam);* JSONObject jsonObject = JSONUtil.parseObj(strJson); String token =* String.valueOf(jsonObject.get("access_token")); String refresh =* String.valueOf(jsonObject.get("refresh_token")); if (StrUtil.isNotBlank(token)* && StrUtil.isNotBlank(refresh)) { // 删除旧Token* refreshTokenMap.remove(accessToken); // 将refresh_Token保存到服务端* refreshTokenMap.put(token, refresh); result.put("token", token);* ResponseResult.success(token); }*/}/*** 刷新令牌** @return {@link ResponseResult}*//** @GetMapping("info")** @MyLog(value = "获取用户信息") // 这里添加了AOP的自定义注解 public ResponseResult info() { //* 获取accessToken String userName =* SecurityContextHolder.getContext().getAuthentication().getName();** SysUser sysUser = sysUserService.getByUsername(userName); UserInfoDto dto = new* UserInfoDto(); BeanUtils.copyProperties(sysUser, dto); return* ResponseResult.success(dto); }*/// 私有方法-----------------------------------private Map<String, Object> getAuthParam(int type) {Map<String, Object> authParam = new HashMap<>();if (type == 1) {authParam.put("client_id", clientId);authParam.put("client_secret", clientSecret);} else if (type == 2) {authParam.put("client_id", dingdingClientId);authParam.put("client_secret", dingdingClientSecret);}return authParam;}@Resourcepublic TokenStore tokenStore;@PostMapping("logout")private ResponseResult logout(@RequestParam(value = "exit", defaultValue = "false") Boolean isExit) {String token = Header.getAuthorization(request.getHeader("Authorization"));// 删除token以注销OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);if (null != oAuth2AccessToken) {if (isExit) {tokenStore.removeAccessToken(oAuth2AccessToken);}return ResponseResult.success();}return ResponseResult.failure(ResponseCode.INTERFACE_ADDRESS_INVALID);}}
配置文件application.yml
base:config:oauth:hostname: 192.168.xx.xxport: xxxxnacos:hostname: 192.168.xx.xxport: xxxxtidb:hostname: 192.168.xx.xxport: xxxxredis:hostname: 192.168.xx.xxport: xxxxpassword: xxxxxxxxxxx
# mongodb:
# hostname: 192.168.xx.xxport: xxxxspring:application:name: oauth2main:allow-bean-definition-overriding: truejackson:time-zone: GMT+8date-format: yyyy-MM-dd HH:mm:sscloud:nacos:discovery:server-addr: ${base.config.nacos.hostname}:${base.config.nacos.port}group: SHUIKU_GROUP
# namespace: 28ac7d69-8afd-474d-924d-28a291330188datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://${base.config.tidb.hostname}:${base.config.tidb.port}/shuiku_base?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: xxxxxxxxxhikari:minimum-idle: 5idle-timeout: 600000maximum-pool-size: 10auto-commit: truepool-name: MyHikariCPmax-lifetime: 1800000connection-timeout: 30000connection-test-query: SELECT 1redis:# 你 Redis 主机地址host: ${base.config.redis.hostname}# 你 Redis 主机端口port: ${base.config.redis.port}# Redis服务器连接密码(默认为空)password: ${base.config.redis.password}# 我们使用 Lettuce 客户端,比 Jedis 更高效lettuce:# 连接池配置pool:# 连接池中的最小空闲连接,默认 0min-idle: 0# 连接池中的最大空闲连接,默认 8max-idle: 8# 连接池最大阻塞等待时间(使用负值表示没有限制),默认 -1msmax-wait: -1ms# 连接池最大连接数(使用负值表示没有限制),默认 8max-active: 8server:port: 9002#logback
logging:level:com.youming.youche.oauth2: info#将日志输出到文件config: classpath:oauth2-log.xmlsecurity:oauth2:client:client-id: oauthclient-secret: oauthaccess-token-uri: http://localhost:${server.port}/oauth/tokenuser-authorization-uri: http://localhost:${server.port}/oauth/authorizeresource:token-info-uri: http://localhost:${server.port}/oauth/check_tokenauthorization:check-token-access: http://localhost:${server.port}/oauth/check_tokendingding:client-id: dingdingclient-secret: dingding相关文章:
总结项目中oauth2模块的配置流程及实际业务oauth2认证记录(Spring Security)
文章目录 简单示例添加oauth2的依赖配置认证服务器配置资源服务器配置安全使用http或者curl命令测试 实际业务中工具类(记录):认证服务器资源服务器、配置安全用户验证登录控制层配置文件application.yml 项目中用过的spring security&#x…...
传感器原理与应用复习
测量与误差 传感器原理与应用复习—测量概述与测量误差 传感器特性与应变式传感器 传感器原理与应用复习–传感器基本特性与应变式传感器 电感式传感器 传感器原理与应用复习–电感式传感器 电容式与电压式传感器 传感器原理与应用复习–电容式与压电式传感器 电磁式与…...
蓝桥杯python比赛历届真题99道经典练习题 (8-12)
【程序8】 题目:输出9*9口诀。 1.程序分析:分行与列考虑,共9行9列,i控制行,j控制列。 2.程序源代码: #include "stdio.h" main() {int i,j,result;printf("\n");for (i=1;i<10;i++){ for(j=1;j<10;j++){result=i*j;printf("%d*%d=%-3…...
八个理由:从java8升级到Java17
目录 前言 1. 局部变量类型推断 2.switch表达式 3.文本块 4.Records 5.模式匹配instanceof 6. 密封类 7. HttpClient 8.性能和内存管理能力提高 前言 从Java 8 到 Java 20,Java 已经走过了漫长的道路,自 Java 8 以来,Java 生态系统…...
使用poi将pptx文件转为图片详解
目录 项目需求 后端接口实现 1、引入poi依赖 2、代码编写 1、controller 2、service层 测试出现的bug 小结 项目需求 前端需要上传pptx文件,后端保存为图片,并将图片地址保存数据库,最后大屏展示时显示之前上传的pptx的图片。需求看上…...
【微服务】springboot整合skywalking使用详解
目录 一、前言 二、SkyWalking介绍 2.1 SkyWalking是什么 2.2 SkyWalking核心功能 2.3 SkyWalking整体架构 2.4 SkyWalking主要工作流程 三、为什么选择SkyWalking 3.1 业务背景 3.2 常见监控工具对比 3.3 为什么选择SkyWalking 3.3.1 代码侵入性极低 3.3.2 功能丰…...
electron——查看electron的版本(代码片段)
electron——查看electron的版本(代码片段)1.使用命令行: npm ls electron 操作如下: 2.在软件内使用代码,如下: console.log(process) console.log(process.versions.electron) process 里包含很多信息: process详…...
【Electron】富文本编辑器之文本粘贴
由于这个问题导致,从其他地方复制来的内容 粘贴发送之后都会多一个 换行 在发送的时候如果直接,发送innerHTML 就 可以解决 Electron h5 Andriod 都没问题,但是 公司的 IOS 端 不支持,且不提供支持(做不了。ÿ…...
【哈希数组】697. 数组的度
697. 数组的度 解题思路 首先创建一个IndexMap 键表示元素 值表示一个列表List list存储该元素在数组的所有索引之后再次创建一个map1 针对上面的List 键表示列表的长度 值表示索引的差值遍历indexmap 将所有的list的长度 和 索引的差值存储遍历map1 找到最大的key 那么这个Ke…...
GO语言工具函数库--Lancet
支持300常用功能的开源GO语言工具函数库–Lancet lancet(柳叶刀)是一个全面、高效、可复用的go语言工具函数库。lancet受到了java apache common包和lodash.js的启发。 特性 全面、高效、可复用300常用go工具函数,支持string、slice、dateti…...
25、商城系统(七):商城项目基础功能pom.xml(重要),mybatis分页插件
截止这一章,我们就不把重心放在前端,后台的基础代码,因为后面都是业务层面的crud。 前端直接替换这两个文件夹即可,后台代码也直接复制: 一、重新更新一下所有的pom.xml 这个地方我踩了好多坑,最后得到一个完整的pom.xml,建议大家直接用我的pom.xml替换即可。 1.comm…...
【Docker-Dev】Mac M2 搭建docker mysql
Mac M2 搭建Mysql 1、前言2、前置说明-Docker的代理访问3、前置说明-Mysql的镜像访问3.1、提取信息3.1.1、开启Mysql的实例3.1.2、Dokcer连接Mysql3.1.3、官方简易版的docker-compose3.1.4、如何登录mysql bash3.1.5、自定义my.cnf文件3.1.6、如何知道其他自定义配置项 4、M2安…...
idea中终端Terminal页面输入命令git log后如何退出
1、idea中Terminal输入命令git log后如何退出? 2、解决 输入q键会自动退出git log命令...
程序员必备IDEA插件,什么是是IDE?
IDEA是一款功能强大的集成开发环境(IDE)插件,它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。 我们在编写完接口代码后需要进行接口调试等操作,一般需要打开额外的调试工具。今天就给大家介绍一款IDEA插件ÿ…...
SkyWalking UI 修改发布Nginx
文章目录 SkyWalking UI修改图标修改路由发布到Nginx添加认证修改路由模式vite.config.ts添加baseNginx配置 SkyWalking UI skywalking-booster-ui下载地址 修改图标 替换 logo.svg 修改路由 router - data - index.ts 发布到Nginx 添加认证 # 安装 yum install -y h…...
移动硬盘打不开怎么办?没有比这更好的办法了
移动硬盘打不开是常见故障,可能的原因有很多,例如硬盘驱动器故障、文件系统损坏、分区表错误等。本文将详细分析这些原因,并提供相应的解决方法,帮助您解决移动硬盘打不开的问题。 当移动硬盘打不开时,为了保留其中的文…...
[场景实现]:多选框与树形结构递归
一、场景描述 实现一个分配权限的页面,最左侧是大的权限模块的名称,左右侧是控制其是否勾选的多选框。中间部分是一级权限模块下的子权限名称及多选框。 请求此权限模块数据的接口返回的是树形结构 对象数组。 主要属性为menuName表示权限名࿰…...
从0到1浅析Redis服务器反弹Shell那些事
文章目录 前言Redis服务1.1 特点与应用1.2 安装与使用1.3 语法和配置1.4 未授权访问 反弹Shell2.1 Web服务写入Webshell2.2 Linux定时任务反弹shell2.3 /etc/profile.d->反弹shell2.4 写入ssh公钥登录服务器2.5 利用Redis主从复制RCE2.6 SSRF漏洞组合拳->RCE 总结 前言 …...
JavaScript中alert、confrim、prompt的使用及区别【精选】
Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍JavaScript中alert、confrim、prompt的区别及使用以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获,友友们有任…...
Docker Compose容器编排实战
介绍 Docker Compose 是 Docker 官方提供的一种工具,用于定义和运行多个 Docker 容器的应用。它使用简单的 YAML 文件(通常称为 docker-compose.yml)来配置应用的服务,并使用单个命令即可创建、启动和停止整个应用。 官方文档&am…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
