一、Spring Security 模块搭建(单体架构和分布式架构)
1.创建权限的数据库
导入相应的数据信息
例如基于用户的 RBAC 角色访问控制则有下面五张表
user、user_role(user和role的关联表)、role、permission(role和menu的关联表)、menu
2.导入SpringSecurity的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
3.config中配置DaoAuthenticationProviderCustom
@Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {
@Autowired
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
}
4.config中配置 WebSecurityConfig
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//配置用户信息服务
// @Bean
// public UserDetailsService userDetailsService() {
// //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }
@Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
@Autowired
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProviderCustom);
}
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
.anyRequest().permitAll()//其它请求全部放行
.and()
.formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
}
public static void main(String[] args) {
String password = "111111";
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
for (int i = 0; i < 5; i++) {
//生成密码
String encode = passwordEncoder.encode(password);
System.out.println(encode);
//校验密码,参数1是输入的明文 ,参数2是正确密码加密后的串
boolean matches = passwordEncoder.matches(password, encode);
System.out.println(matches);
}
boolean matches = passwordEncoder.matches("1234", "$2a$10$fb2RlvFwr9HsRu9vH1OxCu/YiMRw6wy5UI6u3s0A.0bVSuR1UqdHK");
System.out.println(matches);
}
}
5.在controller中对方法授权
例如:对r1方法的调用上使用@PreAuthorize指定只有p1的权利才可以访问
@RequestMapping("/r/r1")
@PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
public String r1() {
return "访问r1资源";
}
@RequestMapping("/r/r2")
@PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问
public String r2() {
return "访问r2资源";
}
6.在config中配置 AuthorizationServer
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Resource(name="authorizationServerTokenServicesCustom")
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private AuthenticationManager authenticationManager;
//客户端详情服务
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()// 使用in-memory存储
.withClient("XcWebApp")// client_id
// .secret("XcWebApp")//客户端密钥
.secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥
.resourceIds("xuecheng-plus")//资源列表
.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//客户端接收授权码的重定向地址
.redirectUris("http://www.51xuecheng.cn")
;
}
//令牌端点的访问配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)//认证管理器
.tokenServices(authorizationServerTokenServices)//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
//令牌端点的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
security
.tokenKeyAccess("permitAll()") //oauth/token_key是公开
.checkTokenAccess("permitAll()") //oauth/check_token公开
.allowFormAuthenticationForClients() //表单认证(申请令牌)
;
}
}
7. 在config中配置 TokenConfig
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "mq123";
@Autowired
TokenStore tokenStore;
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
//令牌管理服务
@Bean(name="authorizationServerTokenServicesCustom")
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
}
8.授权方式
授权码模式(Authorization Code Grant)和密码模式(Resource Owner Password Credentials Grant)是两种常用的授权方式,它们在发送请求时所需要的参数有所不同。
授权码模式(Authorization Code Grant)
授权码模式通常涉及以下几个步骤,每个步骤可能需要发送不同的请求和参数:
- 用户授权请求:
- 客户端引导用户到授权服务器进行授权。
- 请求参数通常包括:
response_type
:必填,值固定为code
,表示请求类型为授权码。client_id
:必填,客户端的ID,用于标识请求方。redirect_uri
:可选,授权成功后重定向的URI,必须与客户端注册的回调地址一致。scope
:可选,表示请求的权限范围。state
:推荐,用于维护请求和回调之间的状态,防止CSRF攻击。
- 获取访问令牌请求:
- 用户授权后,授权服务器将用户重定向回客户端的
redirect_uri
,并附上授权码(code)。 - 客户端使用授权码向授权服务器请求访问令牌。
- 请求参数通常包括:
grant_type
:必填,值固定为authorization_code
,表示授权类型为授权码。code
:必填,授权服务器返回的授权码。redirect_uri
:必填,必须与获取授权码时使用的回调地址一致。client_id
:必填,客户端的ID。client_secret
:必填,客户端的密钥,用于验证客户端的身份。
- 用户授权后,授权服务器将用户重定向回客户端的
密码模式(Resource Owner Password Credentials Grant)
密码模式允许客户端直接通过用户名和密码获取访问令牌,通常用于客户端和资源所有者之间具有高信任度的场景。请求参数通常包括:
grant_type
:必填,值固定为password
,表示授权类型为密码。username
:必填,用户的用户名。password
:必填,用户的密码。client_id
:必填,客户端的ID,用于标识请求方。client_secret
:在某些实现中可能是必填的,用于验证客户端的身份。然而,由于直接处理用户的密码,这种模式存在安全风险,因此在一些场景中可能不要求client_secret
,或者客户端密钥通过其他方式(如HTTP Basic认证)进行验证。
9.自定义UserDetailService(连接数据库中的用户数据)
@Slf4j
@Component
public class UserServiceImpl implements UserDetailsService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
XcMenuMapper xcMenuMapper;
@Autowired
ApplicationContext applicationContext;
//传入的请求认证的参数就是AuthParamsDto
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//将传入的json转成AuthParamsDto对象
AuthParamsDto authParamsDto = null;
try {
authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {
throw new RuntimeException("请求认证参数不符合要求");
}
//认证类型,有password,wx。。。
String authType = authParamsDto.getAuthType();
//根据认证类型从spring容器取出指定的bean
String beanName = authType+"_authservice";
AuthService authService = applicationContext.getBean(beanName, AuthService.class);
//调用统一execute方法完成认证
XcUserExt xcUserExt = authService.execute(authParamsDto);
//封装xcUserExt用户信息为UserDetails
//根据UserDetails对象生成令牌
UserDetails userPrincipal = getUserPrincipal(xcUserExt);
return userPrincipal;
}
/**
* @description 查询用户信息
* @param xcUser 用户id,主键
* @return com.xuecheng.ucenter.model.po.XcUser 用户信息
* @author Mr.M
* @date 2022/9/29 12:19
*/
public UserDetails getUserPrincipal(XcUserExt xcUser){
String password = xcUser.getPassword();
//权限
String[] authorities= {"test"};
//根据用户id查询用户的权限
List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(xcUser.getId());
if(xcMenus.size()>0){
List<String> permissions =new ArrayList<>();
xcMenus.forEach(m->{
//拿到了用户拥有的权限标识符
permissions.add(m.getCode());
});
//将permissions转成数组
authorities = permissions.toArray(new String[0]);
}
xcUser.setPassword(null);
//将用户信息转json
String userJson = JSON.toJSONString(xcUser);
UserDetails userDetails = User.withUsername(userJson).password(password).authorities(authorities).build();
return userDetails;
}
}
二、其他模块(分布式项目)
除了以上的Spring Security用于配置的auth模块外所调用的Spring Security的模块都需要进行的操作
1.导入SpringSecurity相关依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.在config中配置ResouceServerConfig
注意:RESOURCE_ID 值必须和Spring Security模块中的AuthorizationServer的resourceIds值一致。
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
//资源服务标识
public static final String RESOURCE_ID = "xuecheng-plus";
@Autowired
TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/r/**","/course/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()
;
}
}
3.在config中配置TokenConfig
@Configuration
public class TokenConfig {
String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
三、网关模块(分布式项目中的网关)
1.导入SpringSecurity相关依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.在config中配置SecurityConfig
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {
//安全拦截配置
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated()
.and().csrf().disable().build();
}
}
3.在config中配置TokenConfig
@Configuration
public class TokenConfig {
String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
4.在config中配置GatewayAuthFilter
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {
//白名单
private static List<String> whitelist = null;
static {
//加载白名单
try (
InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
) {
Properties properties = new Properties();
properties.load(resourceAsStream);
Set<String> strings = properties.stringPropertyNames();
whitelist= new ArrayList<>(strings);
} catch (Exception e) {
log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
e.printStackTrace();
}
}
@Autowired
private TokenStore tokenStore;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//请求的url
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
//白名单放行
for (String url : whitelist) {
if (pathMatcher.match(url, requestUrl)) {
return chain.filter(exchange);
}
}
//检查token是否存在
String token = getToken(exchange);
if (StringUtils.isBlank(token)) {
return buildReturnMono("没有认证",exchange);
}
//判断是否是有效的token
OAuth2AccessToken oAuth2AccessToken;
try {
oAuth2AccessToken = tokenStore.readAccessToken(token);
boolean expired = oAuth2AccessToken.isExpired();
if (expired) {
return buildReturnMono("认证令牌已过期",exchange);
}
return chain.filter(exchange);
} catch (InvalidTokenException e) {
log.info("认证令牌无效: {}", token);
return buildReturnMono("认证令牌无效",exchange);
}
}
/**
* 获取token
*/
private String getToken(ServerWebExchange exchange) {
String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(tokenStr)) {
return null;
}
String token = tokenStr.split(" ")[1];
if (StringUtils.isBlank(token)) {
return null;
}
return token;
}
private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(new RestErrorResponse(error));
byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return 0;
}
}
还要在resource目录下创建文件security-whitelist.properties:
/**=临时全部打开
/auth/**=认证地址
/content/open/**=内容管理公开访问接口
/media/open/**=媒资管理公开访问接口
这时可以将其他模块的config中的ResouceServerConfig的antMatchers给注释掉,因为放行的操作统一由网关进行处理
四、编写SpringSecurity工具类SecurityUtil (单体架构和分布式架构)
这样可以通过Security.XcUser user = SecurityUtil.getUser()获取当前用户的信息
比如获取当前的用户名: user.getUsername()
@Slf4j
public class SecurityUtil {
public static XcUser getUser() {
try {
//拿 到当前用户身份
Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principalObj instanceof String) {
//取出用户身份信息
String principal = principalObj.toString();
//将json转成对象
XcUser user = JSON.parseObject(principal, XcUser.class);
return user;
}
} catch (Exception e) {
log.error("获取当前登录用户身份出错:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
@Data
public static class XcUser implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String username;
private String password;
private String salt;
private String name;
private String nickname;
private String wxUnionid;
private String companyId;
/**
* 头像
*/
private String userpic;
private String utype;
private LocalDateTime birthday;
private String sex;
private String email;
private String cellphone;
private String qq;
/**
* 用户状态
*/
private String status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
}
五、创建相关类(单体架构和分布式架构)
model类:
创建AuthParamsDto 类
@Data
public class AuthParamsDto {
private String username; //用户名
private String password; //域 用于扩展
private String cellphone;//手机号
private String checkcode;//验证码
private String checkcodekey;//验证码key
private String authType; // 认证的类型 password:用户名密码模式类型 sms:短信模式类型
private Map<String, Object> payload = new HashMap<>();//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId
}
XcUserExt 继承用户类
@Data
public class XcUserExt extends XcUser {
//用户权限
List<String> permissions = new ArrayList<>();
}
service类:
AuthService :
public interface AuthService {
/**
* @description 认证方法
* @param authParamsDto 认证参数
* @return com.xuecheng.ucenter.model.po.XcUser 用户信息
* @author Mr.M
* @date 2022/9/29 12:11
*/
XcUserExt execute(AuthParamsDto authParamsDto);
}
WxAuthService
public interface WxAuthService {
/**
* 微信扫码认证,申请令牌,携带令牌查询用户信息、保存用户信息到数据库
* @param code 授权码
* @return
*/
public XcUser wxAuth(String code);
}
serviceImpl类:
PasswordAuthServiceImpl :
@Service("password_authservice")
public class PasswordAuthServiceImpl implements AuthService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
CheckCodeClient checkCodeClient;
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
//账号
String username = authParamsDto.getUsername();
//输入的验证码
String checkcode = authParamsDto.getCheckcode();
//验证码对应的key
String checkcodekey = authParamsDto.getCheckcodekey();
// if(StringUtils.isEmpty(checkcode) || StringUtils.isEmpty(checkcodekey)){
// throw new RuntimeException("请输入的验证码");
// }
//
// //远程调用验证码服务接口去校验验证码
// Boolean verify = checkCodeClient.verify(checkcodekey, checkcode);
// if(verify == null||!verify){
// throw new RuntimeException("验证码输入错误");
// }
//账号是否存在
//根据username账号查询数据库
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
//查询到用户不存在,要返回null即可,spring security框架抛出异常用户不存在
if(xcUser==null){
throw new RuntimeException("账号不存在");
}
//验证密码是否正确
//如果查到了用户拿到正确的密码
String passwordDb = xcUser.getPassword();
//拿 到用户输入的密码
String passwordForm = authParamsDto.getPassword();
//校验密码
boolean matches = passwordEncoder.matches(passwordForm, passwordDb);
if(!matches){
throw new RuntimeException("账号或密码错误");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser,xcUserExt);
return xcUserExt;
}
}
UserServiceImpl :
@Slf4j
@Component
public class UserServiceImpl implements UserDetailsService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
XcMenuMapper xcMenuMapper;
@Autowired
ApplicationContext applicationContext;
//传入的请求认证的参数就是AuthParamsDto
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//将传入的json转成AuthParamsDto对象
AuthParamsDto authParamsDto = null;
try {
authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {
throw new RuntimeException("请求认证参数不符合要求");
}
//认证类型,有password,wx。。。
String authType = authParamsDto.getAuthType();
//根据认证类型从spring容器取出指定的bean
String beanName = authType+"_authservice";
AuthService authService = applicationContext.getBean(beanName, AuthService.class);
//调用统一execute方法完成认证
XcUserExt xcUserExt = authService.execute(authParamsDto);
//封装xcUserExt用户信息为UserDetails
//根据UserDetails对象生成令牌
UserDetails userPrincipal = getUserPrincipal(xcUserExt);
return userPrincipal;
}
/**
* @description 查询用户信息
* @param xcUser 用户id,主键
* @return com.xuecheng.ucenter.model.po.XcUser 用户信息
* @author Mr.M
* @date 2022/9/29 12:19
*/
public UserDetails getUserPrincipal(XcUserExt xcUser){
String password = xcUser.getPassword();
//权限
String[] authorities= {"test"};
//根据用户id查询用户的权限
List<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(xcUser.getId());
if(xcMenus.size()>0){
List<String> permissions =new ArrayList<>();
xcMenus.forEach(m->{
//拿到了用户拥有的权限标识符
permissions.add(m.getCode());
});
//将permissions转成数组
authorities = permissions.toArray(new String[0]);
}
xcUser.setPassword(null);
//将用户信息转json
String userJson = JSON.toJSONString(xcUser);
UserDetails userDetails = User.withUsername(userJson).password(password).authorities(authorities).build();
return userDetails;
}
}
WxAuthServiceImpl :
@Service("wx_authservice")
public class WxAuthServiceImpl implements AuthService, WxAuthService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
XcUserRoleMapper xcUserRoleMapper;
@Autowired
WxAuthServiceImpl currentPorxy;
@Autowired
RestTemplate restTemplate;
@Value("${weixin.appid}")
String appid;
@Value("${weixin.secret}")
String secret;
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
//得到账号
String username = authParamsDto.getUsername();
//查询数据库
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
if(xcUser == null){
throw new RuntimeException("用户不存在");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser, xcUserExt);
return xcUserExt;
}
@Override
public XcUser wxAuth(String code) {
//申请令牌
Map<String, String> access_token_map = getAccess_token(code);
//访问令牌
String access_token = access_token_map.get("access_token");
String openid = access_token_map.get("openid");
//携带令牌查询用户信息
Map<String, String> userinfo = getUserinfo(access_token, openid);
// 保存用户信息到数据库
XcUser xcUser = currentPorxy.addWxUser(userinfo);
return xcUser;
}
@Transactional
public XcUser addWxUser(Map<String,String> userInfo_map){
String unionid = userInfo_map.get("unionid");
String nickname = userInfo_map.get("nickname");
//根据unionid查询用户信息
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getWxUnionid, unionid));
if(xcUser !=null){
return xcUser;
}
//向数据库新增记录
xcUser = new XcUser();
String userId= UUID.randomUUID().toString();
xcUser.setId(userId);//主键
xcUser.setUsername(unionid);
xcUser.setPassword(unionid);
xcUser.setWxUnionid(unionid);
xcUser.setNickname(nickname);
xcUser.setName(nickname);
xcUser.setUtype("101001");//学生类型
xcUser.setStatus("1");//用户状态
xcUser.setCreateTime(LocalDateTime.now());
//插入
int insert = xcUserMapper.insert(xcUser);
//向用户角色关系表新增记录
XcUserRole xcUserRole = new XcUserRole();
xcUserRole.setId(UUID.randomUUID().toString());
xcUserRole.setUserId(userId);
xcUserRole.setRoleId("17");//学生角色
xcUserRole.setCreateTime(LocalDateTime.now());
xcUserRoleMapper.insert(xcUserRole);
return xcUser;
}
/**
* 携带授权码申请令牌
* https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
*
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
* @param code 授权
* @return
*/
private Map<String,String> getAccess_token(String code){
String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
//最终的请求路径
String url = String.format(url_template, appid, secret, code);
//远程调用此url
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, null, String.class);
//获取响应的结果
String result = exchange.getBody();
//将result转成map
Map<String,String> map = JSON.parseObject(result, Map.class);
return map;
}
/**
* 携带令牌查询用户信息
*
* https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
*
* {
* "openid":"OPENID",
* "nickname":"NICKNAME",
* "sex":1,
* "province":"PROVINCE",
* "city":"CITY",
* "country":"COUNTRY",
* "headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
* "privilege":[
* "PRIVILEGE1",
* "PRIVILEGE2"
* ],
* "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
*
* }
* @param access_token
* @param openid
* @return
*/
private Map<String,String> getUserinfo(String access_token,String openid){
String url_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
String url = String.format(url_template, access_token, openid);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
//获取响应的结果
String result = new String(exchange.getBody().getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
//将result转成map
Map<String,String> map = JSON.parseObject(result, Map.class);
return map;
}
}