zoukankan      html  css  js  c++  java
  • SpringBoot实现JWT认证

    SpringBoot实现JWT认证

    本文会从Token、JWT、JWT的实现、JWTUtil封装到SpringBoot中使用JWT,如果有一定的基础,可以跳过前面的内容~

    Token

    简介

    Token 是一个临时、唯一、保证不重复的令牌,例如智能门锁,它可以生成一个临时密码,具有一定时间内的有效期。

    实现思路

    UUID具有上述的特性,所以我们可以使用UUID作为token,生产UUID后放入Redis,设置Redis的过期时间。

    Token的SessionID

    token和SESSIONID非常的相似,但是SESSIONID在分布式项目中不能共享,虽然SESSION可以通过Redis等技术实现共享,但是使用这类技术会降低项目的性能和可用性。所以现在普通使用Token代替Session使用。

    简单的Token实现思路

    实现思路:

    1. 验证用户的账号密码
    2. 如果正确,生成UUID作为Key
    3. 将此Key作为Key,将用户信息作为Value,存入Redis
    4. 最后返回Token给客户端,客户端将Cookie保存到Cookie中

    用户在每次请求时,都会携带此Token,后端在拦截器中校验Token是否存在,如果存在找到对应的用户信息,判断其有哪些权限。

    Token优点:

    1. 可以通过Header、Body提交,实现跨域操作
    2. 可以隐藏参数的真实性,实现参数的脱敏
    3. 临时、唯一

    存在问题:

    1. 使用Token,必须依赖Redis和Cookie
    2. 需要频繁操作Redis

    JWT

    简介

    JWT,全称Json Web Token,是目前最流行的跨域认证解决方案。它的实现思想和上面的token是基本一致的,是一种更加成熟和完善的解决方案。

    原理

    JWT的原理就是,当服务器认证账号密码通过后,生成一个JSON对象,返回给用户,保存在Cookie中。当用户下一次访问的时候自动携带这个JSON对象,服务器可以根据这个对象判断用户的身份。为了防止用户篡改数据信息,服务器生成这个JSON的时候,会进行一些加密操作。此时服务器中就不需要保存session数据。

    JWT的数据结构

    JWT中的数据分为三部分,每部分都是一串很长的字符串,中间用.间隔

    • header: 头部,标记加密算法
    • Payload: 负载,存放具体数据
    • Signature:签名,Payload采用MD5加密后的签名值

    完整的格式为:header.Payload.Signature

    Header部分是由一个JSON对象组成,它描述JWT的元数据,通常是下面的样子:

    {
    
    	'alg' : "HS256",
    	"typ" : "JWT"
    
    }
    

    alg表示签名的算法,默认为HMAC SHA256(可以写成 HS256)

    typ属性表示令牌的类型,JWT令牌统一写为JWT

    生成JWT后,此部分会进行BASE64编码,最终被解析为:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    

    jwt中的Header部分的内容默认是没有加密的,只是进行了Base64处理。可以直接使用Base64反加密获取原文。

    Payload

    Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段:

    • iss (issuer):签发人
    • exp (expiration time):过期时间
    • sub (subject):主题
    • aud (audience):受众
    • nbf (Not Before):生效时间
    • iat (Issued At):签发时间
    • jti (JWT ID):编号

    除了官方字段,还可以支持自定义字段,下面就是一个例子:

    {
      "name": "rayfoo",
      "phone": 18338862369
    }
    

    生产JWT后的:

    eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ
    

    注意,这部分的内容默认也是没有加密的,只是进行了Base64编码。可以直接使用Base64反加密获取原文。但是我们可以对其进行一些混淆操作。

    Signature

    Signature 部分是对前两部分的签名,防止数据篡改。

    首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

    这一段并不是使用base64加密,而是使用header中提供的加密方式进行的加密.

    可以浅显的理解为将Payload中的数据按照header,payload+密钥(secret)作为一个整体进行MD5(也可能是任意类型的)加密。在下面这段代码中,密钥就是:rayfoo。

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      rayfoo
    )
    

    jwt生成的signature

    AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w
    

    完整的token

    算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。这就是完整的JWT:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ.AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w
    

    可以在https://jwt.io/#encoded-jwt进行测试

    Base64URL编码

    前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

    JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+/=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_ 。这就是 Base64URL 算法。

    JWT的使用方式

    客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

    此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

    Authorization: Bearer <token>
    

    另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

    JWT 的几个特点

    优点:

    (1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次,不容易被客户端修改。

    (2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

    (3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。效率也比token高。

    缺点:

    (1)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

    (2)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

    (3)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

    (4)如果jwt中payload的数据过多,会占用服务器的带宽资源。

    如何手写一个JWT

    了解了上面的一些概念后,我们可以自己动手实现一个jwt

    1. 创建两个JSONObject对象,分别作为Header和Payload
    2. 初始化Header,添加相应内容,进行base64编码
    3. 初始化Payload,添加相应内容,进行base64编码+混淆
    4. 对Payload进行md5加盐、加密

    对三部分内容进行拼接,使用.间隔

    建议在payload中增加一个时间戳,用于指定过期时间。

    在Java中使用JWT

    jwt提供了不止一种的实现

    • Auth0实现 的 java-jwt

    • Brian Campbell实现的 jose4j

    • connect2id实现的 nimbus-jose-jwt

    • Les Haziewood实现的 jjwt

    • Inversoft实现的prime-jwt

    • Vertx实现的vertx-auth-jwt.

    几乎所有库都要求JAVA版本1.7或更高版本, 1.6或以下的版本需要二次开发(或不支持)

    从易用性, 扩展性, 完整性等来看, 使用首先推荐 jose4j, 其次是 Nimbus-jose-jwt.

    关于这些类库的评测:http://andaily.com/blog/?p=956

    封装JWT工具类&payload加密

    引入依赖

    下面的代码都是基于auth0 提供的 java-jwt实现的

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

    封装工具类

    由于JWT中的payload是不安全的,没有进行加密,所以在工具类中进行了加密操作。

    这里的加密操作只是一种加密思路,你也可以使用自己的任意加密方式来让payload中的内容更加安全。

    package cn.rayfoo.common.util;
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTCreator;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTDecodeException;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.util.Calendar;
    import java.util.Map;
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p>JSON WEB TOKEN 工具类</p>
     * @date 2020/8/11 9:19
     */
    public class JWTUtil {
    
        /**
         * 签名 此签名为 rayfoo 的16位 大写 MD5
         */
        private static final String SIGN_KEY = "5A1332068BA9FD17";
    
        /**
         * 默认的过期时间,30分钟
         */
        private static final Integer DEFAULT_EXPIRES = 60 * 30;
    
        /**
         * token默认的长度
         */
        private static final Integer DEFAULT_TOKEN_SIZE = 3;
    
    
        /**
         * 生成令牌
         *
         * @param map     数据正文
         * @param expires 过期时间,单位(秒)
         */
        public static String getToken(Map<String, String> map, Integer expires) throws Exception {
    
            //创建日历
            Calendar instance = Calendar.getInstance();
            //设置过期时间
            instance.add(Calendar.SECOND, expires);
    
            //创建jwt builder对象
            JWTCreator.Builder builder = JWT.create();
    
            //payload
            map.forEach((k, v) -> {
                builder.withClaim(k, v);
            });
    
            //指定过期时间
            String token = builder.withExpiresAt(instance.getTime())
                    //设置加密方式
                    .sign(Algorithm.HMAC256(SIGN_KEY));
            //返回tokean
            return confoundPayload(token);
        }
    
        /**
         * 解析token
         *
         * @param token 输入混淆payload后的token
         */
        public static DecodedJWT verify(String token) throws Exception {
            //如果token无效
            if (token == null || "".equals(token)) {
                throw new JWTDecodeException("无效的token!");
            }
            //解析token
            String dToken = deConfoundPayload(token);
            //创建返回结果
            return JWT.require(Algorithm.HMAC256(SIGN_KEY)).build().verify(dToken);
    
        }
    
        /**
         * 重载getToken 此方法为获取默认30分钟有效期的token
         *
         * @param map 数据正文
         */
        public static String getToken(Map<String, String> map) throws Exception {
            return getToken(map, DEFAULT_EXPIRES);
        }
    
    
        /**
         * 对一个base64编码进行混淆  此处还可以进行replace混淆,考虑到效率问题,这里就不做啦~
         * 对于加密的思路还有位移、字符替换等~
         *
         * @param token 混淆payload前的token
         */
        private static String confoundPayload(String token) throws Exception {
            //分割token
            String[] split = token.split("\.");
            //如果token不符合规范
            if (split.length != DEFAULT_TOKEN_SIZE) {
                throw new JWTDecodeException("签名不正确");
            }
            //取出payload
            String payload = split[1];
            //获取长度
            int length = payload.length() / 2;
            //指定截取点
            int index = payload.length() % 2 != 0 ? length + 1 : length;
            //混淆处理后的token
            return split[0] + "." + reversePayload(payload, index) + "." + split[2];
        }
    
        /**
         * 对一个混淆后的base编码进行解析
         *
         * @param token 混淆后的token
         */
        private static String deConfoundPayload(String token) throws Exception {
            //分割token
            String[] split = token.split("\.");
            //如果token不符合规范
            if (split.length != DEFAULT_TOKEN_SIZE) {
                throw new JWTDecodeException("签名不正确");
            }
            //取出payload
            String payload = split[1];
            //返回解析后的token
            return split[0] + "." + reversePayload(payload, payload.length() / 2) + "." + split[2];
        }
    
        /**
         * 将md5编码位移
         *
         * @param payload payload编码
         * @param index   位移处
         */
        private static String reversePayload(String payload, Integer index) {
            return payload.substring(index) + payload.substring(0, index);
        }
    
    
    }
    
    

    此时,我们就可以使用此工具类颁发token、解析token了~

    package cn.rayfoo;
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import com.auth0.jwt.interfaces.Verification;
    import org.apache.commons.codec.binary.StringUtils;
    
    import java.util.*;
    
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p></p>
     * @date 2020/8/10 16:18
     */
    public class JwtTest {
    
        //密钥
        private static final String SIGN_KEY = "rayfoo";
    
        public static void main(String[] args) throws Exception{
    
            //创建map
            Map<String, String> map = new HashMap<>();
            map.put("username", "rayfoo");
            //颁发token
            String token = JWTUtil.getToken(map);
            System.out.println(token);
    
            //解析token
            DecodedJWT verify = JWTUtil.verify(token);
            System.out.println(verify.getClaim("username").asString());
    
        }
    
    }
    
    

    此时,使用混淆后的token解析,发现无法解析到payload:

    常见异常的处理

    • JWTDecodeException:header、payload被修改会出现的异常
    • SignatureVerificationException:签名不匹配异常
    • TokenExpiredException:令牌过期异常
    • AlgorithmMismatchException:算法不匹配异常

    建议使用全局异常处理进行细粒度异常处理

    在SpringBoot中使用JWT

    使用JWT之前

    先基于SpringBoot+MyBatis实现一个简单的查询操作,完整的代码稍后会上传到Github,这里只进行关键部分的介绍。

    传统的密码校验:

        @Override
        public User login(User user) throws Exception {
            //这里假设user、user内的username、password数据都是正确的
            User example = User.builder().username(user.getUsername()).password(user.getPassword()).build();
            //查询用户是否存在
            List<User> reslut = userMapper.select(example);
            //如果没找到代表用户名或者密码错误
            if (ObjectUtils.isEmpty(reslut)) {
                throw new Exception("用户名或密码错误!");
            }
            return reslut.get(0);
        }
    

    上面时service层代码,如果执行没有报错说明拿到了正确的查询结果,此时在Controller中可以将用户的登录信息保存到Session或者Redis中,用于校验。

        @PostMapping("/login")
        public Map<String, Object> login(@RequestBody User user) {
    
            //初始化返回值
            Map<String, Object> map = new HashMap<>(2);
            try {
                //用户登录校验
                User loginUser = userService.login(user);
                //没有抛出异常表示正常
                map.put("code", 200);
                map.put("msg", "认证成功!");
                //使用session或者redis记录。。。
            } catch (Exception exception) {
                //如果出现异常记录错误信息
                map.put("code", 500);
                map.put("msg", exception.getMessage());
            }
            //返回结果
            return map;
        }
    

    使用JWT

    有了JWT以后,我们可以使用token来代替Session/Redis

     @PostMapping("/login")
        public Map<String, Object> login(@RequestBody User user) {
    
            //初始化返回值
            Map<String, Object> map = new HashMap<>(3);
            try {
                //用户登录校验
                User loginUser = userService.login(user);
    
                //没有抛出异常表示正常
                map.put("code", 200);
                map.put("msg", "认证成功!");
    
                //声明payload
                Map<String, String> payload = new HashMap<>(2);
    
                //初始化payload
                payload.put("id", loginUser.getId().toString());
                payload.put("username", loginUser.getUsername());
    
                //获取令牌
                String token = JWTUtil.getToken(payload,20);
    
                //在响应结果中添加token
                map.put("token", token);
    
    
            } catch (Exception exception) {
                //如果出现异常记录错误信息
                map.put("code", 500);
                map.put("msg", exception.getMessage());
            }
            //返回结果
            return map;
        }
    

    JWT包含有权限的接口

    这里的代码没有进行优化,只是用最简单直白的方式介绍了JWT对接口的保护

    @GetMapping("/list")
        public Map<String, Object> userList(String token) {
    
            //初始化返回值
            Map<String, Object> map = new HashMap<>(3);
    
            List<User> result = null;
    
            String errorMsg = "";
    
            //校验token
            log.info("当前token为:" + token);
    
            try {
                //验证令牌
                DecodedJWT verify = JWTUtil.verify(token);
                //如果令牌校验成功
                result = userService.userList();
                //返回查询结果
                map.put("code", 200);
                map.put("msg", "查询成功");
                map.put("result", result);
                return map;
            } catch (JWTDecodeException e) {
                //其实是用户修改了header或者payload,但是不用告诉用户错误的细节
                e.printStackTrace();
                errorMsg = "token无效!";
            } catch (SignatureVerificationException e) {
                e.printStackTrace();
                //其实是修改了签名,但是不用告诉用户错误的细节
                errorMsg = "token无效!";
            } catch (TokenExpiredException e) {
                e.printStackTrace();
                errorMsg = "token已过期!";
            } catch (AlgorithmMismatchException e) {
                e.printStackTrace();
                //其实是修改了算法,但是不用告诉用户错误的细节
                errorMsg = "token无效!";
            } catch (Exception e) {
                e.printStackTrace();
                errorMsg = "token无效!";
            }
            //返回错误信息
            map.put("code", 500);
            map.put("msg", "token校验失败");
            map.put("result", errorMsg);
            return map;
        }
    
    

    使用拦截器优化Token校验

    在前面的案例中,如果每个接口都进行拦截器校验,冗余的代码会非常的多,程序的可读性也非常低。

    在单体应用中,可以使用拦截器来校验token

    分布式项目中,可以在网关内校验token

    token拦截器

    在上面的案例中,token在body中作为数据传递的,但是这样是不安全的,比较推荐的做法是加在请求头内,通过请求头携带.

    package cn.rayfoo.modules.base.interceptor;
    
    import cn.rayfoo.common.util.JWTUtil;
    import org.springframework.web.servlet.HandlerInterceptor;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p></p>
     * @date 2020/8/11 15:58
     */
    public class JWTInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            //从请求头内获取token
            String token = request.getHeader("authorization");
    
            //验证令牌  如果令牌不正确会出现异常 被全局异常处理
            JWTUtil.verify(token);
    
            return true;
        }
    
    }
    
    

    拦截器注册

    这里我对所有的请求进行了拦截,放行了登录接口,真实的场景下,我们一般会放行所有/user/**的请求,另外,这个拦截器中没有注入其他属性,所以可以通过此种方式创建,如果拦截器内注入了属性,需要使用@Bean+方法的形式注册拦截器。详细的内容,可以参考我博客中关于拦截器的介绍。

    package cn.rayfoo.common.config;
    
    import cn.rayfoo.modules.base.interceptor.JWTInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p></p>
     * @date 2020/8/11 16:13
     */
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
            registry.addInterceptor(new JWTInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/user/login");
    
        }
    
    }
    
    

    封装统一返回结果类

    接下来的代码我会使用统一结果集来封装返回值,下面是统一结果集的代码:

    package cn.rayfoo.common.response;
    
    import lombok.Data;
    
    
    /**
     * @author rayfoo@qq.com
     * @date 2020年8月6日
     */
    @Data
    public class Result<T> {
    
        /**
         * 状态码
         */
        private Integer code;
    
        /**
         * 提示信息
         */
        private String  msg;
    
        /**
         * 数据记录
         */
        private T data;
    
        public Result() {
        }
    
        public Result(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public Result(Integer code, String msg, T data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    
    }
    

    全局异常处理

    在拦截器中处理异常也是非常不好的习惯,我们可以将异常交由统一异常处理来管理

    package cn.rayfoo.common.exception;
    
    import cn.rayfoo.common.response.Result;
    import com.auth0.jwt.exceptions.AlgorithmMismatchException;
    import com.auth0.jwt.exceptions.JWTDecodeException;
    import com.auth0.jwt.exceptions.SignatureVerificationException;
    import com.auth0.jwt.exceptions.TokenExpiredException;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p>全局异常处理</p>
     * @date 2020/8/11 16:36
     */
    @ControllerAdvice@Slf4j
    public class ServiceExceptionHandler {
    
        /**
         * 默认异常的状态码
         */
        private static final Integer DEFAULT_EXCEPTION = 500;
    
        /**
         * token超时异常状态码
         */
        private static final Integer TOKEN_ERROR_EXCEPTION = 505;
    
        /**
         * token无效状态码
         */
        private static final Integer TOKEN_EXPIRED_EXCEPTION = 506;
    
    
        /**
         * 处理token异常
         */
        @ResponseBody
        @ExceptionHandler({SignatureVerificationException.class, AlgorithmMismatchException.class, JWTDecodeException.class})
        public Result<String> tokenErrorException() {
            Result<String> result = new Result<>();
            result.setCode(TOKEN_ERROR_EXCEPTION);
            result.setMsg("无效的token!");
            log.error("无效的token");
            return result;
        }
    
        /**
         * 处理token异常
         */
        @ResponseBody
        @ExceptionHandler({TokenExpiredException.class})
        public Result<String> tokenExpiredException() {
            Result<String> result = new Result<>();
            result.setCode(TOKEN_EXPIRED_EXCEPTION);
            result.setMsg("token超时!");
            log.error("用户token超时");
            return result;
        }
    
        /**
         * 处理所有RuntimeException异常
         */
        @ResponseBody
        @ExceptionHandler({RuntimeException.class})
        public Result<String> allException(RuntimeException e) {
            Result<String> result = new Result<>();
            result.setCode(DEFAULT_EXCEPTION);
            result.setMsg( e.getMessage());
            log.error(e.getMessage());
            e.printStackTrace();
            return result;
        }
    
        /**
         * 处理所有Exception异常
         */
        @ResponseBody
        @ExceptionHandler({Exception.class})
        public Result<String> allException(Exception e) {
            Result<String> result = new Result<>();
            result.setCode(DEFAULT_EXCEPTION);
            result.setMsg( e.getMessage());
            log.error(e.getMessage());
            e.printStackTrace();
            return result;
        }
    
    }
    

    封装BaseController

    这里介绍一个Controller的小技巧,可以通过通用Controller来封装一些公共的属性

    package cn.rayfoo.modules.base.controller;
    
    import cn.rayfoo.modules.base.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.ModelAttribute;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * @date 2020/8/5 14:34
     * @description 基础controller
     */
    public class BaseController {
    
        /**
         * 注入全部service
         */
        @Autowired
        protected UserService userService;
    
    
        /**
         * 创建session、Request、Response等对象
         */
        protected HttpServletRequest request;
        protected HttpServletResponse response;
        protected HttpSession session;
    
    
        /**
         * 在每个子类方法调用之前先调用
         * 设置request,response,session这三个对象
         *
         * @param request
         * @param response
         */
        @ModelAttribute
        public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) {
            this.request = request;
            this.response = response;
            this.session = request.getSession(true);
            //可以在此处拿到当前登录的用户
        }
    
    }
    
    

    Controller代码优化

    如果需要用到token中的数据,可以使用request对象中获取token进行相关的处理。下面是优化后的Controller代码,是不是焕然一新呢?

    package cn.rayfoo.modules.base.controller;
    
    import cn.rayfoo.common.response.HttpStatus;
    import cn.rayfoo.common.response.Result;
    import cn.rayfoo.common.util.JWTUtil;
    import cn.rayfoo.modules.base.entity.User;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author rayfoo@qq.com
     * @version 1.0
     * <p></p>
     * @date 2020/8/11 14:12
     */
    @RestController
    @Slf4j
    @RequestMapping("/user")
    public class UserController extends BaseController {
    
    
        @PostMapping("/login")
        public Result<String> login(@RequestBody User user) throws Exception {
    
            //初始化返回值
            Result<String> result = new Result<>();
    
            //用户登录校验
            User loginUser = userService.login(user);
    
            //没有抛出异常表示正常
            result.setCode(HttpStatus.OK.value());
            result.setMsg("认证成功!");
    
            //声明payload
            Map<String, String> payload = new HashMap<>(2);
    
            //初始化payload
            payload.put("id", loginUser.getId().toString());
            payload.put("username", loginUser.getUsername());
    
            //获取令牌
            String token = JWTUtil.getToken(payload, 20);
    
            //在响应结果中添加token
            result.setData(token);
    
            //返回结果
            return result;
        }
    
        @GetMapping("/list")
        public Result<List<User>> userList() throws Exception {
    
            //初始化返回值
            Result<List<User>> result = new Result<>();
            //如果成功,设置状态码和查询到的结果
            result.setCode(HttpStatus.OK.value());
            result.setMsg("查询成功!");
            List<User> users = userService.userList();
            result.setData(users);
            //返回结果
            return result;
        }
    
    }
    
    

    github:https://github.com/18338862369/SpringBoot-JWT

    gitee:https://gitee.com/rayfoo/SpringBoot-JWT

    如果代码对你有帮助 青帮我点个star哦~

    参考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

  • 相关阅读:
    13、SpringBoot开启DevTools
    11、SpringBoot整合Junit
    10、SpringBoot之异常处理
    9、SpringBoot整合Mybatis
    8、SpringBoot整合JDBC
    7、SpringBoot整合Thymeleaf
    6、SpringBoot整合Freemarker
    5、SpringBoot整合JSP
    4、SpringBoot之文件上传
    leetcode787.K站中转最便宜航班
  • 原文地址:https://www.cnblogs.com/zhangruifeng/p/13477048.html
Copyright © 2011-2022 走看看