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

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

看一下官方的解释:

单点登录又称为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);
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分布式事务在微服务架构中的发展历程可以分为以下几个阶段: 1. 单体应用阶段:在传统的单体应用架构中,事务管理是相对简单的,通常使用数据库本身提供的事务机制来保证数据的一致性。这种方式在单体应用中能够很好地工作,但随着业务的增长和复杂性的提升,单体应用面临着可扩展性和灵活性的限制。 2. 集中式事务管理阶段:随着微服务架构的兴起,应用被拆分成多个小型服务,每个服务有自己独立的数据库。在这种情况下,跨服务的事务管理变得复杂起来。集中式事务管理器应运而生,例如使用分布式事务管理器(如XA协议)来协调多个服务之间的事务操作。这种方式能够保证数据的一致性,但也带来了性能和可用性的挑战。 3. 最终一致性解决方案阶段:由于集中式事务管理存在一些问题,例如性能瓶颈、单点故障等,逐渐出现了一些基于最终一致性的解决方案。最终一致性指的是,在一段时间后,系统中的所有副本最终都会达到一致的状态。这种方式通过使用消息队列、事件驱动等机制来解耦服务之间的依赖关系,并使用补偿机制来处理异常情况,从而实现了分布式事务的处理。 4. Saga模式阶段:Saga是一种用于处理长时间和复杂事务的模式。它将事务拆分成一系列小的、可回滚的事务片段,每个事务片段对应一个服务的操作。每个事务片段都有自己的补偿操作,用于撤销之前的操作。Saga模式通过协调不同服务之间的事务片段来实现分布式事务的一致性。 需要注意的是,以上阶段并不是严格的时间顺序,不同的组织和系统可能会在不同的阶段采用不同的解决方案。此外,随着技术的发展和实践经验的积累,可能还会出现新的解决方案和模式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值