JSON格式WEB令牌JWT(json+web+token)

一。jwt简介

    传统web项目认证流程

   当用户登录成功后,将用户信息保存在session中,session的唯一标识符jessionid保存在cookie中,当浏览器再次访问时,属于同一域名下,自动传递cookie从而达到有状态性获取服务器的session信息。

    多个web平台一套认证机制

这就需要跨域的单点登录问题,一般的方案是结合springsession+redis+oauth2的思路,
具体参考:https://blog.csdn.net/liaomin416100569/article/details/78871969
    这里涉及到token的概念,一旦某个平台登录,会通过请求将token传递到其他需要单点登录的平台,平台写到自己的cookie或者localStorage中

  基于jwt的认证机制
   上面两种模式都是将数据存储在服务器端,与客户端交互只需要传递较小cookie(32sessionid),由于使用redis存储session的高效性,第二种模式也是我推荐的。

   基于jwt的认证模式,将所有登录后的用户信息存储在一个token加密串中,该token字符串你可以使用cookie也可以使用localStorage存储,请求时可以使用参数或者cookie的方式传递到服务器,服务器验证token串获取用户信息,使用jwt方式,服务器无需存储,每次传送都需要传送大量的token信息,服务器验证token需要耗费较大的cpu,这种方式已有特定的格式规范,完备的加密和签名方式,使用起来相对较简单,有配套的api,一般适用于有移动端同服务器交互的项目。
 

二。jwt结构

 jwt实际上是通过json方式来构造一个加密字符串,,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的
JWT 的三个部分依次如下。

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

jwt官网有个debugger工具可以测试构建一个jwt的token串
https://jwt.io/#debugger

JSON格式WEB令牌JWT(json+web+token)_第1张图片
   

Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

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

alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

Payload

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

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

除了官方字段,你还可以在这个部分定义私有字段,实际是键值对

Signature

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

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


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

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

注意:
   JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
   为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
 

三。jwt实战

官网关于实现jwt的库很多,其中使用javascript或者java的都有,这里主要是java
常用的 java-jwt (https://github.com/auth0/java-jwt)
这里模拟一个登录例子,使用springcloud微服务
开发三个maven子项目:

  • springcloud_common:封装公共的实体,jwt生成token,验证token
  • springcloud_userservice:封装成认证微服务,生成token
  • springcloud_userclient:界面端,调用userservice获取token,存储到cookie中,用户发起请求过滤验证token

springcloud_common

添加maven依赖


            com.auth0
            java-jwt
            3.5.0
        
        
            org.projectlombok
            lombok
            1.16.18
        

添加响应结果实体类

package io.github.jiaozi789.sb.entity;

import lombok.Data;

@Data
public class Result {
    private int code;
    private String message;
    private Object data;
    public static Result error(int code,String message){
        Result result=new Result();
        result.code=code;
        result.message=message;
        return result;
    }
    public static Result success(Object data){
        Result result=new Result();
        result.data=data;
        return result;
    }
}

用户实体类

@Data
public class User {
    private String userName;
    private String password;
    private String email;
}

jwt关于token操作工具类

public class JwtUtils {
    public static final String secrect="#$%DFDFG";
    public static final long EXPIRE_DATE=86400000;
    private static Algorithm algorithm = Algorithm.HMAC256(secrect);
    /**
     * 传递内容生成token
     * @param other
     * @return
     */
    public static String genToken(Map other){
        //生成jwt头部
        Map headerClaims = new HashMap();
        headerClaims.put("alg", "HS256");
        headerClaims.put("typ", "JWT");

        JWTCreator.Builder builder=JWT.create().withHeader(headerClaims)
                .withIssuer("jiaozi") //签发人
                .withIssuedAt(new Date())//签发时间
                .withExpiresAt(new Date(new Date().getTime()+EXPIRE_DATE));//设置有效期
        for(Map.Entry me:other.entrySet()){
            builder.withClaim(me.getKey(),me.getValue());
        }
        String token= builder
                .sign(algorithm);
        return token;
    }

    /**
     * 验证token是否有效
     * @param token
     * @return
     */
    public static Map valid(String token){
        try {
            JWTVerifier verifier = JWT.require(algorithm)
                    .acceptLeeway(1) // 1 sec for nbf, iat and exp
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return jwt.getClaims();
        } catch (JWTVerificationException exception){
            return null;
        }
    }
}

springcloud_userservice
对外提供登录的公共认证中心
添加maven依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.2.RELEASE
         
    
    com.example
    springcloud_userservice
    0.0.1-SNAPSHOT
    springcloud_userservice
    Demo project for Spring Boot

    
        1.8
        Greenwich.RC2
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            com.example
            springcloud_commmon
            0.0.1-SNAPSHOT
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

    
        
            spring-milestones
            Spring Milestones
            https://repo.spring.io/milestone
        
    


添加控制层方法:

@RestController
public class UserController {

    @PostMapping("/login")
    public Result login(@RequestBody User user){
        if("admin".equals(user.getUserName()) && "123456".equals(user.getPassword())) {
            Map tokenData = new HashMap<>();
            tokenData.put("userName", user.getUserName());
            String token = JwtUtils.genToken(tokenData);
            return Result.success(token);
        }else{
            return Result.error(888,"登录失败,请输入正确用户名和密码");
        }
    }
}

添加main方法

@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudUserserviceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudUserserviceApplication.class, args);
    }

}

