1.在用戶登錄后,如果要訪問其他路徑下的資源的話,我們是否還需要再驗證一遍呢?而且我們登陸上系統(tǒng)長時間不操作的話還需不需要再次驗證?所以這種情況下就很需要token來實現(xiàn)登錄功能。并通過redis(redis是一個key-value存儲系統(tǒng),它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型))來存儲token信息。
功能描述:用戶登錄成功后,后臺返回一個token給調(diào)用者,同時自定義一個@AuthToken注解,被該注解標注的API請求都需要進行token效驗,效驗通過才可以正常訪問,實現(xiàn)接口級的鑒權控制。同時token具有生命周期,在用戶持續(xù)一段時間不進行操作的話,token則會過期,用戶一直操作的話,則不會過期。
?2.流程分析
(1)客戶端登錄,輸入用戶名和密碼,在后端數(shù)據(jù)庫進行驗證,如果驗證失敗則返回登陸失敗的提示。如果成功了則生成token,并將token和用戶名雙向綁定,然后存入redis,同時使用token+username作為key把當前的時間戳存入redis。并設置他們的過期時間。
(2)然后設置攔截器,每一個被@AuthToken
注解標注的接口,都要首先檢查客戶端傳過來的Authorization
字段,獲取token。由于token與username雙向綁定,可以通過獲取的token來嘗試從redis中獲取username,如果可以獲取則說明token正確,登陸成功;反之,則說明失敗。
(3)token可以根據(jù)用戶的使用時間來動態(tài)的調(diào)整自己的過期時間。在生成 token 的同時也往 redis 里面存入了創(chuàng)建 token 時的時間戳,每次請求被攔截器攔截 token 驗證成功之后,將當前時間與存在 redis 里面的 token 生成時刻的時間戳進行比較,如果當前時間距離創(chuàng)建時間快要到達設置的redis過期時間的話,就重新設置token過期時間,將過期時間延長。如果用戶在設置的 redis 過期時間的時間長度內(nèi)沒有進行任何操作(沒有發(fā)請求),則token會在redis中過期。token過期后則會登陸失敗,重新輸入用戶名和密碼。
3.代碼實現(xiàn):
項目具體結構如下:
?
首先,先導入依賴:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
自定義注解的實現(xiàn):@Target?說明了Annotation所修飾的對象范圍,表示注解作用在方法和變量上,{ElementType.METHOD, ElementType.TYPE}表示注解作用在方法、類、接口上。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
}
然后設置攔截器:
/**
* @author 旺旺米雪餅
*/
@Slf4j
public class AuthorizationInterceptor implements HandlerInterceptor {
//存放鑒權信息的Header名稱,默認是Authorization
private String httpHeaderName = "Authorization";
//鑒權失敗后返回的錯誤信息,默認為401 unauthorized
private String unauthorizedErrorMessage = "401 unauthorized";
//鑒權失敗后返回的HTTP錯誤碼,默認為401
private int unauthorizedErrorCode = HttpServletResponse.SC_UNAUTHORIZED;
/**
* 存放登錄用戶模型Key的Request Key
*/
public static final String REQUEST_CURRENT_KEY = "REQUEST_CURRENT_KEY";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 如果打上了AuthToken注解則需要驗證token
if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
// String token = request.getHeader(httpHeaderName);
String token = request.getParameter(httpHeaderName);
log.info("Get token from request is {} ", token);
String username = "";
Jedis jedis = new Jedis();
if (token != null && token.length() != 0) {
username = jedis.get(token);
log.info("Get username from Redis is {}", username);
}
if (username != null && !username.trim().equals("")) {
//log.info("token birth time is: {}",jedis.get(username+token));
Long tokeBirthTime = Long.valueOf(jedis.get(token + username));
log.info("token Birth time is: {}", tokeBirthTime);
Long diff = System.currentTimeMillis() - tokeBirthTime;
log.info("token is exist : {} ms", diff);
//重新設置Redis中的token過期時間
if (diff > ConstantKit.TOKEN_RESET_TIME) {
jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);
jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
log.info("Reset expire time success!");
Long newBirthTime = System.currentTimeMillis();
jedis.set(token + username, newBirthTime.toString());
}
//用完關閉
jedis.close();
request.setAttribute(REQUEST_CURRENT_KEY, username);
return true;
} else {
JSONObject jsonObject = new JSONObject();
PrintWriter out = null;
try {
response.setStatus(unauthorizedErrorCode);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
jsonObject.put("code", ((HttpServletResponse) response).getStatus());
jsonObject.put("message", HttpStatus.UNAUTHORIZED);
out = response.getWriter();
out.println(jsonObject);
return false;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) {
out.flush();
out.close();
}
}
}
}
request.setAttribute(REQUEST_CURRENT_KEY, null);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
在工具類中設置token過期時間等信息:
/**
* @author 旺旺米雪餅
*/
public class ConstantKit {
/**
* 設置刪除標志為真
*/
public static final Integer DEL_FLAG_TRUE = 1;
/**
* 設置刪除標志為假
*/
public static final Integer DEL_FLAG_FALSE = 0;
/**
* redis存儲token設置的過期時間,10分鐘
*/
public static final Integer TOKEN_EXPIRE_TIME = 60 * 10;
/**
* 設置可以重置token過期時間的時間界限
*/
public static final Integer TOKEN_RESET_TIME = 1000 * 100;
}
并且設計Md5加密后的token值:
/**
* @author 旺旺米雪餅
*/
@Component
public class Md5TokenGenerator {
public String generate(String... strings) {
long timestamp = System.currentTimeMillis();
String tokenMeta = "";
for (String s : strings) {
tokenMeta = tokenMeta + s;
}
tokenMeta = tokenMeta + timestamp;
String token = DigestUtils.md5DigestAsHex(tokenMeta.getBytes());
return token;
}
}
別忘了在WebConfig中添加攔截器。文章來源:http://www.zghlxwxcb.cn/news/detail-405986.html
最后在controller層中進行驗證:文章來源地址http://www.zghlxwxcb.cn/news/detail-405986.html
@RestController
public class Welcome {
Logger logger = LoggerFactory.getLogger(Welcome.class);
@Autowired
Md5TokenGenerator tokenGenerator;
@Autowired
UserMapper userMapper;
@GetMapping("/welcome")
public String welcome(){
return "welcome token authentication";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ResponseTemplate login(String username, String password) {
logger.info("username:"+username+" password:"+password);
User user = userMapper.getUser(username,password);
logger.info("user:"+user);
JSONObject result = new JSONObject();
if (user != null) {
Jedis jedis = new Jedis();
String token = tokenGenerator.generate(username, password);
jedis.set(username, token);
//設置key生存時間,當key過期時,它會被自動刪除,時間是秒
jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);
jedis.set(token, username);
jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
Long currentTime = System.currentTimeMillis();
jedis.set(token + username, currentTime.toString());
//用完關閉
jedis.close();
result.put("status", "登錄成功");
result.put("token", token);
} else {
result.put("status", "登錄失敗");
}
return ResponseTemplate.builder()
.code(200)
.message("登錄成功")
.data(result)
.build();
}
@RequestMapping(value = "test", method = RequestMethod.GET)
@AuthToken
public ResponseTemplate test() {
logger.info("已進入test路徑");
return ResponseTemplate.builder()
.code(200)
.message("Success")
.data("test url")
.build();
}
}
到了這里,關于Springboot+redis實現(xiàn)token的登錄認證的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!