一晃择、用戶服務(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的缺點