• SpringBoot集成Jwt(详细步骤+图解)


    SpringBoot集成Jwt(详细步骤+图解)
    Jwt简介

    JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证

    Jwt构成(.隔开)
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTkxNjQ4NjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.fo5a-H_C7XG3fSnNdCEMzM2QmrF5c7yypzoSxGzgJOo

    Header(头部):放有签名算法和令牌类型
    Payload(负载):你在令牌上附带的信息:比如用户的姓名,这样以后验证了令牌之后就可以直接从这里获取信息而不用再查数据库了
    Signature(签名):对前两部分的签名,防止数据篡改

    交互流程

     本文将通过几个简单步骤教大家如何集成Jwt,废话不多说,直接上步骤。(注:使用了Lombok,需下载好相关插件及依赖)

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

    • 在项目的yml文件中配置账号密码
    Login:
      username: admin
      password: admin
    
    • 创建用户实体类
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.hibernate.validator.constraints.Length;
    
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    
    /**
     * @author admin
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
    
        @NotNull(message = "id不能为空")
        private Integer id;
    
        @NotBlank(message="姓名不能为空")
        @Length(min = 2, max = 4, message = "name 姓名长度必须在 {min} - {max} 之间")
        private String username;
    
        @NotBlank(message="密码不能为空")
        @Length(min = 5, max = 10, message = "password 密码长度必须在 {min} - {max} 之间")
        private String password;
    
    }
    
    • 创建Jwt(生成/验证)工具类
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTCreator;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import org.apache.commons.lang.StringUtils;
    
    import java.util.Calendar;
    
    /**
     * @author admin
     */
    public class JWTUtils {
    
        /**
         * 获取token
         * @param u user
         * @return token
         */
        public static String getToken(User u) {
            Calendar instance = Calendar.getInstance();
            //默认令牌过期时间7天
            instance.add(Calendar.DATE, 7);
    
            JWTCreator.Builder builder = JWT.create();
            builder.withClaim("userId", u.getId())
                    .withClaim("username", u.getUsername());
    
            return builder.withExpiresAt(instance.getTime())
                    .sign(Algorithm.HMAC256(u.getPassword()));
        }
    
        /**
         * 验证token合法性 成功返回token
         */
        public static DecodedJWT verify(String token) throws MyException {
            if(StringUtils.isEmpty(token)){
                throw new MyException("token不能为空");
            }
    
            //获取登录用户真正的密码假如数据库查出来的是123456
            String password = "admin";
            JWTVerifier build = JWT.require(Algorithm.HMAC256(password)).build();
            return build.verify(token);
        }
    
       /* public static void main(String[] args) {
            DecodedJWT verify = verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTcxMDg1MDAsInVzZXJuYW1lIjoiYWRtaW4ifQ.geBEtpluViRUg66_P7ZisN3I_d4e32Wms8mFoBYM5f0");
            System.out.println(verify.getClaim("password").asString());
        }*/
    }
    
    • 创建Jwt拦截器(拦截所有请求验证token)
    import com.auth0.jwt.exceptions.AlgorithmMismatchException;
    import com.auth0.jwt.exceptions.SignatureVerificationException;
    import com.auth0.jwt.exceptions.TokenExpiredException;
    import com.chentawen.springbootall.util.JWTUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author admin
     */
    @Slf4j
    public class JWTInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String token = request.getHeader("token");
            if(StringUtils.isEmpty(token)){
                throw new MyException("token不能为空");
            }
            try {
                JWTUtils.verify(token);
            } catch (SignatureVerificationException e) {
                log.error("无效签名! 错误 ->", e);
                return false;
            } catch (TokenExpiredException e) {
                log.error("token过期! 错误 ->", e);
                return false;
            } catch (AlgorithmMismatchException e) {
                log.error("token算法不一致! 错误 ->", e);
                return false;
            } catch (Exception e) {
                log.error("token无效! 错误 ->", e);
                return false;
            }
            return true;
        }
    }
    
    • 将拦截器注入到SpirngMVC
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import java.util.List;
    
    /**
     * @author admin
     */
    @Configuration
    public class IntercaptorConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JWTInterceptor())
                    //拦截的路径
                    .addPathPatterns("/**")
                    //排除登录接口
                    .excludePathPatterns("/user/login");
        }
    
    • 创建Controller进行测试
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author admin
     */
    @RestController
    @RequestMapping("user")
    public class UserLoginController {
    
        @Value("${Login.username}")
        private String realUsername;
    
        @Value("${Login.password}")
        private String realPassword;
    
        @GetMapping("login")
        public String login(String username, String password) {
            if (username.equals(realUsername) && password.equals(realPassword)) {
                User u = new User();
                u.setPassword(password);
                u.setUsername(username);
                return JWTUtils.getToken(u);
            }
            return "登录失败!账号或者密码不对!";
        }
    
        @GetMapping("test")
        public String test()  {
            return "访问test - API";
        }
    }
    

    ————————————————

    关于有效期
    由于jwt是直接给用户的,只要能验证成功的jwt都可以被视作登录成功,所以,如果不给jwt设置一个过期时间的话,用户只要存着这个jwt,就相当于永远登录了,而这是不安全的,因为如果这个令牌泄露了,那么服务器是没有任何办法阻止该令牌的持有者访问的(因为拿到这个令牌就等于随便冒充你身份访问了),所以往往jwt都会有一个有效期,通常存在于载荷部分,下面是一段生成jwt的java代码:

    return JWT.create().withAudience(userId)
                    .withIssuedAt(new Date())        <---- 发行时间
                    .withExpiresAt(expiresDate)     <---- 有效期
                    .withClaim("sessionId", sessionId)
                    .withClaim("userName", userName)
                    .withClaim("realName", realName)
                    .sign(Algorithm.HMAC256(userId+"HelloLehr"));
    

    在实际的开发中,令牌的有效期往往是越短越安全,因为令牌会频繁变化,即使有某个令牌被别人盗用,也会很快失效。但是有效期短也会导致用户体验不好(总是需要重新登录),所以这时候就会出现另外一种令牌—refresh token刷新令牌。刷新令牌的有效期会很长,只要刷新令牌没有过期,就可以再申请另外一个jwt而无需登录(且这个过程是在用户访问某个接口时自动完成的,用户不会感觉到令牌替换),对于刷新令牌的具体实现这里就不详细讲啦(其实因为我也没深入研究过XD…)
    ————————————————

    对比Session
    在传统的session会话机制中,服务器识别用户是通过用户首次访问服务器的时候,给用户一个sessionId,然后把用户对应的会话记录放在服务器这里,以后每次通过sessionId来找到对应的会话记录。这样虽然所有的数据都存在服务器上是安全的,但是对于分布式的应用来说,就需要考虑session共享的问题了,不然同一个用户的sessionId的请求被自动分配到另外一个服务器上就等于失效了

    而Jwt不但可以用于登录认证,也把相应的数据返回给了用户(就是载荷里的内容),通过签名来保证数据的真实性,该应用的各个服务器上都有统一的验证方法,只要能通过验证,就说明你的令牌是可信的,我就可以从你的令牌上获取你的信息,知道你是谁了,从而减轻了服务器的压力,而且也对分布式应用更为友好。(毕竟就不用担心服务器session的分布式存储问题了)

    整合Springboot

    导入java-jwt包:

    这个包里实现了一系列jwt操作的api(包括上面讲到的怎么校验,怎么生成jwt等等)

    如果你是Maven玩家:

    pom.xml里写入

    <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.8.3</version>
    </dependency>
    

    如果你是Gradle玩家:

    build.gradle里写入

    compile group: 'com.auth0', name: 'java-jwt', version: '3.8.3'

    如果你是其他玩家:

    maven中央仓库地址点这里

    工具类的编写

    代码如下:

    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTDecodeException;
    import com.auth0.jwt.interfaces.Claim;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.io.Serializable;
    import java.util.Calendar;
    import java.util.Date;
    
    /**
     * @author Lehr
     * @create: 2020-02-04
     */
    public class JwtUtils {
    
        /**
         签发对象:这个用户的id
         签发时间:现在
         有效时间:30分钟
         载荷内容:暂时设计为:这个人的名字,这个人的昵称
         加密密钥:这个人的id加上一串字符串
         */
        public static String createToken(String userId,String realName, String userName) {
    
            Calendar nowTime = Calendar.getInstance();
            nowTime.add(Calendar.MINUTE,30);
            Date expiresDate = nowTime.getTime();
    
            return JWT.create().withAudience(userId)   //签发对象
                    .withIssuedAt(new Date())    //发行时间
                    .withExpiresAt(expiresDate)  //有效时间
                    .withClaim("userName", userName)    //载荷,随便写几个都可以
                    .withClaim("realName", realName)
                    .sign(Algorithm.HMAC256(userId+"HelloLehr"));   //加密
        }
    
        /**
         * 检验合法性,其中secret参数就应该传入的是用户的id
         * @param token
         * @throws TokenUnavailable
         */
        public static void verifyToken(String token, String secret) throws TokenUnavailable {
            DecodedJWT jwt = null;
            try {
                JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();
                jwt = verifier.verify(token);
            } catch (Exception e) {
                //效验失败
                //这里抛出的异常是我自定义的一个异常,你也可以写成别的
                throw new TokenUnavailable();
            }
        }
    
        /**
        * 获取签发对象
        */
        public static String getAudience(String token) throws TokenUnavailable {
            String audience = null;
            try {
                audience = JWT.decode(token).getAudience().get(0);
            } catch (JWTDecodeException j) {
                //这里是token解析失败
                throw new TokenUnavailable();
            }
            return audience;
        }
    
    
        /**
        * 通过载荷名字获取载荷的值
        */
        public static Claim getClaimByName(String token, String name){
            return JWT.decode(token).getClaim(name);
        }
    }
    

    注解类的编写

    在controller层上的每个方法上,可以使用这些注解,来决定访问这个方法是否需要携带token,由于默认是全部检查,所以对于某些特殊接口需要有免验证注解

    免验证注解

    @PassToken:跳过验证,通常是入口方法上用这个,比如登录接口

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Lehr
     * @create: 2020-02-03
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PassToken {
        boolean required() default true;
    }
    

    拦截器的编写

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
    * @author lehr
    */
    @Configuration
    public class JwtInterceptorConfig implements WebMvcConfigurer {
       @Override
       public void addInterceptors(InterceptorRegistry registry) {
    
           //默认拦截所有路径
           registry.addInterceptor(authenticationInterceptor())
                   .addPathPatterns("/**");
       }
       @Bean
       public JwtAuthenticationInterceptor authenticationInterceptor() {
           return new JwtAuthenticationInterceptor();
       }
    }  
    ————————————————
    import com.auth0.jwt.interfaces.Claim;
    import com.imlehr.internship.annotation.PassToken;
    import com.imlehr.internship.dto.AccountDTO;
    import com.imlehr.internship.exception.NeedToLogin;
    import com.imlehr.internship.exception.UserNotExist;
    import com.imlehr.internship.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    /**
     * @author Lehr
     * @create: 2020-02-03
     */
    public class JwtAuthenticationInterceptor implements HandlerInterceptor {
        @Autowired
        AccountService accountService;
    
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
            // 从请求头中取出 token  这里需要和前端约定好把jwt放到请求头一个叫token的地方
            String token = httpServletRequest.getHeader("token");
            // 如果不是映射到方法直接通过
            if (!(object instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) object;
            Method method = handlerMethod.getMethod();
            //检查是否有passtoken注释,有则跳过认证
            if (method.isAnnotationPresent(PassToken.class)) {
                PassToken passToken = method.getAnnotation(PassToken.class);
                if (passToken.required()) {
                    return true;
                }
            }
            //默认全部检查
            else {
                System.out.println("被jwt拦截需要验证");
                // 执行认证
                if (token == null) {
                    //这里其实是登录失效,没token了   这个错误也是我自定义的,读者需要自己修改
                    throw new NeedToLogin();
                }
                
                // 获取 token 中的 user Name
                String userId = JwtUtils.getAudience(token);
    
                //找找看是否有这个user   因为我们需要检查用户是否存在,读者可以自行修改逻辑
                AccountDTO user = accountService.getByUserName(userId);
    
                if (user == null) {
                    //这个错误也是我自定义的
                    throw new UserNotExist();
                }
    
                // 验证 token 
                JwtUtils.verifyToken(token, userId)
                    
                //获取载荷内容
            	String userName = JwtUtils.getClaimByName(token, "userName").asString();
            	String realName = JwtUtils.getClaimByName(token, "realName").asString();
            	
                //放入attribute以便后面调用
                request.setAttribute("userName", userName);
            	request.setAttribute("realName", realName);
                
    
                return true;
    
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest,
                               HttpServletResponse httpServletResponse,
                               Object o, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    Object o, Exception e) throws Exception {
        }
    }
    

    这段代码的执行逻辑大概是这样的:

    目标方法是否有注解?如果有PassToken的话就不用执行后面的验证直接放行,不然全部需要验证
    开始验证:有没有token?没有?那么返回错误
    从token的audience中获取签发对象,查看是否有这个用户(有可能客户端造假,有可能这个用户的账户被冻结了),查看用户的逻辑就是调用Service方法直接比对即可
    检验Jwt的有效性,如果无效或者过期了就返回错误
    Jwt有效性检验成功:把Jwt的载荷内容获取到,可以在接下来的controller层中直接使用了(具体使用方法看后面的代码)
    接口的编写
    这里设计了两个接口:登录和查询名字,来模拟一个迷你业务,其中后者需要登录之后才能使用,大致流程如下:

    登录代码

    /**
         * 用户登录:获取账号密码并登录,如果不对就报错,对了就返回用户的登录信息
         * 同时生成jwt返回给用户
         *
         * @return
         * @throws LoginFailed  这个LoginFailed也是我自定义的
         */
        @PassToken
        @GetMapping(value = "/login")
        public AccountVO login(String userName, String password) throws LoginFailed{
            
            try{
                service.login(userName,password);
            }
            catch (AuthenticationException e)
            {
                throw new LoginFailed();
            }
    
            //如果成功了,聚合需要返回的信息
            AccountVO account = accountService.getAccountByUserName(userName);
            
            //给分配一个token 然后返回
            String jwtToken = JwtUtils.createToken(account);
    
            //我的处理方式是把token放到accountVO里去了
            account.setToken(jwtToken);
    
            return account;
    
        }
    

    业务代码

    这里列举一个需要登录,用来测试用户名字的接口(其中用户的名字来源于jwt的载荷部分)

    @GetMapping(value = "/username")
        public String checkName(HttpServletRequest req) {
            //之前在拦截器里设置好的名字现在可以取出来直接用了
            String name = (String) req.getAttribute("userName");
            return name;
        }

    springboot系列——整合jwt

    jwt 全称Json web token,翻译为网络中基于JSON传输的一种token。该token是无状态的,特别适合于分布式场景。
    jwt的相关介绍,这里不再赘述。理论需要实践来验证,但又抽象于实践。两者相辅相成,jwt的相关理论定义,有的作者已经总结的很到位了,这里主要总结如何快速将jwt整合进springboot。
    首先,老样子,引入依赖jar:

    <!-- JWT依赖 -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.7.0</version>
    </dependency>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>
    

    添加请求拦截器WebConfig(实现WebMvcConfigurer重写addInterceptors),对请求url进行token认证

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Resource
        private JwtFilter jwtFilter ;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(jwtFilter).addPathPatterns("/**");
        }
    }

    新增拦截器JwtFilter

    @Component
    public class JwtFilter extends HandlerInterceptorAdapter {
    
        public static final String LOGIN_URL = "/login";
    
        @Resource
        private JwtTokenUtil jwtTokenUtil;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws SignatureException {
            String uri = request.getRequestURI();
            if(uri.contains(LOGIN_URL) || uri.contains("/doc.html") || uri.contains("/swagger-resources") ){
                return true;
            }
            //获取token
            String token = request.getHeader(jwtTokenUtil.header);
            if(StringUtils.isEmpty(token)){
                token = request.getParameter(jwtTokenUtil.header);
            }
            if(StringUtils.isEmpty(token)){
                throw new SignatureException(jwtTokenUtil.header+"不能为空");
            }
    
            //判断token是否超时
            Claims claims = jwtTokenUtil.getTokenClaim(token);
            if(null == claims || jwtTokenUtil.isTokenExpired(claims.getExpiration())){
                throw new SignatureException(jwtTokenUtil.header+"失效,请重新登录");
            }
            return true;
        }
    }
    

    异常处理类,springAop实现(异常通知)

    @RestControllerAdvice
    public class SecurityExceptionHandler {
        @ExceptionHandler(value = {SignatureException.class})
        public Result authorizationException(SignatureException e){
            return Result.failedWith(null,CodeEnum.FORBBIDEN.getCode(),"权限不足");
        }
    }

    tokenUtil工具类

    @Component
    public class JwtTokenUtil {
        @Value("${jwt.secret}")
        public String secret;
        @Value("${jwt.expire}")
        public int expire;
        @Value("${jwt.header}")
        public String header;
    
        /**
         * 生成token
         * @param subject
         * @return
         */
        public String createToken (String subject){
            Date nowDate = new Date();
            Date expireDate = new Date(nowDate.getTime() + expire * 1000);
            return Jwts.builder()
                    .setHeaderParam("typ", "JWT")
                    .setSubject(subject)
                    .setIssuedAt(nowDate)
                    .setExpiration(expireDate)
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }
        /**
         * 获取token中注册信息
         * @param token
         * @return
         */
        public Claims getTokenClaim (String token) {
            try {
                return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            }catch (Exception e){
                return null;
            }
        }
        /**
         * 验证token是否过期失效
         * @param expirationTime
         * @return
         */
        public boolean isTokenExpired (Date expirationTime) {
            return expirationTime.before(new Date());
        }
    
        /**
         * 获取token失效时间
         * @param token
         * @return
         */
        public Date getExpirationDateFromToken(String token) {
            return getTokenClaim(token).getExpiration();
        }
        /**
         * 获取用户名从token中
         */
        public String getUsernameFromToken(String token) {
            return getTokenClaim(token).getSubject();
        }
    
        /**
         * 获取jwt发布时间
         */
        public Date getIssuedAtDateFromToken(String token) {
            return getTokenClaim(token).getIssuedAt();
        }
    }
    

    类中使用配置添加,application.yml中增加token的有效时间等

    jwt:
      # 加密密钥
      secret: abcdefg1234567
      # token有效时长
      expire: 3600
      # header 名称
      header: token
    

    至此,我们的jwt相关的引入配置工作都基本完成。下面我们要对jwt相关的验证进行使用:
    登录,获取jwt授权生成token,这里验证用户名和密码的工作我们暂时搁置(后面引入springsecurity来做这项工作)

    jwt生成token使用

    @Resource
    JwtTokenUtil jwtTokenUtil;
    
    @Override
    public String login(String userName, String password) {
        //验证用户名密码
        ......
        //生成token
        return jwtTokenUtil.createToken("admin");
    }
    

    其他的接口我们还是沿用之前的查询接口。

    按照以前没有加入jwt认证的方式去调用下用户详情接口(不加token请求),我们就会发现下面的情况:
     
    image.png

    请求被拒绝了,提示权限不足。。。

    调用用户登录接口进行访问:
     
    image.png

    登录成功后将token放入header中,再次调用用户详情接口,bingo,成功啦!!! 将返回的token放入用户详情接口header中,再次请求:
     
    image.png

    至此,jwt的配置和使用我们就已经实操一遍了。

    --------------------------------------------------------------------------------

    springboot整合JWT

    一、JWT介绍

    JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。

    在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。

     

    1.JWT的格式

    JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    A由JWT头部信息header加密得到

    B由JWT用到的身份验证信息json数据加密得到

    C由A和B加密得到,是校验部分

     

    2.怎样使用token?

    可以放到HTTP请求的请求头中,通常是Authorization字段。

     

    3.流程图

    二、java代码实现

    springboot经典的四个步骤
    	1.改pom
    	2.写yml/properties
    	3.启动类
    	4.代码
    

    1.maven依赖

    	<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    

    2.配置application.properties

    没用这个里面的

    server.port=8087
    # 要加密的明文
    jwt.secret=hand2020
    # tocken 过期时间,单位秒
    jwt.expire=300
    
    
    

    3.启动类

    这里选择使用@ServletComponentScan,是因为在Filter类用@component和@configuration会导致

    @WebFilter(urlPatterns = “/testToken”, filterName = “jwtFilter”) url失效变成拦截所有

    package com.example.bootjwt;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    @SpringBootApplication
    @ServletComponentScan //这里是将filter扫描加载进spring容器
    public class BootJwtApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(BootJwtApplication.class, args);
        }
    
    }
    
    

    4.实现代码

    这里主要是做一个简单的demo验证,有三个类JwtController、CreatToken、JwtFilter。

    JwtController:用来接收rest请求。

    JwtUtil:用来生成token,解密token,验证token

    JwtFilter:用来拦截请求对http请求中携带的token进行验证

     

    JwtController

    package com.example.bootjwt.controller;
    
    import com.example.bootjwt.Util.JwtUtil;
    import com.example.bootjwt.domain.User;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @RestController
    public class JwtController {
    
        @PostMapping("/get")
        public String creatToken2(){
            User user = new User();
            user.setId("1");
            user.setUsername("hand2020");
            user.setPassword("123456");
            return JwtUtil.createJWT(40000,user);
        }
    
        @PostMapping("/test")
        public String testToken2(HttpServletRequest request, HttpServletResponse response){
            String token= request.getHeader("Authorization");
            User user = new User();
            user.setId("1");
            user.setUsername("hand2020");
            user.setPassword("123456");
            if (JwtUtil.isVerify(token,user)){
                return "success";
            }
            return "fail";
        }
    }
    
    

    JwtUtil

    这里我是在配置文件中读需要加密的明文,和过期时间。也可以在controller里处理参数设置。

    package com.example.bootjwt.Util;
    
    import com.example.bootjwt.domain.User;
    import io.jsonwebtoken.*;
    import org.springframework.beans.factory.annotation.Value;
    
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    public class JwtUtil {
    
    
    
    //    @Value("${jwt.secret}")
    //    private static String key;
        /**
         * 用户登录成功后生成Jwt
         * 使用Hs256算法  私匙使用用户密码
         *
         * @param ttlMillis jwt过期时间
         * @param user      登录成功的user对象
         * @return
         */
        public static String createJWT(long ttlMillis, User user) {
            //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    
            //生成JWT的时间
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
    
            //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
            Map<String, Object> claims = new HashMap<String, Object>();
            claims.put("id", user.getId());
            claims.put("username", user.getUsername());
            claims.put("password", user.getPassword());
    
            //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
            String key = user.getPassword();
    
            //生成签发人
            String subject = user.getUsername();
    
    
    
            //下面就是在为payload添加各种标准声明和私有声明了
            //这里其实就是new一个JwtBuilder,设置jwt的body
            JwtBuilder builder = Jwts.builder()
                    //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                    .setClaims(claims)
                    //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                    .setId(UUID.randomUUID().toString())
                    //iat: jwt的签发时间
                    .setIssuedAt(now)
                    //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                    .setSubject(subject)
                    //设置签名使用的签名算法和签名使用的秘钥
                    .signWith(signatureAlgorithm, key);
            if (ttlMillis >= 0) {
                long expMillis = nowMillis + ttlMillis;
                Date exp = new Date(expMillis);
                //设置过期时间
                builder.setExpiration(exp);
            }
            return builder.compact();
        }
    
    
        /**
         * Token的解密
         * @param token 加密后的token
         * @param user  用户的对象
         * @return
         */
        public static Claims parseJWT(String token, User user) {
            //签名秘钥,和生成的签名的秘钥一模一样
            String key = user.getPassword();
    
            //得到DefaultJwtParser
            Claims claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(key)
                    //设置需要解析的jwt
                    .parseClaimsJws(token).getBody();
            return claims;
        }
    
    
        /**
         * 校验token
         * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
         * @param token
         * @param user
         * @return
         */
        public static Boolean isVerify(String token, User user) {
            //签名秘钥,和生成的签名的秘钥一模一样
            String key = user.getPassword();
    		//Jwts.parser在执行parseClaimsJws(token)时如果token时间过期会抛出ExpiredJwtException异常
            try {
                //得到DefaultJwtParser
                Claims claims = Jwts.parser()
                        //设置签名的秘钥
                        .setSigningKey(key)
                        //设置需要解析的jwt
                        .parseClaimsJws(token).getBody();
                if (claims.get("password").equals(user.getPassword())) {
                    return true;
                }
    
            }catch (ExpiredJwtException e){
                e.printStackTrace();
            }
            return false;
        }
    
    }
    
    
    

    JwtFilter

    过滤器是通过实现Filter接口,注意@WebFilter相当于xml配置,但是需要在启动类上注解

    @ServletComponentScan,将JwtFilter加入到spring容器中。

    在JwtFilter类上注解@component或@configuration会导致@WebFilter失效从而拦截所有请求

     

    目前这个没用到,直接在controller里做了判断,这个是后续业务需求的demo

    package com.example.bootjwt;
    
    import com.example.bootjwt.Util.JwtUtil;
    import com.example.bootjwt.domain.User;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    @WebFilter(urlPatterns = "/testToken", filterName = "jwtFilter")
    public class JwtFilter implements Filter {
    
        @Autowired
        private CreatToken creatToken;
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setHeader("Access-Control-Allow-Origin", "*");
            String token= request.getHeader("Authorization");
            User user = new User();
            user.setId("1");
            user.setUsername("hand2020");
            user.setPassword("123456");
            boolean flag = JwtUtil.isVerify(token,user);
            if (flag){
                filterChain.doFilter(servletRequest,servletResponse);
            }else {
                System.out.println("失败。。。。。。。。");
                response.getWriter().write("失败。。。。。。。。");
            }
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    
    

    ##三、 测试效果
    由于没有写前端代码,就用postman模拟请求

    1.浏览器发送请求获取token

    http://localhost:8087/get

    2.将token放入请求头中请求

    注意将上次请求获得的token放入请求头内(注意不要过太长时间因为token设置了40秒过期)

    token超时:

    总结

    这个springboot整合jwt只是一个很简单的demo,并不是真正业务中使用方式。后面我会写一个单点登录的例子,会用到jwt。现在这里做一个入门练习

     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    配置yum源时出现Your license is invalid.
    服务器主板损坏更换步骤
    服务器启动无法进入操作系统,自动进入 PXE 启动并提示 Reboot and Select proper Boot device,应该如何处理
    硬盘亮红灯,在 LSI 3108 RAID 卡 PD 菜单中看到硬盘状态 为UB 状态
    烽火服务器 BMC事件日志不停刷CATERR has occurredAssertion
    LSI 3108 RAID 卡在开机自检时出现 “battery hardware is missing”报错
    烽火服务器 BMC 升级完成后控制台无法打开并出现 Connection_failed 报错
    服务器出现风扇告警Lower Critical going low Assertion
    服务器按电源键没有反应无法开机,应该如何排查
    华为服务器 IBMC查看物理盘状态是正常,但无法显示出硬盘容量
  • 原文地址:https://www.cnblogs.com/hanease/p/16381898.html
Copyright © 2020-2023  润新知