单点登录--微服务的登录解决方案

 既然聊到了单点登录,呢么我们就来看看到底什么是单点登录。

看一下官方的解释:

单点登录又称为sso(Single Sign On) ,就是通过用户的一次鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这就意味着在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗。

其实说的简单点,就是将用户的信息存储起来,然后在访问一个系统中别的界面的时候,验证一下是否有这个信息,如果有的话就可以访问,反之回到登录界面 ,这个功能看起来很简单,但是如果放在分布式微服务系统中就没有这么简单了。

在传统的单体应用架构中,我们的确可以很简单的完成这个功能,我们只需要将我们的登录验证信息放到session中就可以了,每个网页都可以访问到session中的信息。

但是如果是在分布式集群中就没有这么简单了。我们都知道session的作用域是一个会话,不同微服务之间不是同一个会话,自然也就不可以共享数据了,当然我们可以让session共享。

接下来我们就正式说一下sso的几种实现方式:

  • tomcat广播模式
  • 使用redis+cokkie---在某个模块登录成功过后,会把信息存放在两个地方
  1. redis: 存放一个key  value    key:随机的一个字符串;value: 用户的信息
  2. cookie: 存放的是redis的key值。

     因为啊,cookie是存在于浏览器中的,无论如何都是共享的,所以我们可以根据请求来的cookie进而得到key值,进而从redis中查询到value。

  • 使用token(token的中文解释是令牌的意思,就是按照一定的规则生成的字符串,字符串中包含用户的信息)

在某个模块登录成功后,会按照一定的规则生成一个token,包含着用户的信息,我们把这个token放在cookie或者http头里边都可以让每一个微服务获取到。

 

下边就以token的方式来介绍一下单点登录。

先说整体的思路:

  1、 前端界面进行登录,将用户的账号和密码输入,我们后台人员拿到。拿到之后对账号和密码在数据库里进行验证,如果正确的话,就将用户的一些信息封装成token(类似于一种加密手段),传回给前台。

2、前台拿到token之后,将token放到cookie中,通过前端的统一过滤器,将cookie中的token放到http的请求头中去(为了方便后台拿取数据)

3、此时,无论哪一个界面对后台进行访问,如果登录的话,就都会带有一个token了,我们后台就可以通过token做一些事情,比如通过token拿取用户的额数据。我们通过反编译,将token中的内容拿取出来,拿到里边的载荷(一般为用户的id)然后根据这个数据取数据库中查取信息返回给前台响应。

 

进入正题: 看代码:

  1、 前端界面进行登录,将用户的账号和密码输入,我们后台人员拿到。拿到之后对账号和密码在数据库里进行验证,如果正确的话,就将用户的一些信息封装成token(类似于一种加密手段),传回给前台。

    @ApiOperation("登录函数,将用户信息封装到token中并且返回,助力前端放到httphead中")
    public R login(@RequestBody LoginVo loginVo){
       String token= ucenterMemberService.login(loginVo);

        return R.ok().data("token",token);
    }
   @Override
    public String login(LoginVo loginVo) {
        if (StringUtils.isEmpty(loginVo.getMobile())||StringUtils.isEmpty(loginVo.getPassword())){
            throw new AAAException("用户名或者密码不能为空",2005);
        }
        UcenterMember ucenterMember = this.getOne(new QueryWrapper<UcenterMember>().eq("mobile", loginVo.getMobile()));
        if (ucenterMember==null){
            throw new AAAException("用户名不存在",2005);
        }
        String encrypt = MD5.encrypt(loginVo.getPassword());

        if (!StringUtils.equals(encrypt,ucenterMember.getPassword())){
            throw new AAAException("密码不正确",2005);
        }
        String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
        return jwtToken;
    }

看一下token方法类:

package com.jkt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author
 */


public class JwtUtils {

    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 得到你的token值
     * @param id
     * @param nickname
     * @return
     */
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT") // token 的类型 JWT
                 .setHeaderParam("alg", "HS256")  // 算法名称

                .setSubject("AAA-user") // 用户
                .setIssuedAt(new Date())  //载荷
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)  // id
                .claim("nickname", nickname) // 昵称   //不要放置一些隐秘。不安全的
                .signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名:Base64Url加密
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) {return false; }
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) {return false;}
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token"); //获取request中header的信息
        if(StringUtils.isEmpty(jwtToken)) {return "";}
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); // 从jwt中拿到载体
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

2、前台拿到token之后,将token放到cookie中,通过前端的统一过滤器,将cookie中的token放到http的请求头中去(为了方便后台拿取数据)


// http request 拦截器
service.interceptors.request.use(
  config => {
  //debugger
  if (cookie.get('guli_token')) {
    config.headers['token'] = cookie.get('guli_token');
  }
    return config
  },
  err => {
  return Promise.reject(err);
})
// http response 拦截器
service.interceptors.response.use(
  response => {
    //debugger
    if (response.data.code == 28004) {
        console.log("response.data.resultCode是28004")
        // 返回 错误代码-1 清除ticket信息并跳转到登录页面
        //debugger
        window.location.href="/login"
        return
    }else{
      if (response.data.code !== 20000) {
        //25000:订单支付中,不做任何提示
        if(response.data.code != 25000) {
          Message({
            message: response.data.message || 'error',
            type: 'error',
            duration: 5 * 1000
          })
        }
      } else {
        return response;
      }
    }
  },
  error => {
    return Promise.reject(error.response)   // 返回接口返回的错误信息
});

3、此时,无论哪一个界面对后台进行访问,如果登录的话,就都会带有一个token了,我们后台就可以通过token做一些事情,比如通过token拿取用户的额数据。我们通过反编译,将token中的内容拿取出来,拿到里边的载荷(一般为用户的id)然后根据这个数据取数据库中查取信息返回给前台响应。

   @ApiOperation("根据token信息查询数据库内用户的详细信息")
    /*
    需要注意的是,这个token是需要从http请求中拿到的,拿到原始的token信息之后呢,我们要通过jwt工具类进行解析
    解析出来用户id以后,根据id去数据库内查询具体的用户所有数据,并且封装返回给前台,即可完成单点登录
    * */
    @GetMapping("getUserInfo")
    public R getUserInfo(HttpServletRequest request){
        String memberIdByJwtToken = JwtUtils.getMemberIdByJwtToken(request); // 这个方法的作用就是从http的头信息中拿去id值
        UcenterMember memberServiceById = ucenterMemberService.getById(memberIdByJwtToken);
        return R.ok().data("member",memberServiceById);
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页