分布式___SSO(single sign on)模式实现单点登录____代码实践

在这里插入图片描述
业务流程,
在这里插入图片描述
1,登陆

@Override
    public UserInfo login(UserInfo userInfo) {
        // 页面传递 admin  -- 123
        // db   admin -- 202cb962ac59075b964b07152d234b70
        // 将 123 加密
        String passwd = userInfo.getPasswd();
        // 123 -- 202cb962ac59075b964b07152d234b70
        String newPwd = DigestUtils.md5DigestAsHex(passwd.getBytes());
        userInfo.setPasswd(newPwd);
        UserInfo info = userInfoMapper.selectOne(userInfo);
        // 如果用户登录成功则将用户信息存放到redis
        Jedis jedis = redisUtil.getJedis();

        if (info!=null){
            // 定义key:user:1:info
            String userKey = userKey_prefix+info.getId()+userinfoKey_suffix;
            // 做存储
            jedis.setex(userKey,userKey_timeOut, JSON.toJSONString(info));

            jedis.close();

            return info;
        }
        return null;
    }

2,带token跳转原web应用页面(用户登录成功之后返回token)京东使用JWT生成token,

1. cookie 中存放 token{认为是一个值}  地下城勇士 DNF:    token = 门票:
					token  === JWT {公钥+私钥+签名}最后使用base64编码得到JWT。
					(1、	公共部分
					主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。
					2、	私有部分
					用户自定义的内容,根据实际需要真正要封装的信息。
					3、	签名部分
					根据用户信息+盐值+密钥生成的签名。如果想知道JWT是否是真实的只要把JWT的信息取出来,加上盐值和服务器中的密钥就可以验证真伪。所以不管由谁保存JWT,只要没有密钥就无法伪造。
					用户信息+ip=密钥:
					iP:当前服务器的Ip地址!
					4、	base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以吧base64编码解成明文,所以不要在JWT中放入涉及私密的信息,因为实际上JWT并不是加密信息。
					)
			2. token 是在登录成功之后创建的。
			3. 有了token 可以访问项目。
					是因为token 中有私钥{用户信息 -- token中的用户名密码[通过解密] 和 redis 做比较}
			4. 电商哪里需要登录 结算,其他地方不需要登录。拦截器 -- springmvc。

生成token用到的jwt工具类,主要有decode解析,encode加密

    @RequestMapping(value = "login")
    @ResponseBody
    public String login(UserInfo userInfo, HttpServletRequest request){
        // 获取linux服务器的ip=192.168.67.1,反正就是获取ip,
        String ip = request.getHeader("X-forwarded-for");
        // 用户名+密码进行验证 select * from user_info where uname = ? and pwd = ?
        UserInfo loginUser = userInfoService.login(userInfo);
        if (loginUser!=null){
            // 做token -- JWT
            HashMap<String, Object> map = new HashMap<>();
            map.put("userId",loginUser.getId());
            map.put("nickName",loginUser.getNickName());

            String token = JwtUtil.encode(key, map, ip);//加密  随后会进行解密 具体看JWTutil.calss
            return token;
        }else {
            return "fail";
        }
    }

下面步骤,通过解析token字符串,的userId,通过id去redis 中进行查询,如果redis中有当前用户,则认证成功!否则fail!

为啥要做认证?为啥要做认证?为啥要做认证?下面就是认证!!
sso—一处登录,多出使用,其他模块只需要调用认证方法!
(说白了就是 到了解析信息 和 redis做比较了!)

在这里插入图片描述

感觉不如图片好看呢?
@Override
public UserInfo verify(String userId) {
    // 根据userId redis 中查数据
    Jedis jedis = redisUtil.getJedis();
    // 定义key
    String key = userKey_prefix+userId+userinfoKey_suffix;

    // 通过key 获取数据
    String userJson = jedis.get(key);
    // 因为认证操作相当于其他模块 ,在登录,就需要延长用户过期时间
    jedis.expire(key,userKey_timeOut);

    if (userJson!=null && userJson.length()>0){
        // 将字符串转换为对象
        UserInfo userInfo = JSON.parseObject(userJson, UserInfo.class);
        return userInfo;
    }
    return null;
}

以上就是 加密 解析 对比redis 对应的上 就将过期时间 延迟。表明用户还在使用啊。如果很长时间 没更新这个redis过期时间,表明 用户可能 出去了,不再使用了,自然 将这个key删除啊!
实现 登陆----认证-----
接下来 将是重中之重!

