告别session jwt使用初尝试

来源:互联网 发布:linux菜鸟私房菜 编辑:程序博客网 时间:2024/06/06 03:31

为什么要告别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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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;        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

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>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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");    }}
原创粉丝点击