spring+springmvc+Interceptor+jwt+redis实现sso单点登录

在分布式环境中,如何支持PC、APP(ios、android)等多端的会话共享,这也是所有公司都需要的解决方案,用传统的session方式来解决,我想已经out了,我们是否可以找一个通用的方案,比如用传统cas来实现多系统之间的sso单点登录或使用oauth的第三方登录方案? 今天给大家简单讲解一下使用spring拦截器Interceptor机制、jwt认证方式、redis分布式缓存实现sso单点登录,闲话少说,直接把步骤记录下来分享给大家:

1. 引入jwt的相关jar包,在项目pom.xml中引入:

<dependency>              <groupId>com.auth0</groupId>              <artifactId>java-jwt</artifactId>              <version>2.2.0</version>  </dependency>   

2. 拦截器配置:

<mvc:interceptor>              <mvc:mapping path="${adminPath}/**" />              <mvc:exclude-mapping path="${adminPath}/rest/login"/>              <bean class="com.ml.honghu.interceptor.LoginInterceptor" />  </mvc:interceptor>  

我这里简单配置了要拦截的url和过滤的url(这个根据自己项目来定)

3. 编写jwt的加密或者解密工具类:

public class JWT {      private static final String SECRET = "HONGHUJWT1234567890QWERTYUIOPASDFGHJKLZXCVBNM";        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;          }      }        //解密      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)) {                    String json = (String)claims.get(PAYLOAD);                    ObjectMapper objectMapper = new ObjectMapper();                    return objectMapper.readValue(json, classT);                }              return null;          } catch (Exception e) {              return null;          }      }  }  

这个加密工具类是我从网上找的,如果各位要修改,可以按照自己业务修改即可。


4. 创建Login.java对象,用来进行jwt的加密或者解密:

public class Login implements Serializable{      /**      *       */      private static final long serialVersionUID = 1899232511233819216L;        /**      * 用户id      */      private String uid;            /**      * 登录用户名      */      private String loginName;            /**      * 登录密码      */      private String password;            public Login(){          super();      }            public Login(String uid, String loginName, String password){          this.uid = uid;          this.loginName = loginName;          this.password = password;      }            public String getUid() {          return uid;      }      public void setUid(String uid) {          this.uid = uid;      }      public String getLoginName() {          return loginName;      }      public void setLoginName(String loginName) {          this.loginName = loginName;      }      public String getPassword() {          return password;      }      public void setPassword(String password) {          this.password = password;      }              }  

5. 定义RedisLogin对象,用来通过uid往redis进行user对象存储:

public class RedisLogin implements Serializable{      /**      *       */      private static final long serialVersionUID = 8116817810829835862L;        /**      * 用户id      */      private String uid;            /**      * jwt生成的token信息      */      private String token;            /**      * 登录或刷新应用的时间      */      private long refTime;            public RedisLogin(){                }            public RedisLogin(String uid, String token, long refTime){          this.uid = uid;          this.token = token;          this.refTime = refTime;      }            public String getUid() {          return uid;      }      public void setUid(String uid) {          this.uid = uid;      }      public String getToken() {          return token;      }      public void setToken(String token) {          this.token = token;      }      public long getRefTime() {          return refTime;      }      public void setRefTime(long refTime) {          this.refTime = refTime;      }                }  

6. 编写LoginInterceptor.java拦截器