三、	业务模块页面登录情况检查
问题: 
1 、由认证中心签发的token如何保存? Cookie
2 、难道每一个模块都要做一个token的保存功能?gmall-web-util 
(在gmall-web-util项目登录成功后写入cookie。)
3 、如何区分请求是否一定要登录? 自定义注解

package com.atguigu.gmall0416.config;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.impl.Base64UrlCodec;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;

// 实现,继承 被spring 容器扫描
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
    // preHandle 进入控制器之前。
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 将生产token 放入cookie 中
        // http://item.gmall.com/32.html?newToken=eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6IkFkbWluaXN0cmF0b3IiLCJ1c2VySWQiOiIyIn0.WUvbFvXQnTMBGNyHWT-DE41MR9cn7c_W1oAtDAzb7VU
        String token = request.getParameter("newToken");
        if (token!=null){
            // 将token 放入cookie 中
            CookieUtil.setCookie(request,response,"token",token,WebConst.COOKIE_MAXAGE,false);
        }
        // 直接访问登录页面,当用户进入其他项目模块中。
        if (token==null){
            //  如果用户登录了,访问其他页面的时候不会有newToken,那么token 可能已经在cookie 中存在了
            token = CookieUtil.getCookieValue(request,"token",false);
        }
        // 已经登录的token,cookie 中的token。
        if (token!=null){
            // 去token 中的是有效数据,解密
            Map map = getUserMapByToken(token);
            String nickName = (String) map.get("nickName");
            request.setAttribute("nickName", nickName);
        }
        //这儿 !!是cookie中没有token,需要再去认证
        // Object handler
        // 获取方法,获取方法上的注解  判断时候有LoginRequire注解?干啥呢?
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        LoginRequire methodAnnotation = handlerMethod.getMethodAnnotation(LoginRequire.class);
        // 说明类上有注解!
        if (methodAnnotation!=null){
            //  必须要登录【调用认证】
            String remoteAddr = request.getHeader("x-forwarded-for");
            // 认证控制器在那个项目? 远程调用,
            String result = HttpClientUtil.doGet(WebConst.VERIFY_ADDRESS + "?token=" + token + "&currentIp=" + remoteAddr);
            if ("success".equals(result)){
                // 说明当前用户已经登录,保存userId : 购物车使用!
                Map map = getUserMapByToken(token);
                String userId = (String) map.get("userId");
                request.setAttribute("userId", userId);
                return true;
            } else {
                // fail
                if (methodAnnotation.autoRedirect()){
                    // 认证失败!重新登录!
                    /*http://passport.atguigu.com/index?originUrl=http%3A%2F%2Fitem.gmall.com%2F32.html*/
                    String requestURL  = request.getRequestURL().toString(); // http://item.gmall.com/28.html
                    // 进行加密编码
                    String encodeURL = URLEncoder.encode(requestURL, "UTF-8");
                    response.sendRedirect(WebConst.LOGIN_ADDRESS+"?originUrl="+encodeURL);
                    return false;
                }
            }
        }
        return true;
    }
    // 解密
    private Map getUserMapByToken(String token) {
        // eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6Im1hcnJ5IiwidXNlcklkIjoiMTAwMSJ9.TF1RTg_1TnkPNOAkA4Gq549iqwzsBplgeabpHvW15ng
        String tokenUserInfo = StringUtils.substringBetween(token, ".");
        // 使用了另一个类
        Base64UrlCodec base64UrlCodec = new Base64UrlCodec();
        byte[] bytes = base64UrlCodec.decode(tokenUserInfo);

        // 数组-- map
        // 字符串
        String str = null;
        try {
            str =  new String(bytes ,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 字符串--map
        Map map = JSON.parseObject(str, Map.class);
        return map;
    }
}

下面是添加拦截器的配置

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Autowired
    AuthInterceptor authInterceptor;
    public void addInterceptors(InterceptorRegistry registry) {
        /*拦截所有请求,也就是在所有的请求之前 都要进行 authInterceptor类方法 */
        registry.addInterceptor(authInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

在这里插入图片描述
最后 自定义个注解。

@Target(ElementType.METHOD)//作用在什么地方 
@Retention(RetentionPolicy.RUNTIME) //生命周期!
public @interface LoginRequire {
    // 默认设置
    boolean autoRedirect() default true;
}

在这里插入图片描述

里面有httpCilentUtil,cookieUtil,拦截器配置,非常的方便 对远程调用 也就是跨工程调用。还有就是 更好的操作 cookie 中的 token。还有拦截器配置,给每一个controller之前都添加 一个方法,通过一个自定义的注解 进行 判断,是否在方法上添加了这个注解,从而确定 是不是 要去认证!

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值