一、基础的登录认证
通过最基础的登录操作来完成登录处理
1.登录页面处理:
2.认证服务的处理
/**
* 用户登录--注意不添加@RequestBody不然会导致前端数据解析错误因为RedirectAttributes
* 注意RedirectAttributes与Model的区别 addFlashAttribute与addAttribute区别
* @param userLoginVo
* @param redirectAttributes
* @return
*/
@PostMapping("/login")
public String login( UserLoginVo userLoginVo ,RedirectAttributes redirectAttributes){
System.out.println(userLoginVo);
R isLogin = memberFeignService.login(userLoginVo);
HashMap<String, String> error = new HashMap<>();
if (isLogin.getCode()!=0){
//登录失败
if (isLogin.getCode()==BugCodeEnume.MEMBER_NOTEXIST_EXCEPTION.getCode()){
//用户名不存在
error.put("userName",isLogin.getMsg());
}
if (isLogin.getCode()==BugCodeEnume.MEMBER_PASSOWRD_EXCEPTION.getCode()){
//密码错误
error.put("password",isLogin.getMsg());
}
redirectAttributes.addFlashAttribute("error",error);
//因为 这里是重定向 那么我们需要用RedirectAttributes来进行视图页面的数据渲染 否则重定向后 页面数据不渲染
System.out.println("登录失败:"+error);
return "redirect:http://www.yueluo.auth.top/login.html";
}
System.out.println("登录成功");
return "redirect:http://www.yueluo.top";
}
3.会员中心的认证逻辑 -远程服务用户名密码校验
/**
* 会员用户登录
* @param loginVo
* @return
*/
@PostMapping("/login")
public R login(@RequestBody MemberLoginVo loginVo){
return memberService.login(loginVo);
}
具体用户名密码认证处理
/**
* 会员登录
* @param loginVo
*/
@Override
public R login(MemberLoginVo loginVo) {
//根据用户名/手机号查询用户
MemberEntity memberEntity = memberDao.selectOne(new QueryWrapper<MemberEntity>()
.eq("username", loginVo.getUserName())
.or()
.eq("mobile", loginVo.getPhone()));
//查询用户为空
if (memberEntity==null){
//登录失败--账号不存在
return R.error(BugCodeEnume.MEMBER_NOTEXIST_EXCEPTION.getCode(), BugCodeEnume.MEMBER_NOTEXIST_EXCEPTION.getMessage());
}
boolean matches = new BCryptPasswordEncoder().matches(loginVo.getPassword(), memberEntity.getPassword());
if (!matches){
//密码错误--登录失败
return R.error(BugCodeEnume.MEMBER_PASSOWRD_EXCEPTION.getCode(), BugCodeEnume.MEMBER_PASSOWRD_EXCEPTION.getMessage());
}
//密码正确--登录成功
return R.ok();
}
4.feign服务
import com.yueluo.mall.auth.vo.UserLoginVo;
import com.yueluo.mall.auth.vo.UserRegisterVo;
import com.yueluo.mall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 远程会员用户服务
* @author xjx
* @email 15340655443@163.com
* @date 2024/2/5 12:11
*/
@FeignClient("mall-member")
public interface MemberFeignService {
/**
* 用户注册
* MemberRegisterVo 相当于这里的UserRegisterVo
* @param registerVo
* @return
*/
@PostMapping("member/member/register")
public R register(@RequestBody UserRegisterVo registerVo);
/**
* 用户登录
* @param loginVo
* @return
*/
@PostMapping("member/member/login")
public R login(@RequestBody UserLoginVo loginVo);
}
二、Auth2.0 (集成第三方登录)
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。
这里及集成了几乎所有的平台开发文档与说明下面地址失效看这个
1.微博开放平台
地址:新浪微博开放平台-首页
1.1创建应用
创建后的基本信息:
1.2授权设置:
1.3社交认证文档:
1.4微博Web端授权的操作:
引导用户点击按钮跳转到对应的授权页面
1.5点击授权按钮后查看回调接口的code信息
获取到了code信息:59d62e59e5ead5a4ea89c6f9cf212568
然后根据code信息我们可以去授权服务器获取对应的AccessToken。
1.6获取Token信息只支持POST方式提交
1.7在PostMan中通过post方式提交成功获取到了对应的token信息
获取到了Token信息后我们就可以去资源服务器获取对象的信息
1.8 前后端代码
1.8.1 code处理
在后台服务中获取code并对应的获取Token信息
1.8.2然后需要同步的调整前端引入的链接地址:
1.8.3 获取Token信息
根据上一步获取的code信息,我们可以获取对应的Token信息 用到的工具类HttpUtil
package com.yueluo.mall.third.utils;
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;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.lang.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;
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);
}
}
}
@RequestMapping("/oauth/weibo/success")
public String weiboOAuth(@RequestParam("code") String code) throws Exception {
Map<String,String> body = new HashMap<>();
body.put("client_id","1093598037");
body.put("client_secret","1085c8de04dee49e9bb110eaf2d3cf62");
body.put("grant_type","authorization_code");
body.put("redirect_uri","http://msb.auth.com/oauth/weibo/success");
body.put("code",code);
// 根据Code获取对应的Token信息
HttpResponse post = HttpUtils.doPost("https://api.weibo.com"
, "/oauth2/access_token"
, "post"
, new HashMap<>()
, null
, body
);
int statusCode = post.getStatusLine().getStatusCode();
if(statusCode != 200){
// 说明获取Token失败,就调回到登录页面
return "redirect:http://msb.auth.com/login.html";
}
// 说明获取Token信息成功
String json = EntityUtils.toString(post.getEntity());
SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
// 注册成功就需要调整到商城的首页
return "redirect:http://msb.mall.com/home.html";
}
1.9 登录和注册
表结构中新增对应的
然后在对应的实体对象中添加对应的属性
service中实现注册和登录的逻辑
/**
* 社交登录
* @param vo
* @return
*/
@Override
public MemberEntity login(SocialUser vo) {
String uid = vo.getUid();
// 如果该用户是第一次社交登录,那么需要注册
// 如果不是第一次社交登录 那么就更新相关信息 登录功能
MemberEntity memberEntity = this.getOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));
if(memberEntity != null){
// 说明当前用户已经注册过了 更新token和过期时间
MemberEntity entity = new MemberEntity();
entity.setId(memberEntity.getId());
entity.setAccessToken(vo.getAccessToken());
entity.setExpiresIn(vo.getExpiresIn());
this.updateById(entity);
// 在返回的登录用户信息中我们同步的也保存 token和过期时间
memberEntity.setAccessToken(vo.getAccessToken());
memberEntity.setExpiresIn(vo.getExpiresIn());
return memberEntity;
}
// 表示用户是第一提交,那么我们就需要对应的来注册
MemberEntity entity = new MemberEntity();
entity.setAccessToken(vo.getAccessToken());
entity.setExpiresIn(vo.getExpiresIn());
entity.setSocialUid(vo.getUid());
// 通过token调用微博开发的接口来获取用户的相关信息
try {
Map<String,String> querys = new HashMap<>();
querys.put("access_token",vo.getAccessToken());
querys.put("uid",vo.getUid());
HttpResponse response = HttpUtils.doGet("https://api.weibo.com"
, "/2/users/show.json"
, "get"
, new HashMap<>()
, querys
);
if(response.getStatusLine().getStatusCode() == 200){
String json = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSON.parseObject(json);
String nickName = jsonObject.getString("screen_name");
String gender = jsonObject.getString("gender");
entity.setNickname(nickName);
entity.setGender("m".equals(gender)?1:0);
}
}catch (Exception e){
}
// 注册用户信息
this.save(entity);
return entity;
}
1.10 登录的串联
在Auth服务中我们需要通过Feign来调用MemberService中的相关服务来完成最后的串联
@RequestMapping("/oauth/weibo/success")
public String weiboOAuth(@RequestParam("code") String code) throws Exception {
Map<String,String> body = new HashMap<>();
body.put("client_id","1093598037");
body.put("client_secret","1085c8de04dee49e9bb110eaf2d3cf62");
body.put("grant_type","authorization_code");
body.put("redirect_uri","http://msb.auth.com/oauth/weibo/success");
body.put("code",code);
// 根据Code获取对应的Token信息
HttpResponse post = HttpUtils.doPost("https://api.weibo.com"
, "/oauth2/access_token"
, "post"
, new HashMap<>()
, null
, body
);
int statusCode = post.getStatusLine().getStatusCode();
if(statusCode != 200){
// 说明获取Token失败,就调回到登录页面
return "redirect:http://msb.auth.com/login.html";
}
// 说明获取Token信息成功
String json = EntityUtils.toString(post.getEntity());
SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
R r = memberFeginService.socialLogin(socialUser);
if(r.getCode() != 0){
// 登录错误
return "redirect:http://msb.auth.com/login.html";
}
String entityJson = (String) r.get("entity");
System.out.println("----------------->" + entityJson);
// 注册成功就需要调整到商城的首页
return "redirect:http://msb.mall.com/home";
}
2.百度开放平台
不知道地址的就百度搜索 : 百度支持的OAuth
地址:
地址:
Auth2.0操作:https://developer.baidu.com/wiki/index.php?title=docs/oauth
2.1创建应用:管理控制台 - 百度开放云平台
创建完成:
2.2引导用户跳转到授权地址:
http://openapi.baidu.com/oauth/2.0/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REGISTERED_REDIRECT_URI&
scope=email&
display=popup
地址修改为我们自己的:与百度连接
报错:配置:
2.3获取到的Code信息
code:d789d0160b2fa99bb1f840002569526e
2.4获取到对应的token信息
Token:121.6966ae0e0f3cd19fa36a375489342b08.YmfrSxYqsOt1eUoPzkC60yCsa7W09OmqTbPsuVL.zmdMFg
3.JustAuth集成工具使用:
官网地址:百度登录 | JustAuth
3.1依赖引入
<!-- 引入justAuth集合第三方平台进行登录授权 配套hutool-http/all 俩个工具依赖-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.16.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
3.2 配置应用信息
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
redis:
host: 127.0.0.1
port: 6379
password: root
application:
name: mall-auth
thymeleaf:
cache: false # 关闭Thymeleaf前端的缓存
# 统一的全局的-设置服务器相应给客户端的日期格式
jackson:
date-format: yyy-MM-dd HH:mm:ss
# auth2.0 第三方登录配置信息
oauth:
weibo:
AppKey: 1894952539
AppSecret: c3b053b1dd15c83ss6fb32serc64e51e
redirectUri: http://www.yueluo.auth.top/oauth/weibo/callback
server:
port: 9000
3.3 业务代码
package com.yueluo.mall.auth.controller;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWeiboRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 第三方平台授权登录
* @author xjx
* @email 15340655443@163.com
* @date 2024/2/6 14:36
*/
@RestController
@RequestMapping("oauth")
public class OAuth2Controller {
@Value("${spring.oauth.weibo.AppKey}")
private String AppKey;
@Value("${spring.oauth.weibo.AppSecret}")
private String AppSecret;
@Value("${spring.oauth.weibo.redirectUri}")
private String redirectUri;
/**
* 浏览器跳转地址 引导用户进行授权登录
* 然后他会进入到我们应用中的回调地址即请求我们的真正登录接口
* @param response
* @throws IOException
*/
@RequestMapping("/render/weibo")
public void renderAuth(HttpServletResponse response) throws IOException {
// 获得 AuthRequest 对象
AuthRequest authRequest = getAuthRequest();
// 生成一个状态值
String state = AuthStateUtils.createState();
// 使用 AuthRequest 对象生成一个重定向 URL,并将其发送到用户的浏览器
response.sendRedirect(authRequest.authorize(state));
}
/**
* 真正登录接口实现
* 1.获取token 2.获取用户信息 即应用中配置的回调地址
* @param callback
* @return
*/
@RequestMapping("/weibo/callback")
public Object login(AuthCallback callback) {
AuthRequest authRequest = getAuthRequest();
return authRequest.login(callback);
}
@RequestMapping("/revoke/{token}")
public Object revokeAuth(@PathVariable("token") String token) throws IOException {
AuthRequest authRequest = getAuthRequest();
return authRequest.revoke(AuthToken.builder().accessToken(token).build());
}
private AuthRequest getAuthRequest() {
return new AuthWeiboRequest(AuthConfig.builder()
.clientId(AppKey)
.clientSecret(AppSecret)
.redirectUri(redirectUri)
.build());
}
}
3.4简单前端代码
<!-- 微博授权登录 发送到微博进行跳转登录 /render/weibo 即 www.yueluo.auth.top(127.0.0.1:9000)/oauth/render/weibo-->
<li>
<a href="/oauth/render/weibo">
<img style="height: 18px;width: 50px" src="/static/login/JD_img/weibo.png"/>
</a>
</li>
4.总结
步骤:
1.创建应用
2.配置回调 授权地址 与 取消授权地址
3.访问API文档中的获取code地址 得到code
4.根据code信息 根据API文档地址去授权服务器获取对应的AccessToken
获取Token信息只支持POST方式提交
5.获取到了Token信息后我们就可以去资源服务器获取对象的信息-具体看API文档
二、分布式Session
1.Session问题
2.同一主域名下的子域名 session共享
域名例如:
search.yueluo.top
auth.yueluo.top
order.yueluo.top我们通过SpringSession来实现Session的共享,Session数据存储在Redis中
2.1spring-session整合
SpringSession的操作指南:
2.2导入spring-session依赖
<!-- spring-session整合-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- redis整合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.3设置对应的配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://118.31.103.120:3306/mall-oms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: xjx
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#server-addr: 118.31.103.120:8848
application:
name: mall-order
redis:
host: 127.0.0.1
port: 6379
password: root
cache:
type: redis # 缓存的类型为redis
redis:
time-to-live: 6000000 # 指定过期时间 s,000
# key-prefix: yueluo # 防止覆盖掉业务方法上配置的注解的key
use-key-prefix: true # 是否使用前缀
cache-null-values: true # 防止缓存穿透 允许空值
session:
store-type: redis
redis:
namespace: spring:session
user:
userName: xjx
age: 18
thymeleaf:
cache: false # 关闭Thymeleaf前端的缓存
# 统一的全局的-设置服务器相应给客户端的日期格式
jackson:
date-format: yyy-MM-dd HH:mm:ss
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto # 主键自增
server:
port: 8030
servlet:
session:
timeout: 1800 # session过期时间
最后我们需要在启动类上添加对应的注解,放开操作
然后在Auth服务和商城首页都整合SpringSession后,我们再商城首页可以看到Session的数据,注意这儿是手动修改Cookie的域名
2.4自定义Cookie
通过自定义Cookie实现session域名的调整
@Configuration
public class MySessionConfig {
/**
* 自定义Cookie的配置
* @return
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("yueluo.top"); // 设置session对应的一级域名
cookieSerializer.setCookieName("ylsession");
return cookieSerializer;
}
/**
* 对存储在Redis中的数据指定序列化的方式
* @return
*/
@Bean
public RedisSerializer<Object> redisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
登录业务中通过HttpSession来设置对应的属性
前端html页面通过 ${session.userLogin} 来得到 注意序列化问题
2.5其他服务拦截获取session中用户信息
package com.yueluo.mall.order.interceptor;
import com.yueluo.mall.common.constant.session.AuthConstant;
import com.yueluo.mall.common.dto.MemberDTO;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 认证拦截器
* @author xjx
* @email 15340655443@163.com
* @date 2024/2/13 17:05
*/
public class AuthInterceptor implements HandlerInterceptor {
/**
* ThreadLocal用于存储当前线程的局部变量
* 本地线程对象Map<Thread, T>,用于存储当前线程的局部变量
*/
public static ThreadLocal<MemberDTO> memberDTOThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//通过HttpSession获取当前用户信息
HttpSession session = request.getSession();
Object sessionAttribute = session.getAttribute(AuthConstant.SESSION_LOGIN_USER);
if (sessionAttribute == null) {
//未登录
session.setAttribute("msg", "要访问订单,请先登录");
response.sendRedirect("http://www.yueluo.top/auth/login.html");
return false;//拦截
}
//已登录
memberDTOThreadLocal.set((MemberDTO) sessionAttribute);
System.out.println("当前用户的数据:" + (MemberDTO) sessionAttribute);
return true;//放行
}
}
package com.yueluo.mall.order.config;
import com.yueluo.mall.order.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 将我们自定义拦截器添加进去
* @author xjx
* @email 15340655443@163.com
* @date 2024/2/12 9:01
*/
@Configuration
public class MyWebInterceptorConfig implements WebMvcConfigurer {
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器--拦截所有请求
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}
2.6注意服务之间的远程调用
package com.yueluo.mall.order.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @author xjx
* @email 15340655443@163.com
* @date 2024/2/14 19:21
*/
@Configuration
public class MallFeignConfig {
/**
* 解决因为访问购物车服务时,没有携带token,导致无法获取到用户信息--而转到了登录页面所以导致类型转换异常
* 因为spring在feign远程调用时创建了一个RequestTemplate,但是没有携带cookie
* Could not extract response: no suitable HttpMessageConverter found for response
* type [java.util.List<com.yueluo.mall.order.vo.OrderItemVo>] and content type [text/html;charset=UTF-8]
* 解决方案 在feign调用时,携带cookie
*
* 如果在异步使用线程池进行远程调用的时候 使用的还是本地线程 因此我们需要同步添加 具体看OrderServiceImpl 中toConfirm方法
* RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
* //同步设置请求头
* RequestContextHolder.setRequestAttributes(requestAttributes);
* @return
*/
@Bean
public RequestInterceptor requestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String cookie = requestAttributes.getRequest().getHeader("Cookie");
requestTemplate.header("Cookie",cookie);
}
};
}
}
使用:
/**
* 查询订单确认信息
* @return
*/
@Override
public OrderConfirmVo toConfirm() {
MemberDTO memberInfo = getMemberInfo();
Long memberId = memberInfo.getId();
OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
//获取RequestContextHolder.getRequestAttributes() RequestContextHolder.getRequestAttributes()
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
//同步设置请求头
RequestContextHolder.setRequestAttributes(requestAttributes);
//1.会员地址信息
List<MemberAddressVo> address = memberFeignService.getAddress(memberId);
orderConfirmVo.setAddress(address);
}, threadPoolExecutor);
CompletableFuture<Void> cartItemsFuture = CompletableFuture.runAsync(() -> {
//同步设置请求头
RequestContextHolder.setRequestAttributes(requestAttributes);
//2.购物车选中的商品信息
List<OrderItemVo> cartItems = cartFeignService.getCartItems(memberId);
orderConfirmVo.setItems(cartItems);
}, threadPoolExecutor);
//3.积分信息
//4.优惠券信息
//5.发票信息
//6.其他信息
//7.计算价格--计算订单的总金额 订单支付的总金额
//8.返回确认信息
CompletableFuture.allOf(addressFuture, cartItemsFuture).join();
System.out.println("当前用户"+memberInfo+"订单确认信息为"+orderConfirmVo.toString());
//9.防重令牌
String token = UUID.randomUUID().toString().replace("-", "");
orderConfirmVo.setOrderToken(token);
//10.缓存防重令牌
redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN_PREFIX+memberId,token);
return orderConfirmVo;
}
2.7服务中获取用户对象
public MemberDTO getMemberInfo(){
//在本地线程中取出
MemberDTO memberDTO = AuthInterceptor.memberDTOThreadLocal.get();
if (memberDTO == null){
//没有登录
return null;
}
return memberDTO;
}
3.不同域下的 Session解决
(spring-security)