jwt的用处:一般用于用户认证,前后端分离的项目,微信小程序,app 等,可以说只要是要登录的都要用到,具体的作用就是生成token,将token返回给浏览器,在用户在下次登录时带回token会来,服务端再对token进行校验,支持实现了跨域请求。
先说下普通跨域身份验证
过程如下:
用户向服务器发送请求包含用户名和密码。验证服务器后,相关数据(如用户信息等)将保存在当前会话中。服务器向用户返回session_id,session信息都会写入到用户的Cookie。用户的每个请求通过在Cookie中取出session_id传给服务器。服务器收到session_id并对比之前保存的数据,确认用户的身份。
JWT的基本原理,基本流程如下:
-
客户端使用账号和密码请求登录接口。
-
登录成功后服务器使用签名密钥生成JWT,然后返回JWT给客户端。
-
客户端再次向服务端请求其他接口时会带上JWT。
-
服务器接收到JWT后验证签名的有效性,对客户端做出相应的响应。
但是如果说使用多个服务器时候,网上说这好像就会出问题。在单点登录的时候如果使用cookie或写入redis持久化session数据,但是万一持久化又出问题了呢,或者系统出问题了咋办,而且感觉改个redis啥的好懒得搞会改逻辑层好多代码,因此我这里我学习了jwt。
先了解下jwt的功能
https://blog.csdn.net/weixin_42873937/article/details/82460997
下面是jwt的小demo实现
先建一个springboot的项目引入包以及jisontoken包
<dependency>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql连接的jar包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
修改配置文件
server.port=8089
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
controller层,根据用户名查找登录信息是否对,如果对生成token,并记录下登录时间,方便在其第二第登录操作时候通过比较时间看token是否失效,我在这里没有验证
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
//注册或登录
@RequestMapping("/login")
@Transactional
public UserResponse login(User user){
String username = user.getUsername();
String password = user.getPassword();
//TODO 检验参数的完整性
UserResponse userResponse = new UserResponse();
User tUser = userRepository.findByUsername(username);
//检验username是否存在
user.setLastLoginTime(new Date());
if(tUser!=null){
//检验密码是否正确
if(!tUser.getPassword().equals(password)) {
userResponse.setErrorNum(Constants.ERR_NUM_PWD_ERR);
userResponse.setErrorMsg(Constants.ERR_MSG_PWD_ERR);
return userResponse;
}
userRepository.updateLastLoginTimeByUserName(user.getLastLoginTime(),username);
}else {
try {
tUser = userRepository.save(user);
} catch (Exception e) {
userResponse.setErrorNum(Constants.ERR_NUM_SERVER_ERR);
userResponse.setErrorMsg(Constants.ERR_MSG_SERVER_ERR);
return userResponse;
}
}
userResponse.setErrorNum(Constants.ERR_NUM_OK);
userResponse.setErrorMsg(Constants.ERR_MSG_OK);
userResponse.setUserName(username);
userResponse.setUserId(tUser.getId());
userResponse.setToken(JwtUtil.generateToken(username,user.getLastLoginTime()));
return userResponse;
}
其他登录:
package com.yccj.controller;
import com.yccj.util.JwtUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
*功能描述
*@author hua_wen
*@date 下午 08:10 2019/12/28
*@param
*@return
*/
@RestController
public class HelloController {
@RequestMapping("/otherLongin")
public Map login(HttpServletRequest request){
String token = request.getParameter("token");
return JwtUtil.validateToken(token);
}
}
实体类
package com.yccj.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;
/**
*功能描述
*@author hua_wen
*@date 下午 07:40 2019/12/28
*@param
*@return
*/
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)//用于标注主键的生成策略,通过strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略
private Integer id;
private String username;
private String password;
private Date lastLoginTime;
}
jwt工具类
package com.yccj.util;
import com.yccj.repository.UserRepository;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
*功能描述
*@author hua_wen
*@date 下午 07:28 2019/12/28
*@param
*@return
*/
@Component
public class JwtUtil {
private static UserRepository userRepository;
@Autowired
public JwtUtil(UserRepository userRepository) {
JwtUtil.userRepository = userRepository;
}
public static final long EXPIRATION_TIME = 3600_000_000L; // 1000 hour
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public static String generateToken(String username,Date generateTime) {
HashMap<String, Object> map = new HashMap<>();
//you can put any data in the map
map.put("username", username);
map.put("generateTime",generateTime);
String jwt = Jwts.builder()
.setClaims(map)
.setExpiration(new Date(generateTime.getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
return jwt;
}
/**
*
* @param token
* @return
*/
public static Map<String,Object> validateToken(String token) {
Map<String,Object> resp = new HashMap<String,Object>();
if (token != null) {
// parse the token.
try {
Map<String, Object> body = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
String username = (String) (body.get("username"));
Date generateTime = new Date((Long)body.get("generateTime"));
if(username == null || username.isEmpty()){
resp.put("ERR_MSG",Constants.ERR_MSG_USERNAME_EMPTY);
return resp;
}
//账号在别处登录
if(userRepository.findByUsername(username).getLastLoginTime().after(generateTime)){
resp.put("ERR_MSG",Constants.ERR_MSG_LOGIN_DOU);
return resp;
}
resp.put("username",username);
resp.put("generateTime",generateTime);
return resp;
}catch (SignatureException | MalformedJwtException e) {
// TODO: handle exception
// don't trust the JWT!
// jwt 解析错误
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_ERR);
return resp;
} catch (ExpiredJwtException e) {
// jwt 已经过期,在设置jwt的时候如果设置了过期时间,这里会自动判断jwt是否已经过期,如果过期则会抛出这个异常,我们可以抓住这个异常并作相关处理。
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_EXP);
return resp;
}
}else {
resp.put("ERR_MSG",Constants.ERR_MSG_TOKEN_EMPTY);
return resp;
}
}
}
网页错误信息
package com.yccj.util;
/**
*功能描述
*@author hua_wen
*@date 下午 07:28 2019/12/28
*@param
*@return
*/
public class Constants {
public static final int ERR_NUM_OK = 200;
public static final String ERR_MSG_OK = "成功";
public static final int ERR_NUM_USER_EXIST = 410;
public static final String ERR_MSG_USER_EXIST = "用户已存在";
public static final int ERR_NUM_PWD_ERR = 411;
public static final String ERR_MSG_PWD_ERR = "密码错误";
public static final int ERR_NUM_USERNAME_EMPTY = 412;
public static final String ERR_MSG_USERNAME_EMPTY = "用户名不能为空";
public static final int ERR_NUM_TOKEN_ERR = 413;
public static final String ERR_MSG_TOKEN_ERR = "Token解析错误";
public static final int ERR_NUM_TOKEN_EXP = 414;
public static final String ERR_MSG_TOKEN_EXP = "Token已过期";
public static final int ERR_NUM_TOKEN_EMPTY = 415;
public static final String ERR_MSG_TOKEN_EMPTY = "Token不能为空";
public static final int ERR_NUM_LOGIN_DOU = 416;
public static final String ERR_MSG_LOGIN_DOU = "已在别处登录";
public static final int ERR_NUM_SERVER_ERR = 500;
public static final String ERR_MSG_SERVER_ERR = "服务器错误";
}
查询类
package com.yccj.repository;
import com.yccj.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Date;
/**
*功能描述
*@author hua_wen
*@date 下午 07:49 2019/12/28
*@param
*@return
*/
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String name);
//@Query 与 @Modifying这两个annotation一起声明,可定义个性化更新操作
@Modifying
@Query("update User set lastLoginTime = :lastLoginTime where username =:username")
void updateLastLoginTimeByUserName(@Param("lastLoginTime")Date lastLoginTime, @Param("username")String username);
}
页面返回
package com.yccj.Response;
import lombok.Data;
/**
*功能描述
*@author hua_wen
*@date 下午 07:48 2019/12/28
*@param
*@return
*/
@Data
public class UserResponse extends BaseResponse{
private String userName;
private Integer userId;
private String token;
}
package com.yccj.Response;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
/**
*功能描述
*@author hua_wen
*@date 下午 07:48 2019/12/28
*@param
*@return
*/
@Data
public class BaseResponse {
private int errorNum;
private String errorMsg;
}
完成后访问界面并传入登录信息username和password
进行其他登录的时候会将刚刚返回页面的token请求后台可以得到
在此时如果前台页面不携带token会玩来给后台,那么后台就可以进行判断,然后进行权限拦截等等操作
在后台传回来的token,网页也会保存在cokie中,在前端可以获得token并每次携带会服务端
学如逆水行舟,不进则退