public class LoginInterceptor implements HandlerInterceptor{        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)              throws Exception {          PrintWriter writer = null;          HandlerMethod method = null;          try {              method = (HandlerMethod) handler;          } catch (Exception e) {              writer = response.getWriter();              ResponseVO responseVO = ResponseCode.buildEnumResponseVO(ResponseCode.REQUEST_URL_NOT_SERVICE, false);              responseMessage(response, writer, responseVO);              return false;          }            IsLogin isLogin = method.getMethodAnnotation(IsLogin.class);          if(null == isLogin){              return true;          }                              response.setCharacterEncoding("utf-8");          String token = request.getHeader("token");          String uid = request.getHeader("uid");          //token不存在          if(StringUtils.isEmpty(token)) {              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TOKEN_NOT_NULL, false);              responseMessage(response, writer, responseVO);              return false;          }          if(StringUtils.isEmpty(uid)){              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_NULL, false);              responseMessage(response, writer, responseVO);              return false;          }                        Login login = JWT.unsign(token, Login.class);          //解密token后的loginId与用户传来的loginId判断是否一致          if(null == login || !StringUtils.equals(login.getUid(), uid)){              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);              responseMessage(response, writer, responseVO);              return false;          }                    //验证登录时间          RedisLogin redisLogin = (RedisLogin)JedisUtils.getObject(uid);          if(null == redisLogin){              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.RESPONSE_CODE_UNLOGIN_ERROR, false);              responseMessage(response, writer, responseVO);              return false;          }                    if(!StringUtils.equals(token, redisLogin.getToken())){              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);              responseMessage(response, writer, responseVO);              return false;          }          //系统时间>有效期(说明已经超过有效期)          if (System.currentTimeMillis() > redisLogin.getRefTime()) {              writer = response.getWriter();              ResponseVO responseVO = LoginResponseCode.buildEnumResponseVO(LoginResponseCode.LOGIN_TIME_EXP, false);              responseMessage(response, writer, responseVO);              return false;          }                    //重新刷新有效期          redisLogin = new RedisLogin(uid, token, System.currentTimeMillis() + 60L* 1000L* 30L);          JedisUtils.setObject(uid , redisLogin, 360000000);          return true;      }        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,              ModelAndView modelAndView) throws Exception {                }        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)              throws Exception {                }            private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseVO responseVO) {          response.setContentType("application/json; charset=utf-8");            JSONObject result = new JSONObject();          result.put("result", responseVO);          out.print(result);          out.flush();          out.close();      }    }  

7. 定义异常的LoginResponseCode

public enum LoginResponseCode {      USERID_NOT_NULL(3001,"用户id不能为空."),       LOGIN_TOKEN_NOT_NULL(3002,"登录token不能为空."),      USERID_NOT_UNAUTHORIZED(3003, "用户token或ID验证不通过"),      RESPONSE_CODE_UNLOGIN_ERROR(421, "未登录异常"),      LOGIN_TIME_EXP(3004, "登录时间超长,请重新登录");            // 成员变量        private int code; //状态码        private String message; //返回消息        // 构造方法        private LoginResponseCode(int code,String message) {            this.code = code;            this.message = message;        }        public int getCode() {          return code;      }      public void setCode(int code) {          this.code = code;      }      public String getMessage() {          return message;      }      public void setMessage(String message) {          this.message = message;      }          public static ResponseVO buildEnumResponseVO(LoginResponseCode responseCode, Object data) {          return new ResponseVO(responseCode.getCode(),responseCode.getMessage(),data);      }            public static Map<String, Object> buildReturnMap(LoginResponseCode responseCode, Object data) {          Map<String, Object> map = new HashMap<String, Object>();          map.put("code", responseCode.getCode());          map.put("message", responseCode.getMessage());          map.put("data", data);          return map;      }  }  

8. 编写统一sso单点登录接口:

@RequestMapping(value = "/login", method = RequestMethod.POST)      public Map<String, Object> login(@RequestBody JSONObject json){          String loginName = json.optString("loginName");          String password = json.optString("password");          //校验用户名不能为空          if(StringUtils.isEmpty(loginName)){              return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_NAME_IS_NOT_EMPTY, null);          }          //校验用户密码不能为空          if(StringUtils.isEmpty(password)){              return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_PWD_CAN_NOT_BE_EMPTY, null);          }          //根据用户名查询数据库用户信息          User user = systemService.getBaseUserByLoginName(loginName);          //用户名或密码不正确          if(null == user){              return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_VALIDATE_NO_SUCCESS, false);          }          boolean isValidate = systemService.validatePassword(password, user.getPassword());          if(!isValidate){              return MemberResponseCode.buildReturnMap(MemberResponseCode.RESPONSE_CODE_USER_VALIDATE_NO_SUCCESS, false);          }          if(isValidate){              //HttpSession session =request.getSession(false);              Login login = new Login(user.getId(), user.getLoginName(), user.getPassword());               //给用户jwt加密生成token              String token = JWT.sign(login, 60L* 1000L* 30L);              Map<String,Object> result =new HashMap<String,Object>();                result.put("loginToken", token);              result.put("userId", user.getId());              result.put("user", user);                            //保存用户信息到session              //session.setAttribute(user.getId() + "@@" + token, user);              //重建用户信息              this.rebuildLoginUser(user.getId(), token);              return ResponseCode.buildReturnMap(ResponseCode.RESPONSE_CODE_LOGIN_SUCCESS, result);          }                    return ResponseCode.buildReturnMap(ResponseCode.USER_LOGIN_PWD_ERROR, false);      }  

9. 测试sso单点登录:

返回结果集:

{    "message": "用户登录成功",    "data": {      "loginToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDkzODA1OTU0NTksInBheWxvYWQiOiJ7XCJ1aWRcIjpcIjExXCIsXCJsb2dpbk5hbWVcIjpcImFkbWluXCIsXCJwYXNzd29yZFwiOlwiZjU0NGQxM2QyY2EwNDU5ZGQ0ZTU1NzVjNmZkYWIzMzM0MzE1MWFlZjgwYmE5ZTNiN2U1ZjM2MzJcIn0ifQ.56L60WtxHXSu9vNs6XsWy5zbmc3kP_IWG1YpReK50DM",      "userId": "11",      "user": {        "QQ":"2147775633",        "id": "11",        "isNewRecord": false,        "remarks": "",        "createDate": "2017-08-08 08:08:08",        "updateDate": "2017-10-29 11:23:50",        "loginName": "admin",        "no": "00012",        "name": "admin",        "email": "2147775633@qq.com",        "phone": "400000000",        "mobile": "13888888888",        "userType": "",        "loginIp": "0:0:0:0:0:0:0:1",        "loginDate": "2017-10-30 10:48:06",        "loginFlag": "1",        "photo": "",        "idCard": "420888888888888888",        "oldLoginIp": "0:0:0:0:0:0:0:1",        "oldLoginDate": "2017-10-30 10:48:06",        "roleNames": "",        "admin": false      }    },    "code": 200  }  

 到此完毕!!


用java实施的电子商务平台太少了,使用spring cloud技术构建的b2b2c电子商务平台更少,大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B、B2C、C2C、O2O、新零售、直播电商等子平台。

Spring Cloud大型企业分布式微服务云构建的B2B2C电子商务平台(企业架构源码可以加求球:叁五三陆二肆柒二伍玖)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335