Springboot整合Jwt实现用户认证
前言
相信大家在进行用户认证中或多或少都要对用户进行认证,当前进行认证的方式有基于session、token等主流方式,但是目前使用最广泛的还是基于JWT的用户认证,特别适用于前后端分离的项目。
本篇博客我将简要的讲解JWT(Json web token)的只要知识点并实现简单的认证,如果我哪些方面有问题,请大家激烈评论,我好进一步修改!
1.Jwt的组成
将Jwt是由头部Header、载荷payload、以及签名sign三个部分组成
1.1 Herder
头部包含签名的生成token的'typ'以及'alo'也就是签名的类型和哪一种算法,还有签名中部分的编码格式
1.1 payload
载荷中存储部分用户的信息,但是最好不要将密码存储在载荷中,因为头部以及载荷安全性不高
1.1 sign
JWT的安全性体现在签名中,它是由头部、载荷以及盐进行Base64加密而成的,中间使用“.”连接,最后使用头部的编码格式编码得到签名。因为头部和载荷的安全性较低,所以我们主要通过盐来防止token伪造,一般通过
HMAC256算法对其进行加密
2.Jwt的认证流程
上述图详细地叙述了通过JWT进行认证的过程
1)用户第一次在浏览器网页里面输入用户名和密码进行认证,当客户端收到用户信息后对用户信息进行验证
2)客户端对用户信息验证无误后,返回一个token给浏览器
3)用户下次登录的时候携带这个token进行登录
4)客户端验证token无误后就放行
5)服务端返回用户访问客户端资源
3.Jwt的实现
3.1 生成token
private static final long EXPIRE_TIME = 1800L;//单位为秒 private static final String TOKEN_SECRET = "woToken"; /** * 生成token * @param sysUser * @return */ public static String sign(SysUser sysUser){ String token = null; try { Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME * 1000); Map<String, Object> map = new HashMap<>(); map.put("alg", "HS256"); map.put("typ", "JWT"); token = JWT.create() .withHeader(map)//添加头部 .withIssuer("auth0") .withClaim("uid", sysUser.getUid())//载体中设置uid .withClaim("username", sysUser.getUsername())//载体中设置username .withExpiresAt(expireDate)//设置过期时间 .withIssuedAt(new Date())//设置签发时间 .sign(Algorithm.HMAC256(TOKEN_SECRET));//secret加密 } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTCreationException e) { e.printStackTrace(); } return token; }
3.2 验证token
/** * 校验token并解析token */ public static Boolean verifyToken(String token){ try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build(); DecodedJWT decodedJWT = verifier.verify(token); return true; } catch (JWTVerificationException e) { e.printStackTrace(); return false; } catch (IllegalArgumentException e) { e.printStackTrace(); return false; } }
3.3 获取token中的uid
/** * 获取token中的payload信息 * @param token * @return */ public static Integer getUid(String token){ try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("uid").asInt(); } catch (JWTDecodeException e) { return null; } }
package com.ku.wo.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTCreationException; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.ku.wo.entity.SysUser; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 生成Jwt和认证 */ public class JwtUtils { private static final long EXPIRE_TIME = 1800L;//单位为秒 private static final String TOKEN_SECRET = "woToken"; /** * 生成token * @param sysUser * @return */ public static String sign(SysUser sysUser){ String token = null; try { Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME * 1000); Map<String, Object> map = new HashMap<>(); map.put("alg", "HS256"); map.put("typ", "JWT"); token = JWT.create() .withHeader(map)//添加头部 .withIssuer("auth0") .withClaim("uid", sysUser.getUid())//载体中设置uid .withClaim("username", sysUser.getUsername())//载体中设置username .withExpiresAt(expireDate)//设置过期时间 .withIssuedAt(new Date())//设置签发时间 .sign(Algorithm.HMAC256(TOKEN_SECRET));//secret加密 } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTCreationException e) { e.printStackTrace(); } return token; } /** * 校验token并解析token */ public static Boolean verifyToken(String token){ try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build(); DecodedJWT decodedJWT = verifier.verify(token); return true; } catch (JWTVerificationException e) { e.printStackTrace(); return false; } catch (IllegalArgumentException e) { e.printStackTrace(); return false; } } /** * 获取token中的payload信息 * @param token * @return */ public static Integer getUid(String token){ try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("uid").asInt(); } catch (JWTDecodeException e) { return null; } } }
3.4 拦截请求
package com.ku.wo.interceptor; import com.ku.wo.utils.JwtUtils; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 用于获取token并校验 */ /** * 单体项目验证token使用拦截器,分布式项目使用网关 */ @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // //不使用JWT使用的登录 //// if (request.getSession().getAttribute("uid") == null){ //// return false;//如果这里为false,后面的都不执行,并且postHandle()方法也不执行 //// } //// return true; if(HttpMethod.OPTIONS.toString().equals(request.getMethod())){ System.out.println("OPTIONS请求,放行!"); return true; } String token = request.getSession().getAttribute("token").toString(); System.out.println("拦截器的token获取"+token); // String token = request.getHeader("token"); if(JwtUtils.verifyToken(token)){ return true; } //失败时返回失败信息 return false; } }
3.5 拦截资源
package com.ku.wo.config; import com.ku.wo.interceptor.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.ArrayList; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired(required = false) LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //创建拦截器对象 //白名单 List<String> patterns = new ArrayList<>(); patterns.add("/user/reg"); patterns.add("/user/login"); registry.addInterceptor(loginInterceptor) .addPathPatterns("/**")//默认对所有请求进行拦截 .excludePathPatterns(patterns); } }
3.6 controller执行
@PostMapping("/login")//在浏览器上面应该改为get public JsonResult<String> login(@RequestParam(value = "name", required = false) String username, @RequestParam(value = "pwd", required = false) String password, HttpSession session){ SysUser data = userDetailsService.login(username, password); String token = JwtUtils.sign(data); session.setAttribute("token", token); session.setAttribute("user", data); session.setAttribute("uid",data.getUid()); session.setAttribute("username", username); return new JsonResult<String>(OK, token); } @PostMapping("/get_login_data")//在浏览器上面应该改为get public JsonResult<SysUser> getLoginData(String token, HttpSession session){ if(!session.getAttribute("token").equals(token)){ return new JsonResult<>(ERROR); } SysUser data = (SysUser)session.getAttribute("user"); return new JsonResult<SysUser>(OK, data); } //JSONResult响应类 package com.ku.wo.utils; import lombok.Data; import java.io.Serializable; @Data //原文使用的是<E>因为属性data属于元素,但是我在网上找到使用T貌似更好, // 尝试一下使用T的情况怎么样,List<T>表示集合中元素都为T类型,网上使用的大多数也是T public class JsonResult<T> implements Serializable { private Integer state; private String message; private T data; //必须要有无参构造 public JsonResult(){ super(); } public JsonResult(Integer state){ super(); this.state = state; } public JsonResult(Throwable e){//Throwable是error和Exception的父类,里面有getMessage()方法获取错误和异常信息 super(); this.message = e.getMessage(); } public JsonResult(Integer state, T data){ super(); this.state = state; this.data = data; } }
4.Jwt测试
5.参考链接
https://blog.csdn.net/m0_56966142/article/details/123398543?ops_request_misc=&request_id=&biz_id=102&utm_term=%E4%BD%BF%E7%94%A8JWT%E5%AE%9E%E7%8E%B0%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-123398543.142^v63^control_1,201^v3^control_2,213^v2^t3_esquery_v2&spm=1018.2226.3001.4187