认证服务+Auth2.0(第三方登录微博)+分布式Session单点登录

一、基础的登录认证

通过最基础的登录操作来完成登录处理

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服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。

 这里及集成了几乎所有的平台开发文档与说明下面地址失效看这个

百度登录 | JustAuth

1.微博开放平台

地址:新浪微博开放平台-首页

 

1.1创建应用  

创建后的基本信息:  

1.2授权设置:  

1.3社交认证文档:  

1.4微博Web端授权的操作:  

引导用户点击按钮跳转到对应的授权页面  

1.5点击授权按钮后查看回调接口的code信息  

获取到了code信息:59d62e59e5ead5a4ea89c6f9cf212568  

然后根据code信息我们可以去授权服务器获取对应的AccessToken。

https://api.weibo.com/oauth2/access_token?client_id=1093598037&client_secret=1085c8de04dee49e9bb110eaf2d3cf62&grant_type=authorization_code&redirect_uri=http://msb.auth.com/success.html&code=59d62e59e5ead5a4ea89c6f9cf212568

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

token访问地址:https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_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的操作指南:  

Spring Session - Spring Boot

 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)

  • 28
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值