七、微服务之用户服务

一、用户服务基于JWT的token认证实现

传统的session身份认证
  • 缺点:session存储在内存中,这样就不能跨实例共享,当下一次请求分发到另外的实例中,就要重新登陆;
  • session是依赖于浏览器的cookie,当移动端访问的时候就很难支持。


    基于token的认证
  • 优点:1保证了服务的无状态,因为用户信息都是存在分布式缓存中;
  • 2 .不依赖于token机制,可以根据与客户端约定的协议来传输token。


    基于JWT的身份认证(JSON WEB TOKENS)

    JWT身份认证

二、实现

2.1 引入jwt的依赖

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java.jwt</artifactId>
</denpendency>

2.2 定义JwtHelper,来生成token

package com.mooc.house.user.utils;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;

import org.apache.commons.lang3.time.DateUtils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;

public class JwtHelper {
  
  private static final String  SECRET = "session_secret";
  
  private static final String  ISSUER = "mooc_user";
  
  /**
  * 生成token的方法
  *@Param claims 表示将用户的哪些数据设置到token里,比如用户的姓名或者email。这样减少了数据库的压力,从token里获取用户信息
*/
  public static String genToken(Map<String, String> claims){
    try {
      //定义算法
      Algorithm algorithm = Algorithm.HMAC256(SECRET);
      //设置发布者 、过期时间信息
      JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addDays(new Date(), 1));
    //将用户信息设置到token中
      claims.forEach((k,v) -> builder.withClaim(k, v));
      return builder.sign(algorithm).toString();
    } catch (IllegalArgumentException | UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  
  /**
  * 校验操作
*/
  public static Map<String, String> verifyToken(String token)  {
    Algorithm algorithm = null;
    try {
      algorithm = Algorithm.HMAC256(SECRET);
    } catch (IllegalArgumentException | UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
    JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
    DecodedJWT jwt =  verifier.verify(token);
    Map<String, Claim> map = jwt.getClaims();
    Map<String, String> resultMap = Maps.newHashMap();
    map.forEach((k,v) -> resultMap.put(k, v.asString()));
    return resultMap;
  }

}

2.3 service层操作

vpackage com.mooc.house.user.service;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.mooc.house.user.common.UserException;
import com.mooc.house.user.common.UserException.Type;
import com.mooc.house.user.mapper.UserMapper;
import com.mooc.house.user.model.User;
import com.mooc.house.user.utils.BeanHelper;
import com.mooc.house.user.utils.HashUtils;
import com.mooc.house.user.utils.JwtHelper;

@Service
public class UserService {

  @Autowired
  private StringRedisTemplate redisTemplate;
  
  @Autowired
  private UserMapper userMapper;
  
  @Autowired
  private MailService mailService;
  

  @Value("${file.prefix}")
  private String imgPrefix;
  
  /**
   * 1.首先通过缓存获取
   * 2.不存在将从通过数据库获取用户对象
   * 3.将用户对象写入缓存,设置缓存时间5分钟
   * 4.返回对象
   * @param id
   * @return
   */
  public User getUserById(Long id) {
    String key = "user:"+id;
    String json =  redisTemplate.opsForValue().get(key);
    User user = null;
    if (Strings.isNullOrEmpty(json)) {
      user =  userMapper.selectById(id);
      user.setAvatar(imgPrefix + user.getAvatar());
      String string  = JSON.toJSONString(user);
      redisTemplate.opsForValue().set(key, string);
      redisTemplate.expire(key, 5, TimeUnit.MINUTES);
    }else {
      user = JSON.parseObject(json,User.class);
    }
    return user;
  }

  public List<User> getUserByQuery(User user) {
    List<User> users = userMapper.select(user);
    users.forEach(u -> {
      u.setAvatar(imgPrefix + u.getAvatar());
    });
    return users;
  }

  /**
   * 注册
   * @param user
   * @param enableUrl
   * @return
   */
  public boolean addAccount(User user, String enableUrl) {
    user.setPasswd(HashUtils.encryPassword(user.getPasswd()));
    BeanHelper.onInsert(user);
    userMapper.insert(user);
    registerNotify(user.getEmail(),enableUrl);
    return true;
  }

  /**
   * 发送注册激活邮件
   * @param email
   * @param enableUrl
   */
  private void registerNotify(String email, String enableUrl) {
    String randomKey = HashUtils.hashString(email) + RandomStringUtils.randomAlphabetic(10);
    redisTemplate.opsForValue().set(randomKey, email);
    redisTemplate.expire(randomKey, 1,TimeUnit.HOURS);
    String content = enableUrl +"?key="+  randomKey;
    mailService.sendSimpleMail("房产平台激活邮件", content, email);
  }

  public boolean enable(String key) {
    String email = redisTemplate.opsForValue().get(key);
    if (StringUtils.isBlank(email)) {
       throw new UserException(UserException.Type.USER_NOT_FOUND, "无效的key");
    }
    User updateUser = new User();
    updateUser.setEmail(email);
    updateUser.setEnable(1);
    userMapper.update(updateUser);
    return true;
  }

  /**
   * 校验用户名密码、生成token并返回用户对象
   * @param email
   * @param passwd
   * @return
   */
  public User auth(String email, String passwd) {
    if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) {
      throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
    }
    User user = new User();
    user.setEmail(email);
    user.setPasswd(HashUtils.encryPassword(passwd));
    user.setEnable(1);
    List<User> list =  getUserByQuery(user);
    if (!list.isEmpty()) {
       User retUser = list.get(0);
       onLogin(retUser);
       return retUser;
    }
    throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
  }

  private void onLogin(User user) {
    String token =  JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts",Instant.now().getEpochSecond()+""));
    renewToken(token,user.getEmail());
    user.setToken(token);
  }

  private String renewToken(String token, String email) {
    redisTemplate.opsForValue().set(email, token);
    redisTemplate.expire(email, 30, TimeUnit.MINUTES);
    return token; 
  }

  public User getLoginedUserByToken(String token) {
    Map<String, String> map = null;
    try {
      map = JwtHelper.verifyToken(token);
    } catch (Exception e) {
      throw new UserException(Type.USER_NOT_LOGIN,"User not login");
    }
    String email =  map.get("email");
    Long expired = redisTemplate.getExpire(email);
    if (expired > 0L) {
      renewToken(token, email);
      User user = getUserByEmail(email);
      user.setToken(token);
      return user;
    }
    throw new UserException(Type.USER_NOT_LOGIN,"user not login");
    
  }

  private User getUserByEmail(String email) {
    User user = new User();
    user.setEmail(email);
    List<User> list = getUserByQuery(user);
    if (!list.isEmpty()) {
      return list.get(0);
    }
    throw new UserException(Type.USER_NOT_FOUND,"User not found for " + email);
  }

  public void invalidate(String token) {
    Map<String, String> map = JwtHelper.verifyToken(token);
    redisTemplate.delete(map.get("email"));
  }

  @Transactional(rollbackFor = Exception.class)
  public User updateUser(User user) {
     if (user.getEmail() == null) {
        return null;
     }
     if (!Strings.isNullOrEmpty(user.getPasswd()) ) {
        user.setPasswd(HashUtils.encryPassword(user.getPasswd()));
     }
     userMapper.update(user);
     return userMapper.selectByEmail(user.getEmail());
  }

  public void resetNotify(String email,String url) {
    String randomKey = "reset_" + RandomStringUtils.randomAlphabetic(10);
    redisTemplate.opsForValue().set(randomKey, email);
    redisTemplate.expire(randomKey, 1,TimeUnit.HOURS);
    String content = url +"?key="+  randomKey;
    mailService.sendSimpleMail("房产平台重置密码邮件", content, email);
    
  }

  public String getResetKeyEmail(String key) {
    return  redisTemplate.opsForValue().get(key);
  }

  public User reset(String key, String password) {
    String email = getResetKeyEmail(key);
    User updateUser = new User();
    updateUser.setEmail(email);
    updateUser.setPasswd(HashUtils.encryPassword(password));
    userMapper.update(updateUser);
    return getUserByEmail(email);
  }

}
登陆验证

鉴权

登出

2.4 jwt的优势

jwt的优势

2.5 jwt的缺点

jwt的缺点
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 1. 微服务架构介绍 1.1 什么是微服务架构? 形像一点来说,微服务架构就像搭积木,每个微服务都是一个零件,并使...
    静修佛缘阅读 6,628评论 0 39
  • 转载本文需注明出处:微信公众号EAWorld,违者必究。 本文目录: 一、单体应用 VS 微服务 二、微服务常见安...
    72a1f772fe47阅读 8,530评论 3 25
  • 本文目录:一、单体应用 VS 微服务二、微服务常见安全认证方案三、JWT介绍四、OAuth 2.0 介绍五、思考总...
    挨踢的懒猫阅读 17,946评论 5 29
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 刘诗雯有很多外号 什么刘萌萌啊 酸酸啊 小杏啊 这其中 最出名的还是从小跟到大的外号:枣 但其实 这么多外号里 刘...
    旻Queenie阅读 1,096评论 0 5