SpringBoot集成JWT验证方式

导读:本篇文章讲解 SpringBoot集成JWT验证方式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

  为什么要告别session?有这样一个场景,系统的数据量达到千万级,需要几台服务器部署,当一个用户在其中一台服务器登录后,用session保存其登录信息,其他服务器怎么知道该用户登录了?(单点登录),当然解决办法有,可以用spring-session。如果该系统同时为移动端服务呢?移动端通过url向后台要数据,如果用session,通过sessionId识别用户,万一sessionId被截获了,别人可以利用sessionId向后台要数据,就有安全隐患了。所以有必要跟session说拜拜了。服务端不需要存储任何用户的信息,用户的验证应该放在客户端,jwt就是这种方式!

什么是jwt?

最详细的是官网:https://jwt.io/ 

这里以java的ssm框架为例,集成jwt。

1.pom.xml 导入jwt的包

<!-- jwt -->
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>2.2.0</version>
</dependency>

2.编写jwt的工具类,有加密解密功能就好

import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.internal.com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

public class JWT {

  private static final String SECRET = "XX#$%()(#*!()!KL<><MQLMNQNQJQK sdfkjsdrow32234545fdf>?N<:{LWPW";   private static final String EXP = "exp";   private static final String PAYLOAD = "payload";   //加密,传入一个对象和有效期   public static <T> String sign(T object, long maxAge) {
  try {     final JWTSigner signer = new JWTSigner(SECRET);
    final Map<String, Object> claims = new HashMap<String, Object>();     ObjectMapper mapper = new ObjectMapper();     String jsonString = mapper.writeValueAsString(object);     claims.put(PAYLOAD, jsonString);     claims.put(EXP, System.currentTimeMillis() + maxAge);     return signer.sign(claims);   } catch(Exception e) {     return null;   } } //解密,传入一个加密后的token字符串和解密后的类型 public static<T> T unsign(String jwt, Class<T> classT) {
  
  final JWTVerifier verifier = new JWTVerifier(SECRET);   try {     final Map<String,Object> claims= verifier.verify(jwt);     if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {       long exp = (Long)claims.get(EXP);       long currentTimeMillis = System.currentTimeMillis();       if (exp > currentTimeMillis) {         String json = (String)claims.get(PAYLOAD);         ObjectMapper objectMapper = new ObjectMapper();         return objectMapper.readValue(json, classT);       }     }       return null;     } catch (Exception e) {       return null;     }   } }

3.jwt有了,ssm要如何去利用,用户验证的第一步是登录,登录时根据用户传来的username和password到数据库验证身份,如果合法,便给该用户jwt加密生成token

//处理登录

@RequestMapping(value="login", produces = "application/json; charset=utf-8")
public @ResponseBody ResponseData login(HttpServletRequest request, @RequestParam( "email") String email,@RequestParam("password") String password) {
  Login login = new Login();
  login.setEmail(email);
  login.setPassword(password);
  ResponseData responseData = ResponseData.ok();
  //先到数据库验证
  Integer loginId = userService.checkLogin(login);
  if(null != loginId) {
    User user = userService.getUserByLoginId(loginId);
    login.setId(loginId);
    //给用户jwt加密生成token
    String token = JWT.sign(login, 60L* 1000L* 30L);
    //封装成对象返回给客户端
    responseData.putDataValue("loginId", login.getId());
    responseData.putDataValue("token", token);
    responseData.putDataValue("user", user);
  }else{
    responseData = ResponseData.customerError();
  } 
  return responseData;
}

4.在用户登录时,把loginId和token返回给前台

以后用户每次请求时,都得带上这两个参数,后台拿到token后解密出loginId,与用户传递过来的loginId比较,如果相同,则说明用户身份合法。因为是每个登录过后的每个请求,这里用springmvc的拦截器做

<mvc:interceptors> 
  <mvc:interceptor> 
  <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> 
  <mvc:mapping path="/**" /> 
  <!-- /register 和 /login 不需要拦截--> 
  <mvc:exclude-mapping path="/register" />
  <mvc:exclude-mapping path="/login" />
  <bean class="com.xforce.charles.interceptor.TokenInterceptor"></bean> 
  </mvc:interceptor> 
    <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 --> 
</mvc:interceptors> 

5.拦截器代码

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;
import com.xforce.charles.model.Admin;
import com.xforce.charles.model.Login;
import com.xforce.charles.util.JWT;
import com.xforce.charles.util.ResponseData;

public class TokenInterceptor implements HandlerInterceptor{

public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception arg3)throws Exception {}
public void postHandle(HttpServletRequest request, HttpServletResponse response,Object handler, ModelAndView model) throws Exception {}

//拦截每个请求
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
  response.setCharacterEncoding("utf-8");
  String token = request.getParameter("token");
  ResponseData responseData = ResponseData.ok();
  //token不存在
  if(null != token) {
    Login login = JWT.unsign(token, Login.class);
    String loginId = request.getParameter("loginId");
    //解密token后的loginId与用户传来的loginId不一致,一般都是token过期
    if(null != loginId && null != login) {
      if(Integer.parseInt(loginId) == login.getId()) {
        return true;
      }
    else{
      responseData = ResponseData.forbidden();
      responseMessage(response, response.getWriter(), responseData);
      return false;
    }
  }else{
      responseData = ResponseData.forbidden();
      responseMessage(response, response.getWriter(), responseData);
      return false;
   }
  }else{
    responseData = ResponseData.forbidden();
    responseMessage(response, response.getWriter(), responseData);
    return false;
  }
}

//请求不通过,返回错误信息给客户端
private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseData responseData) {
  responseData = ResponseData.forbidden();
  response.setContentType("application/json; charset=utf-8"); 
  String json = JSONObject.toJSONString(responseData);
  out.print(json);
  out.flush();
  out.close();
}
}

6.注意点:用@ResponseBody返回json数据时,有时会有乱码,需要在springmvc的配置文件里面加以下配置(spring4以上)

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
      <bean class="org.springframework.http.converter.StringHttpMessageConverter">
        <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
      </bean>
  </mvc:message-converters>
</mvc:annotation-driven> 

7.最后分享一个类,用于返回给客户端的万能类,我觉得它可以满足一般的接口

import java.util.HashMap;
import java.util.Map;

public class ResponseData {

private final String message;
private final int code;
private final Map<String, Object> data = new HashMap<String, Object>();

public String getMessage() {
  return message;
}

public int getCode() {
  return code;
}

public Map<String, Object> getData() {
  return data;
}

public ResponseData putDataValue(String key, Object value) {
  data.put(key, value);
  return this;
}

private ResponseData(int code, String message) {
  this.code = code;
  this.message = message;
}

public static ResponseData ok() {
  return new ResponseData(200, "Ok");
}

public static ResponseData notFound() {
  return new ResponseData(404, "Not Found");
}

public static ResponseData badRequest() {
  return new ResponseData(400, "Bad Request");
}

public static ResponseData forbidden() {
  return new ResponseData(403, "Forbidden");
}

public static ResponseData unauthorized() {
  return new ResponseData(401, "unauthorized");
}

public static ResponseData serverInternalError() {
  return new ResponseData(500, "Server Internal Error");
}

public static ResponseData customerError() {
  return new ResponseData(1001, "customer Error");
}
}

  

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/70233.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!