
JWT 的原理
基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。
token应用流程为:
1、初次登录:用户初次登录,输入用户名密码。
2、密码验证:服务器从数据库取出用户名和密码进行验证。
3、生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT。
4、返还JWT:服务器的HTTP RESPONSE中将JWT返还。
5、带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorization字段都要有值,为JWT,用来验证用户身份以及对路由,服务和资源的访问权限进行验证。请求验证的url可以例如:http://127.0.0.1:8083/change/goodsMenu? token=JWT
JWT 是jsonWebToken 简称,现在流行的前后端分离开发(jwt不止是运用于web应用,app,小程序,H5混合开发都可)基本上都使用jwt来验证用户token,减少了服务器session 的使用,也能减轻服务器的压力,是当下Java开发不二选择
更多的关于jwt的东西都可自行百度,这里就不多赘述
使用JWT
1、添加依赖
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.2.0</version>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
2、找到一个合适的Jwt加密工具
网上的加密工具都不一样,但达到的目的都是一样的
- @Component
- public class JwtTokenUtil {
-
- private static final long serialVersionUID = -3301605591108950415L;
-
- private String secret = ParamUtil.getValue("jwt_secret");
-
- private Long expiration = Long.valueOf(ParamUtil.getValue("jwt_expiration"));
-
- private String tokenHeader = ParamUtil.getValue("jwt_tokenHeader");
-
- private Clock clock = DefaultClock.INSTANCE;
-
- public String generateToken(UserInfo userInfo) {
- Map<String, Object> claims = new HashMap<>();
- return doGenerateToken(claims, userInfo.getName());
- }
-
- public String generateToken(UserInfo user, String userName) {
- Map<String, Object> claims = new HashMap<>();
- // 自定义参数,把需要存入token的参数都可以自定义到这里,比如 角色 部门类的关键参数
- claims.put("userId", user.getId());
- claims.put("phone", user.getName());
- return doGenerateToken(claims, userName);
- }
-
- private String doGenerateToken(Map<String, Object> claims, String subject) {
- final Date createdDate = clock.now();
- final Date expirationDate = calculateExpirationDate(createdDate);
-
- return Jwts.builder()
- .setClaims(claims) // 存入自定义参数
- .setSubject(subject)
- .setIssuedAt(createdDate)
- .setExpiration(expirationDate)
- .signWith(SignatureAlgorithm.HS512, secret)
- .compact();
- }
-
- private Date calculateExpirationDate(Date createdDate) {
- return new Date(createdDate.getTime() + expiration);
- }
- public String getUsernameFromToken(String token) {
- return getClaimFromToken(token, Claims::getSubject);
- }
-
- public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
- final Claims claims = getAllClaimsFromToken(token);
- return claimsResolver.apply(claims);
- }
-
- public Claims getAllClaimsFromToken(String token) {
- return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
- }
-
-
- private Boolean isTokenExpired(String token) {
- final Date expiration = getExpirationDateFromToken(token);
- return expiration.before(clock.now());
- }
-
- public Date getExpirationDateFromToken(String token) {
- return getClaimFromToken(token, Claims::getExpiration);
- }
- }
3、生产加密token,登录时返回
加密字符串可以得到了,接下来就是运用起来,在登录接口利用jwtTokenUtil 生成一个Token 并在登录接口一并返回
4、拦截器拦截并验证token
- @Component
- public class LoginInterceptor implements HandlerInterceptor {
-
- @Autowired
- private JwtTokenUtil jwtTokenUtil;
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- // 如果不是映射到方法直接通过
- if (!(handler instanceof HandlerMethod)) {
- return true;
- }
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- Method method = handlerMethod.getMethod();
- String servletPath = request.getServletPath();
- if (methodAnnotation != null) {
- // 判断是否存在令牌信息,如果存在,则允许登录
- //token_access 这就是前端放在请求头中的jwtToken
- String accessToken = request.getHeader("token_access");
- if (ToolUtil.isEmpty(accessToken)) {
- //这里抛给前端的为一个自定义异常枚举,可自己定义
- //RRException 为继承 RuntimeException 的自定义枚举
- throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
- }
- try {
-
- Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
- //通过jwt加密工具中自定义参数取出自己想要验证的值,来进行操作
- Long userId = Long.valueOf(claims.get("userId").toString());
- //这个可要可不要,通过这里自己在请求中加参数,可以让前端少传几个已经在token中自定义好的值
- request.setAttribute("userId", userId);
- //接下来可处理需要验证并且已经通过后的业务
-
- } catch (ExpiredJwtException e) {
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token已过期");
- ServletOutputStream out = response.getOutputStream();
- OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
- ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
- ow.flush();
- ow.close();
- return false;
- } catch (MalformedJwtException e) {
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token有误");
- ServletOutputStream out = response.getOutputStream();
- OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
- ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
- ow.flush();
- ow.close();
- return false;
- }
- return true;
- }
-
- }
处理类已经完成,接下来是添加拦截器 自定义类并且实现WebMvcConfigurer
- @Configuration
- @Slf4j
- public class MyInterceptor implements WebMvcConfigurer {
- /*
- @Autowired
- private LoginInterceptor loginInterceptor;*/
-
- /**
- * 重写添加拦截器方法并添加配置拦截器
- */
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(loginInterceptor());//添加拦截规则
- }
-
- @Bean
- public LoginInterceptor loginInterceptor (){
- return new LoginInterceptor();
- }
- }
到此就可以完成spring boot 对jwt的实现,也可以实现用户单点登录,这里就不细说。原理就是在用户登录时就在服务器中存入token,在拦截器中获取到并对比即可
* 另外说明,以上时全局拦截,如果有的同志只需要进行局部拦截的话,可以使用自定义注解的方式
Spring boot 中使用注解也时非常方便 如果有同志对注解不是很了解的话,可以百度一哈
1、创建注解类
2、使用注解
3、在拦截规则中使用注解
- Method method = handlerMethod.getMethod();
- // 通过获取注解的方法,判断有注解的方法则需要拦截验证
- YourAnnotation yourAnnotation = method.getAnnotation(YourAnnotation.class);
- // 有 @methodAnnotation 注解,需要认证
- if (yourAnnotation != null) {
- // 判断是否存在令牌信息,如果存在,则允许登录
- String accessToken = request.getHeader("token_access");
- if (ToolUtil.isEmpty(accessToken)) {
- throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
- }
- try {
- Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
-
- } catch (ExpiredJwtException e) {
- }
- }
这样就可以只验证加了自定义注解的接口,实现局部拦截


剑轩
转java了哇..