• JWT的token认证


    JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
     
    一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
    头部(Header)
    头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以 被表示成一个JSON对象。
       {"typ":"JWT","alg":"HS256"}
    在头部指明了签名算法是HS256算法。 我们进行BASE64编 码
    编码后的字符串如下:
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

      载荷(playload)
    
    
      载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
    
    
    (1)标准中注册的声明(建议但不强制使用)
    
    
        iss: jwt签发者
    
    
        sub: jwt所面向的用户
    
    
        aud: 接收jwt的一方
    
    
        exp: jwt的过期时间,这个过期时间必须要大于签发时间
    
    
        nbf: 定义在什么时间之前,该jwt都是不可用的.
    
    
        iat: jwt的签发时间
    
    
          jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    
    
    (2)公共的声明
    
    
       公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    
    
    (3)私有的声明
    
    
       私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    
    
        这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而
    
    
    private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
    
    
      定义一个payload:
    
    
        {"sub":"1234567890","name":"John Doe","admin":true}
    
    
        然后将其进行base64编码,得到Jwt的第二部分。
    
    
        eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    
    
    签证(signature)
    
    
         jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
    
    
         header (base64后的)
    
    
         payload (base64后的)
    
    
          secret
    
    
          这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
         TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    
    
    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
    
    
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    
    
    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
      Java的JJWT实现JWT
     
      引入依赖 

    <dependency>
       <groupId>io.jsonwebtoken</groupId> 
       <artifactId>jjwt</artifactId> 
    <version>0.6.0</version>
    </dependency>

      

    //生成token
    JwtBuilder builder= Jwts.builder().setId("111")
    .setSubject("test")
          .setIssuedAt(new Date()) //签发时间
    .signWith(SignatureAlgorithm.HS256,"testsign"); //签名秘钥
    System.out.println( builder.compact() )

      每次结果是不一样的,因为我们的载荷中包含了时间。

      

    String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiO jE1MjM0MTM0NTh9.gq0J‐cOM_qCNqU_s‐d_IrRytaNenesPmqAIhQpYXHZk";
    //claims相当于一个map集合 Claims claims
    = Jwts.parser().setSigningKey("testsign").parseClaimsJws(token).getBody(); System.out.println("id:"+claims.getId());
    System.out.println("subject:"+claims.getSubject());
    System.out.println("IssuedAt:"+claims.getIssuedAt()); }


     过期时间校验 

    //为了方便测试,我们将过期时间设置为1分钟
     long now = System.currentTimeMillis();//当前时间
     long exp = now + 1000*60;//过期时间为1分钟 
     JwtBuilder builder= Jwts.builder().setId("111") 
        .setSubject("test")
    .setIssuedAt(new Date())
    .signWith(SignatureAlgorithm.HS256,"testsign")
    .setExpiration(new Date(exp)); //过期时间
    System.out.println( builder.compact() );



    //校验
    Claims claims =Jwts.parser().setSigningKey("testsign").parseClaimsJws(token).getBody();
        System.out.println("id:"+claims.getId());
        System.out.println("subject:"+claims.getSubject());
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        System.out.println("签发时间:"+sdf.format(claims.getIssuedAt()));
        System.out.println("过期时间:"+sdf.format(claims.getExpiration()));
        System.out.println("当前时间:"+sdf.format(new Date()) );
     
     
    当过期时会引发io.jsonwebtoken.ExpiredJwtException异常  需要try catch 捕获
     

    自定义claims

       

    //为了方便测试,我们将过期时间设置为1分钟
     long now = System.currentTimeMillis();//当前时间
     long exp = now + 1000*60;//过期时间为1分钟 
     JwtBuilder builder= Jwts.builder().setId("111") 
        .setSubject("test") 
           .setIssuedAt(new Date()) 
           .signWith(SignatureAlgorithm.HS256,"testsign") 
           .setExpiration(new Date(exp)); //过期时间
    .claim("roles","admin") //自定义claim
    .claim("logo","logo.png");
        System.out.println( builder.compact() );

    //获取使用 claims.get()
       System.out.println("roles:"+claims.get("roles"));
       System.out.println("logo:"+claims.get("logo"));


       

    JWT 工具类  

    @ConfigurationProperties("jwt.config")
     public class JwtUtil   { 
     private String key ; 
     private long ttl ;//一个小时
     public String getKey() {
         return key;
     }
    public void setKey(String key) {

    this.key = key;
    }
    public long getTtl() { return ttl; } public void setTtl(long ttl) { this.ttl = ttl;
    }

    /*** 生成JWT ** @param id * @param subject * @return */

    public String createJWT(String id, String subject, String roles) //自行配置参数
    {
    long nowMillis = System.currentTimeMillis();
    Date now = new Date(nowMillis);
    JwtBuilder builder = Jwts.builder().setId(id)
    .setSubject(subject)
    .setIssuedAt(now)
    .signWith(SignatureAlgorithm.HS256, key)
    //.claim("roles", roles);
    if (ttl > 0) {
    builder.setExpiration( new Date( nowMillis + ttl));
    }
         return builder.compact();
    }
    /**
    * 解析JWT
    * @param jwtStr
    * @return
    */
    public Claims parseJWT(String jwtStr){
        return Jwts.parser()
               .setSigningKey(key)
               .parseClaimsJws(jwtStr)
               .getBody();
    }
    }
    application.yml, 添加配置
    jwt:
      config: 
         key: testscret
         ttl: 360000 //1小时
    @Bean   //注入bean容器 
    public JwtUtil jwtUtil(){
    return new util.JwtUtil();
    }

    使用拦截器 实现token鉴权 

     创建拦截器类 

    @Component
    public class JwtInterceptor implements HandlerInterceptor{
        @Autowired
        private JwtUtil jwtUtil;
    
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //无论如何都放行。具体能不能操作还是在具体的操作中去判断。
            //拦截器只是负责把头请求头中包含token的令牌进行一个解析验证。
            String header = request.getHeader("Authorization"); 
    
            if(header!=null && !"".equals(header)){
                //如果有包含有Authorization头信息,就对其进行解析  
                if(header.startsWith("Bearer ")){  //对token进行混淆
                    //得到token
                    String token = header.substring(7);
                    //对令牌进行验证
                    try {
                        Claims claims = jwtUtil.parseJWT(token);
                        String roles = (String) claims.get("roles");
                        if(roles!=null && roles.equals("admin")){
                            request.setAttribute("claims_admin", token);   //放入request中 给control处理
                        }
                        if(roles!=null && roles.equals("user")){
                            request.setAttribute("claims_user", token);
                        }
                    }catch (Exception e){
                        throw new RuntimeException("令牌不正确!");
                    }
                }
            }
            return true;
        }
    }

    配置拦截器类 

    @Configuration
    public class InterceptorConfig extends WebMvcConfigurationSupport {
    
        @Autowired
        private JwtInterceptor jwtInterceptor;
    protected void addInterceptors(InterceptorRegistry registry) { //注册拦截器要声明拦截器对象和要拦截的请求 registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") //拦截路径 .excludePathPatterns("/**/login/**"); //不拦截路径 } }
    //业务层验证令牌
    Claims claims=(Claims)request.getAttribute("user_claims");
    if(claims==null){
    return new Result(false,StatusCode.ACCESSERROR,"无权访问");
    }

    安全配置类 

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //authorizeRequests所有security全注解配置实现的开端,表示开始说明需要的权限。
            //需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限。
            //antMatchers表示拦截什么路径,permitAll任何权限都可以访问,直接放行所有。
            //anyRequest()任何的请求,authenticated认证后才能访问
            //.and().csrf().disable();固定写法,表示使csrf拦截失效。
            http
                    .authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .anyRequest().authenticated()
                    .and().csrf().disable();
        }
    }
  • 相关阅读:
    Exchange调整入站SMTP连接超时时间
    使用WSS和SOFS管理集群存储--Storage
    String构造方法和字符集编码转换
    系统类和数学操作类
    JavaSE——数据类型流、内存流、打印流
    JavaSE——装饰设计模式+简单加密解密工程
    JavaSE——装饰设计模式
    JavaSE——装饰流
    JavaSE —— IO
    JavaSE —— IO简介
  • 原文地址:https://www.cnblogs.com/qin1993/p/12565720.html
Copyright © 2020-2023  润新知