Springboot集成社交登录功能
pom
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-util -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
工具类,用于发http请求
package com.song.common.utils;
import com.alibaba.nacos.client.utils.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtils {
/**
* get
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doGet(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
/**
* post form
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param bodys
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
Map<String, String> bodys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
/**
* Post String
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
/**
* Post stream
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
/**
* Put String
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
/**
* Put stream
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
/**
* Delete
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doDelete(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (0 < sbQuery.length()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
}
}
}
if (0 < sbQuery.length()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (KeyManagementException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
}
实体
package com.song.gulimall.member.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/* *
* 会员
*
* @author
* @email
* @date 2020-12-07 22:54:20
*/
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* 会员等级id
*/
private Long levelId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
private String nickname;
/**
* 手机号码
*/
private String mobile;
/**
* 邮箱
*/
private String email;
/**
* 头像
*/
private String header;
/**
* 性别
*/
private Integer gender;
/**
* 生日
*/
private Date birth;
/**
* 所在城市
*/
private String city;
/**
* 职业
*/
private String job;
/**
* 个性签名
*/
private String sign;
/**
* 用户来源
*/
private Integer sourceType;
/**
* 积分
*/
private Integer integration;
/**
* 成长值
*/
private Integer growth;
/**
* 启用状态
*/
private Integer status;
/**
* 注册时间
*/
private Date createTime;
/**
* 社交登录UID
*/
private String uid;
/**
* 社交登录TOKEN
*/
private String accessToken;
/**
* 社交登录过期时间
*/
private long expiresIn;
}
package com.song.gulimall.member.vo;
import lombok.Data;
/**
* 用以封装社交登录认证后换回的令牌等信息
*/
@Data
public class SocialUser {
/**
* 令牌
*/
private String access_token;
private String remind_in;
/**
* 令牌过期时间
*/
private long expires_in;
/**
* 该社交用户的唯一标识
*/
private String uid;
private String isRealName;
}
微博认证的回调接口 路径可以在微博开放平台中设置
package com.song.gulimall.gulimallauthserver.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.song.common.utils.AuthServerConstant;
import com.song.common.utils.HttpUtils;
import com.song.common.utils.R;
import com.song.common.vo.MemberResponseVo;
import com.song.gulimall.gulimallauthserver.feign.MemberFeignService;
import com.song.gulimall.gulimallauthserver.vo.SocialUser;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@Controller
public class OauthController {
@Resource
private MemberFeignService memberFeignService;
/* *
* 微博认证接口
* @param code
* @param session
* @return
* @throws Exception
*/
@RequestMapping("/oauth2.0/weibo/success")
public String authorize(String code, HttpSession session) throws Exception {
//1. 使用code换取token,换取成功则继续2,否则重定向至登录页
Map<String, String> query = new HashMap<>();
query.put("client_id", "2144471074");
query.put("client_secret", "ff63a0d8d591a85a29a19492817316ab");
query.put("grant_type", "authorization_code");
query.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/weibo/success");
query.put("code", code);
//发送post请求换取token
HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<String, String>(), query, new HashMap<String, String>());
Map<String, String> errors = new HashMap<>();
if (response.getStatusLine().getStatusCode() == 200) {
//2. 调用member远程接口进行oauth登录,登录成功则转发至首页并携带返回用户信息,否则转发至登录页
String json = EntityUtils.toString(response.getEntity());
SocialUser socialUser = JSON.parseObject(json, new TypeReference<SocialUser>() {
});
R login = memberFeignService.authLogin(socialUser);
//2.1 远程调用成功,返回首页并携带用户信息
if (login.getCode() == 0) {
String jsonString = JSON.toJSONString(login.get("memberEntity"));
System.out.println("----------------" + jsonString);
MemberResponseVo memberResponseVo = JSON.parseObject(jsonString, new TypeReference<MemberResponseVo>() {
});
System.out.println("----------------" + memberResponseVo);
session.setAttribute(AuthServerConstant.LOGIN_USER, memberResponseVo);
return "redirect:http://gulimall.com";
} else {
//2.2 否则返回登录页
errors.put("msg", "登录失败,请重试");
session.setAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
} else {
errors.put("msg", "获得第三方授权失败,请重试");
session.setAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}
}
controller
/* *
* 社交登录
* @param socialUser
* @return
*/
@PostMapping("/oauth2/login")
public R authLogin(@RequestBody SocialUser socialUser) {
MemberEntity entity=memberService.authLogin(socialUser);
if (entity!=null){
return R.ok().put("memberEntity",entity);
}else {
return R.error();
}
}
service
MemberEntity authLogin(SocialUser socialUser);
impl
/* *
* 社交登录
* @param socialUser
* @return
*/
@Override
public MemberEntity authLogin(SocialUser socialUser) {
MemberEntity uid = this.getOne(new QueryWrapper<MemberEntity>().eq("uid", socialUser.getUid()));
//1 如果之前未登陆过,则查询其社交信息进行注册
if (uid == null) {
Map<String, String> query = new HashMap<>();
query.put("access_token", socialUser.getAccess_token());
query.put("uid", socialUser.getUid());
//调用微博api接口获取用户信息
String json = null;
try {
HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<>(), query);
json = EntityUtils.toString(response.getEntity());
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObject = JSON.parseObject(json);
//获得昵称,性别,头像
String name = jsonObject.getString("name");
String gender = jsonObject.getString("gender");
String profile_image_url = jsonObject.getString("profile_image_url");
//封装用户信息并保存
uid = new MemberEntity();
MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
uid.setLevelId(defaultLevel.getId());
uid.setNickname(name);
uid.setGender("m".equals(gender) ? 0 : 1);
uid.setHeader(profile_image_url);
uid.setAccessToken(socialUser.getAccess_token());
uid.setUid(socialUser.getUid());
uid.setExpiresIn(socialUser.getExpires_in());
this.save(uid);
} else {
//2 否则更新令牌等信息并返回
uid.setAccessToken(socialUser.getAccess_token());
uid.setUid(socialUser.getUid());
uid.setExpiresIn(socialUser.getExpires_in());
this.updateById(uid);
}
return uid;
}
微博开放API接口:https://open.weibo.com/wiki/%E5%BE%AE%E5%8D%9AAPI
Session共享
解决方案:
解决session集群的方案
整合springSession 解决了session共享问题以及不同域名之间的共享问题(自定义cookies 设置范围最大的域名)
pom
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置
server:
servlet:
session:
timeout: 30 # 超时时间
spring:
session:
store-type: redis # 存储方式
package com.song.gulimall.product.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
/* *
* session 配置
*/
@Configuration
public class GulimallSessionConfig {
/* *
* 存入redis的序列化方式
* @return
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
/* *
* 设置客户端的暴露bean 解决不同的域名之间的访问 设置父域名
* @return
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("GULISESSIONID");
serializer.setDomainName("gulimall.com");
return serializer;
}
}
启动类注解
@EnableRedisHttpSession
@EnableRedisHttpSession
@SpringBootApplication
@EnableFeignClients("com.song.gulimall.gulimallauthserver.feign")
@EnableDiscoveryClient
public class GulimallAuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallAuthServerApplication.class, args);
}
}