走进SSO

 

单点登录

什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分

例如在访问网易账号中心(http://reg.163.com/ )登录成功后

以下站点都是登录状态

单点登录解决的问题

  • 用户身份信息独立管理,更好的分布式管理
  • 可以自行拓展安全策略 
  • 解决跨域问题

 单点登录设计

 1.单点登录时序图

 

2.对上图的一个简要描述

  1. 用户访问web应用,发现此用户没有登录,重新定向到认证中心,并将自己的url作为参数传递
  2. 用户输入账号密码提交登录申请,认证中心去数据库核对用户信息
  3. 用户信息核对完成后存入缓存,并且生成token
  4. 用户带着token再次访问web应用,跳转到web应用功能页面,并将token存入cookie中
  5. 用户继续访问其他功能页面,首先检查cookie数据,然后拿着token重新提交认证中心
  6. 认证成功,跳转到业务功能页面

使用技术

  • cookie:缓存token信息
  • redis:缓存用户信息
  • JWT:JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案(防伪)
  • 拦截器:自定义拦截器,重写 preHandle方法
  • 自定义注解:判断用户是否登录
  • httpClientUtils :HTTP客户端编程工具包

SSO实现

1.所使用到部分的依赖

        <!--JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.4</version>
        </dependency>
        <!--es-->

2.拦截器 

package com.atguigu.commerce.config;


import com.alibaba.fastjson.JSON;
import com.atguigu.commerce.util.HttpClientUtil;
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 com.atguigu.commerce.config.CookieUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Map;

/**
 * @description
 * @Version:V1.0
 * @params
 * @return
 * @auther: zxj
 * @date: 2020/4/18 10:39
 */

@Component
public class AuthInterceptor extends HandlerInterceptorAdapter  {
    //用户进入控制器之前
    //多个拦截器执行顺序,跟拦截器的顺序有关系

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如何获取到token

        //用户在登录完成会获得token
        String token = request.getParameter("newToken");
        // 将token 放入cookie 中!
        //        Cookie cookie = new Cookie("token",token);
        //        response.addCookie(cookie);
        // 当token 不为null 时候放cookie
        if (token!=null){
            CookieUtil.setCookie(request,response,"token",token,WebConst.COOKIE_MAXAGE,false);
        }
        // 当用户访问非登录之后的页面,登录之后,继续访问其他业务模块时,url 并没有newToken,但是后台可能将token 放入了cookie 中!
        if (token==null){
            token = CookieUtil.getCookieValue(request,"token",false);

        }
        // 从cookie 中获取token,解密token!
        if (token!=null){
            // 开始解密token 获取nickName
            Map map = getUserMapByToken(token);
            // 取出用户昵称
            String nickName = (String) map.get("nickName");
            // 保存到作用域
            request.setAttribute("nickName",nickName);
        }

        // 在拦截器中获取方法上的注解!
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 获取方法上的注解LoginRequire
        LoginRequire methodAnnotation = handlerMethod.getMethodAnnotation(LoginRequire.class);
        if (methodAnnotation!=null){
            // 此时有注解 ,
            // 判断用户是否登录了? 调用verify
            // 获取服务器上的ip 地址
            String salt = request.getHeader("X-forwarded-for");
                    //"192.168.217.1";
            // 调用verify()认证 http://passport.atguigu.com/verify
            String result = HttpClientUtil.doGet(WebConst.VERIFY_ADDRESS + "?token=" + token + "&salt=" + salt);
            if ("success".equals(result)){
                // 登录,认证成功!
                // 保存一下userId
                // 开始解密token 获取nickName
                Map map = getUserMapByToken(token);
                // 取出userId
                String userId = (String) map.get("userId");
                // 保存到作用域
                request.setAttribute("userId",userId);
                return true;
            }else {
                // 认证失败!并且 methodAnnotation.autoRedirect()=true; 必须登录
                if (methodAnnotation.autoRedirect()){
                    // 必须登录!跳转到页面
                    // 先获取到url
                    String requestURL  = request.getRequestURL().toString();
                    System.out.println("requestURL:"+requestURL); // http://item.gmall.com/36.html
                    // 将url 进行转换
                    // http%3A%2F%2Fitem.gmall.com%2F36.html
                    String encodeURL  = URLEncoder.encode(requestURL, "UTF-8");
                    System.out.println("encodeURL:"+encodeURL); //  http%3A%2F%2Fitem.gmall.com%2F36.html
                    // http://passport.atguigu.com/index?originUrl=http%3A%2F%2Fitem.gmall.com%2F36.html
                    //LOGIN_ADDRESS="http://passport.atguigu.com/index";
                    response.sendRedirect(WebConst.LOGIN_ADDRESS+"?originUrl="+encodeURL);
                    return false;
                }
            }
        }

        return true;
    }

    private Map getUserMapByToken(String token) {
        //解吗token获取map数据
        //1.获取token中间部分
        String tokenUserInfo = StringUtils.substringBetween(token, ".");
        //进行base64解吗
        Base64UrlCodec base64UrlCodec = new Base64UrlCodec();
        //得到byte数组
        byte[] decode = base64UrlCodec.decode(tokenUserInfo);
        //将获得decode转换为字符串
        String mapJson=null;
        try {
             mapJson = new String(decode, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return JSON.parseObject(mapJson,Map.class);

    }
    //用户进入控制器之后,视图渲染之前

//    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//    }
//    ///用户进入控制器之后,视图渲染之后
//    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//    }
}

 3.PassPortConller

package com.atguigu.commerce.passport.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.atguigu.commerce.bean.UserInfo;
import com.atguigu.commerce.passport.until.JwtUtil;
import com.atguigu.commerce.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;


@Controller
public class PassPortController {

    @Value("${token.key}")
    private  String key;
    @Reference
    private UserService userService;
    @RequestMapping("index")
    public String index(HttpServletRequest request){
        //获取originUrl
        String originUrl = request.getParameter("originUrl");
        //保存originUrl
        request.setAttribute("originUrl",originUrl);
        return "index";
    }

    @RequestMapping("login")
    @ResponseBody
    public String login( UserInfo userInfo,HttpServletRequest request){
        //获取salt部分,服务器IP地址
        String salt=request.getHeader("X-forwarded-for");
        //调用登录方法
        UserInfo info =userService.login(userInfo);
        if (info!=null)
        {
           //制作token

            HashMap<String, Object> map = new HashMap<>();
            map.put("userId",info.getId());
            map.put("nickName",info.getNickName());
            String token= JwtUtil.encode(key,map,salt);
            return  token;

        }else {
            return "fail";
        }

    }


    @RequestMapping("verify")
    @ResponseBody
    public  String verify(HttpServletRequest request){
        //获取IP
        //String salt=request.getHeader("X-forwarded-for");
        ;
        String token = request.getParameter("token");
        String salt = request.getParameter("salt");
        //String salt="192.168.217.1";
        //调用jwt工具类
        Map<String, Object> map = JwtUtil.decode(token, key, salt);

        if (map!=null && map.size()>0){
            //获取jserid
            String userId = (String) map.get("userId");

            UserInfo userInfo= userService.verify(userId);
            if (userInfo!=null){
                return "success";
            }else {
                return "fail";
            }
        }
        return "fail";
    }
}

4.ServiceImpl

package com.atguigu.commerce.user.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.atguigu.commerce.bean.UserAddress;
import com.atguigu.commerce.bean.UserInfo;
import com.atguigu.commerce.config.RedisUtil;
import com.atguigu.commerce.service.UserService;

import com.atguigu.commerce.user.mapper.UserAddressMapper;
import com.atguigu.commerce.user.mapper.UserinfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;


import java.util.List;


@Service  
public class UserServiceImpl implements UserService {
    public String userKey_prefix="user:";
    public String userinfoKey_suffix=":info";
    public int userKey_timeOut=60*60*24;


    @Autowired
    private UserinfoMapper userinfoMapper;
    @Autowired
    private UserAddressMapper userAddressMapper;
    @Autowired
    private RedisUtil redisUtil;
    @Override
    public List<UserInfo> findAll() {
        return userinfoMapper.selectAll();
    }

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress userAddress = new UserAddress();
        userAddress.setUserId(userId);
        return userAddressMapper.select(userAddress);
    }

    @Override
    public UserInfo login(UserInfo userInfo) {

        //查询数据库
        String passwd = userInfo.getPasswd();
        //对密码加密
        String newPwd = DigestUtils.md5DigestAsHex(passwd.getBytes());
        //将加密后的密码赋值给当前对象
        userInfo.setPasswd(newPwd);

        UserInfo info = userinfoMapper.selectOne(userInfo);
        if (info!=null){
            //获取jedis
            Jedis jedis = redisUtil.getJedis();
            //定义key
            String userKey=userKey_prefix+info.getId()+userinfoKey_suffix;
            //放入key
            jedis.setex(userKey,userKey_timeOut, JSON.toJSONString(info));
            //关闭jedis、
            jedis.close();
            return  info;
        }
        return null;
    }

    @Override
    public UserInfo verify(String userId) {
         Jedis jedis=null;
        try {
            //获取jedis
           jedis = redisUtil.getJedis();
            //定义key
            String userKey=userKey_prefix+userId+userinfoKey_suffix;

            String userJson = jedis.get(userKey);

            if (!StringUtils.isEmpty(userJson)){
                //userJson转换为对象
                UserInfo userInfo = JSON.parseObject(userJson, UserInfo.class);
                return  userInfo;
            }
        }catch (Exception e){

            e.printStackTrace();
        }finally {
            if (jedis!=null){
                jedis.close();
            }

        }

        return null;

    }


}

 

                                                                           登录完成                                                                                                                 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值