• Token验证登录状态的简单实现


    设计思路

    1. 用户发出登录请求,带着用户名和密码到服务器进行验证,服务器验证成功就在后台生成一个token返回给客户端
    2. 客户端将token存储到cookie中,服务端将token存储到redis中,可以设置存储token的有效期。
    3. 后续客户端的每次请求资源都必须携带token,服务端接收到请求首先校验是否携带token,以及token是否和redis中的匹配,若不存在或不匹配直接拦截返回错误信息(如未认证)。
    • token管理:生成、校验、解析、删除

    • token:这里使用userId_UUID的形式

    • 有效期:使用Redis key有效期设置(每次操作完了都会更新延长有效时间)

    • 销毁token:删除Redis中key为userId的内容

    • token存储:客户端(Cookie)、服务端(Redis)

    • Cookie的存取操作(jquery.cookie插件)

    • Redis存取(StringRedisTemplate)

    实现

    【Redis操作类】

    package com.bpf.tokenAuth.utils;
    
    import java.util.concurrent.TimeUnit;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    @Component
    public class RedisClient {
        
        public static final long TOKEN_EXPIRES_SECOND = 1800;
    
        @Autowired
        private StringRedisTemplate redisTpl;
        
        /**
         * 向redis中设值
         * @param key 使用 a:b:id的形式在使用rdm进行查看redis情况时会看到分层文件夹的展示形式,便于管理
         * @param value
         * @return
         */
        public boolean set(String key, String value) {
            boolean result = false;
            try {
                redisTpl.opsForValue().set(key, value);
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
        
        
        /**
         * 向redis中设置,同时设置过期时间
         * @param key
         * @param value
         * @param time
         * @return
         */
        public boolean set(String key, String value, long time) {
            boolean result = false;
            try {
                redisTpl.opsForValue().set(key, value);
                expire(key, time);
                result =  true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
        
        /**
         * 获取redis中的值
         * @param key
         * @return
         */
        public String get(String key) {
            String result = null;
            try {
                result = redisTpl.opsForValue().get(key);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
      
        }
        
        /**
         * 设置key的过期时间
         * @param key
         * @param time
         * @return
         */
        public boolean expire(String key, long time) {
            boolean result = false;
            try {
                if(time > 0) {
                    redisTpl.expire(key, time, TimeUnit.SECONDS);
                    result = true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
        
        /**
         * 根据key删除对应value
         * @param key
         * @return
         */
        public boolean remove(String key) {
            boolean result = false;
            try {
                redisTpl.delete(key);
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }   
    }
    【Token管理类】
    package com.bpf.tokenAuth.utils.token;
    
    import java.util.UUID;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import com.bpf.tokenAuth.utils.RedisClient;
    
    @Component
    public class RedisTokenHelp implements TokenHelper {
        
        @Autowired
        private RedisClient redisClient;
    
        @Override
        public TokenModel create(Integer id) {
            String token = UUID.randomUUID().toString().replace("-", "");
            TokenModel mode = new TokenModel(id, token);
            redisClient.set(id == null ? null : String.valueOf(id), token, RedisClient.TOKEN_EXPIRES_SECOND);
            return mode;
        }
    
        @Override
        public boolean check(TokenModel model) {
            boolean result = false;
            if(model != null) {
                String userId = model.getUserId().toString();
                String token = model.getToken();
                String authenticatedToken = redisClient.get(userId);
                if(authenticatedToken != null && authenticatedToken.equals(token)) {
                    redisClient.expire(userId, RedisClient.TOKEN_EXPIRES_SECOND);
                    result = true;
                }
            }
            return result;
        }
    
        @Override
        public TokenModel get(String authStr) {
            TokenModel model = null;
            if(StringUtils.isNotEmpty(authStr)) {
                String[] modelArr = authStr.split("_");
                if(modelArr.length == 2) {
                    int userId = Integer.parseInt(modelArr[0]);
                    String token = modelArr[1];
                    model = new TokenModel(userId, token);
                }
            }
            return model;
        }
    
        @Override
        public boolean delete(Integer id) {
            return redisClient.remove(id == null ? null : String.valueOf(id));
        }
    
    }
    
    

    【拦截器逻辑】

    
    
    package com.bpf.tokenAuth.interceptor;
    
    import java.lang.reflect.Method;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import com.bpf.tokenAuth.annotation.NoneAuth;
    import com.bpf.tokenAuth.constant.NormalConstant;
    import com.bpf.tokenAuth.entity.JsonData;
    import com.bpf.tokenAuth.utils.JsonUtils;
    import com.bpf.tokenAuth.utils.token.TokenHelper;
    import com.bpf.tokenAuth.utils.token.TokenModel;
    
    @Component
    public class LoginInterceptor extends HandlerInterceptorAdapter {
        
        @Autowired
        private TokenHelper tokenHelper;
        
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            System.out.println(11);
            // 如果不是映射到方法直接通过
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            //如果被@NoneAuth注解代表不需要登录验证,直接通过
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            if(method.getAnnotation(NoneAuth.class) != null) return true;       
            //token验证
            String authStr = request.getHeader(NormalConstant.AUTHORIZATION);
            TokenModel model = tokenHelper.get(authStr);
            
            //验证通过
            if(tokenHelper.check(model)) {
                request.setAttribute(NormalConstant.CURRENT_USER_ID, model.getUserId());
                return true;
            }
            //验证未通过
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().write(JsonUtils.obj2String(JsonData.buildError(401, "权限未认证")));
            return false;
        }
    }
    
    

    【登录逻辑】

    package com.bpf.tokenAuth.controller;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.bpf.tokenAuth.annotation.NoneAuth;
    import com.bpf.tokenAuth.constant.MessageConstant;
    import com.bpf.tokenAuth.constant.NormalConstant;
    import com.bpf.tokenAuth.entity.JsonData;
    import com.bpf.tokenAuth.entity.User;
    import com.bpf.tokenAuth.enums.HttpStatusEnum;
    import com.bpf.tokenAuth.mapper.UserMapper;
    import com.bpf.tokenAuth.utils.token.TokenHelper;
    import com.bpf.tokenAuth.utils.token.TokenModel;
    
    @RestController
    @RequestMapping("/token")
    public class TokenController {
        
        @Autowired
        private UserMapper userMapper;
        
        @Autowired
        private TokenHelper tokenHelper;
        
        @NoneAuth
        @GetMapping
        public Object login(String username, String password) {
            User user = userMapper.findByName(username);
            if(user == null || !user.getPassword().equals(password)) {
                return JsonData.buildError(HttpStatusEnum.NOT_FOUND.getCode(), MessageConstant.USERNAME_OR_PASSWORD_ERROR);
            }
            //用户名密码验证通过后,生成token
            TokenModel model = tokenHelper.create(user.getId());
            return JsonData.buildSuccess(model);    
        }
        
        @DeleteMapping
        public Object logout(HttpServletRequest request) {
            Integer userId = (Integer) request.getAttribute(NormalConstant.CURRENT_USER_ID);
            if(userId != null) {
                tokenHelper.delete(userId);
            }
            return JsonData.buildSuccess();
        }
    
    }

    测试

    【login.html】



    <!DOCTYPE html>
    <html>  
    <head>
    <title>Login</title>
    <link rel="stylesheet" href="../res/css/login.css">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    </head>
    <body>
        <form>
            <input type="text" name="username" id="username">
            <input type="password" name="password" id="password">
        </form>
        <input type="button" value="Login" onclick="login()">
    </body>
    <script type="text/javascript">
    function login(){
        $.ajax({
            url: "/tokenAuth/token",
            dataType: "json",
            data: {'username':$("#username").val(), 'password':$("#password").val()},
            type:"GET",
            success:function(res){
                console.log(res);
                if(res.code == 200){
                    var authStr = res.data.userId + "_" + res.data.token;
                    //把生成的token放在cookie中
                    $.cookie("authStr", authStr);
                    window.location.href = "index.html";
                }else alert(res.msg);
            }
        });
    }
    </script>
    </html>
    
    

    【index.html】

    <!DOCTYPE html>
    <html>  
    <head>
    <title>Index</title>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    </head>
    <body>
        <input type="button" value="Get" onclick="get()">
        <input type="button" value="logout" onclick="logout()">
    </body>
    <script type="text/javascript">
    
    function get(){
        $.ajax({
            url: "/tokenAuth/user/bpf",
            dataType: "json",   
            type:"GET",
            beforeSend: function(request) {
                //将cookie中的token信息放于请求头中
                request.setRequestHeader("authStr", $.cookie('authStr'));
            },
            success:function(res){
                console.log(res);
            }
        });
    }
    
    function logout(){
        $.ajax({
            url: "/tokenAuth/token",
            dataType: "json",   
            type:"DELETE",
            beforeSend: function(request) {
                //将cookie中的token信息放于请求头中
                request.setRequestHeader("authStr", $.cookie('authStr'));
            },
            success:function(res){
                console.log(res);
            }
        });
    }
    </script>
    </html>

    测试环境中两个页面login.html和index.html均当做静态资源处理
    【未登录状态】

    【登录状态】

    • 访问登录网站http://localhost:8080/tokenAuth/page/login.html,输入username和password进行点击Login按钮登录
    • 登录成功并跳转到index页面,并且生成cookie,这里没有设置cookie有效期,默认关闭浏览器失效



    再次点击get按钮请求数据,请求成功
    点击logout按钮销毁登录状态,然后再次请求数据
  • 相关阅读:
    【转载】Fiddler 抓包工具使用指北: 弱网络环境模拟限速测试流程
    【原创】python+selenium,用xlrd,读取excel数据,执行测试用例
    自动化测试常用断言的使用方法(python+selenium)
    selenium中的等待方法及区别
    python利用unittest进行测试用例执行的几种方式
    使用uiautomator做UI测试
    Python+Appium学习之启动手机APP或者浏览器
    查看Android应用包名、Activity的几个方法
    JavaWeb前置知识(一) : 动态和静态的区别、两种架构、常见状态码
    随笔分类
  • 原文地址:https://www.cnblogs.com/BruceV/p/11956676.html
Copyright © 2020-2023  润新知