七、微服務(wù)之用戶服務(wù)

一晃择、用戶服務(wù)基于JWT的token認證實現(xiàn)

傳統(tǒng)的session身份認證
  • 缺點:session存儲在內(nèi)存中冀值,這樣就不能跨實例共享,當(dāng)下一次請求分發(fā)到另外的實例中宫屠,就要重新登陸列疗;
  • session是依賴于瀏覽器的cookie,當(dāng)移動端訪問的時候就很難支持浪蹂。


    基于token的認證
  • 優(yōu)點:1保證了服務(wù)的無狀態(tài)抵栈,因為用戶信息都是存在分布式緩存中告材;
  • 2 .不依賴于token機制,可以根據(jù)與客戶端約定的協(xié)議來傳輸token古劲。


    基于JWT的身份認證(JSON WEB TOKENS)

    JWT身份認證

二斥赋、實現(xiàn)

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 表示將用戶的哪些數(shù)據(jù)設(shè)置到token里产艾,比如用戶的姓名或者email疤剑。這樣減少了數(shù)據(jù)庫的壓力,從token里獲取用戶信息
*/
  public static String genToken(Map<String, String> claims){
    try {
      //定義算法
      Algorithm algorithm = Algorithm.HMAC256(SECRET);
      //設(shè)置發(fā)布者 胰舆、過期時間信息
      JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addDays(new Date(), 1));
    //將用戶信息設(shè)置到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.不存在將從通過數(shù)據(jù)庫獲取用戶對象
   * 3.將用戶對象寫入緩存缠捌,設(shè)置緩存時間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;
  }

  /**
   * 發(fā)送注冊激活郵件
   * @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("房產(chǎn)平臺激活郵件", 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("房產(chǎn)平臺重置密碼郵件", 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);
  }

}
登陸驗證

鑒權(quán)

登出

2.4 jwt的優(yōu)勢

jwt的優(yōu)勢

2.5 jwt的缺點

jwt的缺點
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伴奥,一起剝皮案震驚了整個濱河市盛杰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倦零,老刑警劉巖误续,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扫茅,居然都是意外死亡蹋嵌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門葫隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栽烂,“玉大人,你說我怎么就攤上這事恋脚∠侔欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵糟描,是天一觀的道長怀喉。 經(jīng)常有香客問我,道長船响,這世上最難降的妖魔是什么躬拢? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮见间,結(jié)果婚禮上聊闯,老公的妹妹穿的比我還像新娘。我一直安慰自己米诉,他們只是感情好馅袁,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荒辕,像睡著了一般汗销。 火紅的嫁衣襯著肌膚如雪犹褒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天弛针,我揣著相機與錄音叠骑,去河邊找鬼。 笑死削茁,一個胖子當(dāng)著我的面吹牛宙枷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茧跋,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼慰丛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘾杭?” 一聲冷哼從身側(cè)響起诅病,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粥烁,沒想到半個月后贤笆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡讨阻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年芥永,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝吮。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡埋涧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奇瘦,到底是詐尸還是另有隱情飞袋,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布链患,位于F島的核電站,受9級特大地震影響瓶您,放射性物質(zhì)發(fā)生泄漏麻捻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一呀袱、第九天 我趴在偏房一處隱蔽的房頂上張望贸毕。 院中可真熱鬧,春花似錦夜赵、人聲如沸明棍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摊腋。三九已至沸版,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兴蒸,已是汗流浹背视粮。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留橙凳,地道東北人蕾殴。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像岛啸,于是被迫代替她去往敵國和親钓觉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 微服務(wù)架構(gòu)介紹 1.1 什么是微服務(wù)架構(gòu)坚踩? 形像一點來說荡灾,微服務(wù)架構(gòu)就像搭積木,每個微服務(wù)都是一個零件堕虹,并使...
    靜修佛緣閱讀 6,645評論 0 39
  • 轉(zhuǎn)載本文需注明出處:微信公眾號EAWorld卧晓,違者必究。 本文目錄: 一赴捞、單體應(yīng)用 VS 微服務(wù) 二逼裆、微服務(wù)常見安...
    72a1f772fe47閱讀 8,557評論 3 25
  • 本文目錄:一、單體應(yīng)用 VS 微服務(wù)二赦政、微服務(wù)常見安全認證方案三胜宇、JWT介紹四、OAuth 2.0 介紹五恢着、思考總...
    挨踢的懶貓閱讀 17,975評論 5 29
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理桐愉,服務(wù)發(fā)現(xiàn),斷路器掰派,智...
    卡卡羅2017閱讀 134,664評論 18 139
  • 劉詩雯有很多外號 什么劉萌萌啊 酸酸啊 小杏啊 這其中 最出名的還是從小跟到大的外號:棗 但其實 這么多外號里 劉...
    旻Queenie閱讀 1,117評論 0 5