java实现基于JWT的token认证
程序员文章站
2022-07-03 09:02:56
...
java实现基于JWT的token认证
1.引入依赖
<!--引入JWT依赖,由于是基于Java,所以需要的是java-jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.创建JWT工具类
public class JwtUtil {
//jwt过期时间
private final static Long ttlMillis = 1000 * 60 * 60l;
/**
* 用户登录成功后生成Jwt
* 使用Hs256算法 私匙使用用户密码
*
* @param user 登录成功的user对象
* @return
*/
public static String createJWT(User user) {
//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("id", user.getId());
claims.put("username", user.getUsername());
claims.put("password", user.getPassword());
//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
String key = user.getPassword();
//生成签发人
String subject = user.getUsername();
//下面就是在为payload添加各种标准声明和私有声明了
//这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(UUID.randomUUID().toString())
//iat: jwt的签发时间
.setIssuedAt(now)
//代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
.setSubject(subject)
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
//设置过期时间
builder.setExpiration(exp);
}
return "Bearer "+builder.compact();
}
/**
* Token的解密
* @param token 加密后的token
* @param user 用户的对象
* @return
*/
public static Claims parseJWT(String token, User user) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校验token
* 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
* @param token
* @param user
* @return
*/
public static Boolean isVerify(String token, User user) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("password").equals(user.getPassword())) {
return true;
}
return false;
}
}
3.创建JWT注解类
/**
* 使用此注解的方法不进行token校验
*/
//@Target注解(注解解释:这个注解标注我们定义的注解是可以作用在类上还是方法上还是属性上面)
@Target({ElementType.METHOD, ElementType.TYPE})
//@Retention注解(注解解释:作用是定义被它所注解的注解保留多久,一共有三种策略,
// 1. SOURCE 被编译器忽略,
// 2.CLASS 注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是默认行为,所有没有用Retention注解的注解,都会采用这种策略。
// 3.RUNTIME 保留至运行时。所以我们可以通过反射去获取注解信息。
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
//boolean required() default true; 默认required() 属性为true
boolean required() default true;
}
/**
* 使用此注解的方法会进行token校验
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
boolean required() default true;
}
4.创建用于JWT验证的拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=utf-8");
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有PassToken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken loginToken = method.getAnnotation(PassToken.class);
if (loginToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(CheckToken.class)) {
// 从 http 请求头中取出 token,前端发送求时需在header中添加Authorization头信息,值为token字符串
String token = request.getHeader("Authorization").replace("Bearer ","");
CheckToken checkToken = method.getAnnotation(CheckToken.class);
if (checkToken.required()) {
// 执行认证
if (token == null) {
returnJsonResult(response, 401, "无token,请重新登录");
}
// 获取 token 中的 user id
Integer userId;
try {
userId = JWT.decode(token).getClaim("id").asInt();
} catch (JWTDecodeException j) {
returnJsonResult(response, 401, "访问异常");
throw new RuntimeException("访问异常!");
}
User user = userService.findById(userId);
if (user == null) {
returnJsonResult(response, 401, "用户不存在,请重新登录");
throw new RuntimeException("用户不存在,请重新登录");
}
Boolean verify = JwtUtil.isVerify(token, user);
if (!verify) {
returnJsonResult(response, 400, "非法访问");
throw new RuntimeException("非法访问!");
}
return true;
}
}
return true;
}
/**
* 用于向前端发送验证结果
* @param response
* @param code
* @param msg
* @throws IOException
*/
void returnJsonResult(HttpServletResponse response, Integer code, String msg) throws IOException {
PrintWriter out = response.getWriter();
ResponseResult result = new ResponseResult(null, code, msg);
ObjectMapper mapper = new ObjectMapper();//转换器
String jsonResult = mapper.writeValueAsString(result);//将对象转换成json
out.print(jsonResult);
out.flush();
out.close();
}
}
5.在SpringMVC.xml中配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.baixingyuan.interceptor.AuthenticationInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
6.使用刚才创建的注解类
@PostMapping("/login")
@PassToken //使用此注解将会跳过验证
public ResponseResult login(@RequestBody User user){
@DeleteMapping("/{newId}")
@CheckToken //使用此注解将会验证token
public ResponseResult delete(@PathVariable("newId") Integer newId){
7.测试
1.测试登录
1.使用postman发送登录请求
2.登录成功,返回token
2.获取用户列表
1.发送请求
2.验证成功,返回数据
上一篇: less入门
下一篇: CentOS7部署OpenStack