添加application.yml

server:
  port: 8899
spring:
  application:
    name: userservice
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://${regserver_address}:8761/eureka/

启动时 加上 --regserver_address=你的注册中心地址
我这里直接使用docker启动一个注册中心

docker run --net=host -itd springcloud/eureka --name eurekaserver

我在idea中
JSON格式WEB令牌JWT(json+web+token)_第2张图片

 

springcloud_userclient

添加maven依赖
 



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.2.RELEASE
         
    
    com.example
    springcloud_userclient
    0.0.1-SNAPSHOT
    springcloud_userclient
    Demo project for Spring Boot

    
        1.8
        Greenwich.RC2
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
            com.example
            springcloud_commmon
            0.0.1-SNAPSHOT
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

    
        
            spring-milestones
            Spring Milestones
            https://repo.spring.io/milestone
        
    


添加application.yml配置

server:
  port: 8000
spring:
  application:
    name: userclient
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://${regserver_address}:8761/eureka/

编写一个client端调用userservice端的feign接口

@FeignClient("userservice")
public interface UserService {

    @PostMapping("/login")
    public Result login(@RequestBody User user);
}

添加登录控制层

@Controller
public class LoginController {

    @Autowired
    private UserService userService;
    @PostMapping("/rlogin")
    public String login(User user, HttpServletResponse response){
        Result result = userService.login(user);
        if(result.getCode()==0) {
            Cookie cookie = new Cookie("auth_token", result.getData().toString());
            cookie.setMaxAge((int)JwtUtils.EXPIRE_DATE/1000);
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
        }
        return "redirect:/suc.html";
    }
}

添加过滤器

package io.github.jiaozi789.sb.web;

import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import io.github.jiaozi789.utils.JwtUtils;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

@Configuration
public class MyAuthConfig {
    @Bean
    public FilterRegistrationBean filter(){
        FilterRegistrationBean frb=new FilterRegistrationBean();
        frb.setFilter(new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest request=(HttpServletRequest)servletRequest;
                HttpServletResponse response=(HttpServletResponse)servletResponse;
                if(request.getRequestURI().toString().endsWith("/login.html") ||request.getRequestURI().toString().endsWith("/rlogin") ){
                    filterChain.doFilter(servletRequest,servletResponse);
                    return;
                }
                Cookie[] cookies = request.getCookies();
                String auth_token=null;
                if(cookies!=null)
                for (Cookie c:cookies
                     ) {
                    if(c.getName().equals("auth_token")){
                        auth_token=c.getValue();
                    }
                }
                if(auth_token==null){
                    auth_token=request.getParameter("auth_token");
                }

                if(auth_token!=null){
                    try {
                        Map claims = JwtUtils.valid(auth_token);
                        if(claims!=null) {
                                filterChain.doFilter(servletRequest, servletResponse);
                                return;
                        }
                    } catch (JWTDecodeException exception){
                        response.sendRedirect("/login.html");
                    }
                }
                response.sendRedirect("/login.html");


            }
        });
        frb.setUrlPatterns(Arrays.asList("/*"));
        return frb;
    }
}

添加main方法

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SpringcloudUserclientApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudUserclientApplication.class, args);
    }

}

添加登录页面 static目录下login.html




    
    Title


  
用户名:
密码:

添加成功页面  suc.html




    
    Title


  登录成功

依次启动注册中心,userservice,userclient 访问http://localhost:8000/login.html
输入admin和123456,关闭浏览器重新访问suc.html发现可以登录了
如果需要跨域单点登录,只需要单点登录程序提供一个传入token写入到cookie的路径映射即可
 

 

 

 

你可能感兴趣的:(分布式应用,安全性)