zoukankan      html  css  js  c++  java
  • 170810、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>

    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;
            }
        }
    }

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

    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
    }

    原文地址:http://2147775633.iteye.com/blog/2398104

  • 相关阅读:
    Android实战:手把手实现“捧腹网”APP(一)-----捧腹网网页分析、数据获取
    容器云平台使用体验:数人云Crane(续)
    [React Native]升级React Native版本
    [React Native]去掉WebStorm中黄色警告
    数据库--mysql介绍
    缓存数据库-redis(补充)
    缓存数据库-redis(订阅发布)
    缓存数据库-redis(管道)
    缓存数据库-redis数据类型和操作(sorted set)
    缓存数据库-redis数据类型和操作(set)
  • 原文地址:https://www.cnblogs.com/zrbfree/p/7760873.html
Copyright © 2011-2022 走看看