初識單點登陸及JWT實現

單點登陸

多系統,單一位置登陸,實現多系統同時登陸的一種技術java

(三方登陸:某系統使用其餘系統的用戶,實現本系統登陸的方式。如微信登陸、支付寶登陸)nginx

單點登陸通常是用於互相授信的系統,實現單一位置登陸,全系統有效web

 

1、Session跨域

  所謂 Session 跨域就是摒棄了系統提供的 Session ,而使用自定義的相似 Session 的機制來保存客戶端數據的一種解決方案。算法

  如:經過設置 cookie 的 domain 來實現 cookie 的跨域傳遞。在 cookie 中傳遞一個自定義的 session_id。這個 session_id 是客戶端的惟一標記,將這個標記做爲key,將客戶須要保存的數據做爲value,在服務端進行保存(數據庫保存或nosql保存)。這種機制就是 Session 的跨域解決。spring

  什麼爲跨域:客戶端請求的時候,請求的服務器,不是同一個IP、端口、域名、主機名的時候,都稱爲跨域。sql

  什麼是域:在應用模型中,一個完整的、有獨立訪問路徑的功能集合成爲一個域。數據庫

       如:百度稱爲一個應用或系統,其下有若干個域,如搜索引擎(www.baidu.com),百度貼吧(tie.baidu.com),百度知道(zhidao.baidu.com)等。json

       有時也稱爲多級域名。域的劃分:以IP、端口、域名、主機名爲標準,實現劃分。後端

 

2、Spring Session 共享

  spring-session 技術是 spring 提供的用於處理集羣會話共享的解決方案。spring-session技術是將用戶 session 數據保存到第三方容器中,如數據庫。跨域

  Spring-session 技術是解決同域名下的多服務器集羣 session 共享問題的,不能解決跨域 Session 共享問題

 

3、Nginx Session 共享

  nginx中的 ip_hash 技術可以將某個 ip 的請求定向到同一臺後端,這樣一來這個ip下的某個客戶端和某個後端就能創建起穩固的session,ip_hash是在upstream配置中定義的

 

4、Token身份認證

  使用基於 Token 的身份驗證方法,在服務端不須要存儲用戶的登陸記錄,大概流程以下:

  1)客戶端使用用戶名、密碼請求登陸

  2)服務端收到請求、去驗證用戶名與密碼

  3)驗證成功後,服務端會簽發y一個 token ,再把這個 token 發送給客戶端

  4)客戶端收到 token 之後能夠把它存儲起來,好比放在 cookie 裏或者 Local Storage裏

  5)客戶端每次向服務器請求資源的時候須要帶着服務器簽發的 token

  6)服務端收到請求,而後去驗證客戶端請求裏面帶着的 token,若是驗證成功,就向客戶端返回請求的數據

  使用token的優點:

  無狀態、可擴展:

    在客戶端存儲的 token 是無狀態的,而且可以被擴展,基於這種無狀態和不存儲session信息,負載均衡器可以將用戶信息從一個服務傳到其餘服務器上。

  安全性:

    請求中發送token而再也不發送cookie可以防止CSRF(跨域請求僞造)。即便在客戶端使用cookie存儲token。cookie也僅僅是一個存儲機制而不是用於認證。

  不將信息存儲在session中,讓咱們少了對session的操做。

 

5、JSON Web Token(JWT)機制

  JWT是一種緊湊且自包含的,用於在多方傳遞 json 對象的技術。傳遞的數據可使用數字簽名增長其安全性。可使用HMAC加密算法或RSA公鑰/私鑰加密方式。

  緊湊:數據小,能夠經過URL、POST參數,請求頭髮送,且數據小表明傳輸速度快。

  自包含:使用 payload 數據塊j記錄用戶必要且不隱私的數據,能夠有效的減小數據庫訪問次數,提升代碼性能

  JWT通常用於處理用戶身份校驗或數據信息交換

  JWT的數據結構

    JWT的數據結構:A.B.C  以.(點)來劃分

    A-header  頭信息

    B-payload  (有效荷載?)

    C-Signature  簽名

  header:

  數據結構:{"alg":"加密算法名稱","typ":"JWT"}

  alg能夠有 HMAC 或 SHA256 或 RSA 等

  payload:主要分爲三部分:已註冊信息、公開數據、私有數據

  singature:

    簽名信息,這是一個由開發者提供的信息。是服務器驗證的傳遞的數據是否有效安全的標準。  

  執行流程

  

 

 簡單實現

1)造數據 JWTUsers模擬數據庫用戶名密碼

package cn.zytao.taosir;

import java.util.HashMap;
import java.util.Map;

/**
 * 用於模擬用戶數據的,開發中應訪問數據庫驗證用戶
 * @author TAOSIR
 *
 */
public class JWTUsers {
    
    private static final Map<String,String> USERS =new HashMap<>(11);
    
    static {
        for(int i=0;i<10;i++) {
            USERS.put("admin"+i, "pwd"+i);
        }
    }
    
    //驗證是否能夠登陸
    public static boolean isLogin(String username,String pwd) {
        if(null == username || username.trim().length()==0)
            return false;
        String obj=USERS.get(username);
        if(null ==obj||!obj.equals(pwd))
            return false;
        return true;
    }
}

2)JWTSubject

package cn.zytao.taosir;
/**
 * 做爲Subject數據使用,也就是payload中保存的public claims
 * 其中不該包含任何敏感數據
 * 開發中建議使用實體類型,或BO,DTO數據對象
 * @author TAOSIR
 *
 */
public class JWTSubject {
    
    private String username;
    
    public JWTSubject() {
        super();
    }
    
    public JWTSubject(String username) {
        super();
        this.username = username;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username=username;
    }
}

3)JWT結果對象

package cn.zytao.taosir;
/**
 * 做爲Subject數據使用,也就是payload中保存的public claims
 * 其中不該包含任何敏感數據
 * 開發中建議使用實體類型,或BO,DTO數據對象
 * @author TAOSIR
 *
 */
public class JWTSubject {
    
    private String username;
    
    public JWTSubject() {
        super();
    }
    
    public JWTSubject(String username) {
        super();
        this.username = username;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username=username;
    }
}

4)響應對象

package cn.zytao.taosir;

public class JWTResponseData {
    
    private Integer code;//返回碼
    
    private Object data;//業務數據
    
    private String msg;//返回描述
    
    private String token;//身份標識

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

5)JWT控制類

package cn.zytao.taosir;
/**
 * JWT工具類
 * @author TAOSIR
 *
 */

import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

public class JWTUtils {
    
    private static final String JWT_SECERT = "test_jwt_secert";//服務器的key,密鑰
    private static final ObjectMapper MAPPER = new ObjectMapper();//用戶java對象和json字符串轉換
    public static final int JWT_ERRCODE_EXPIRE = 1005;//Token過時
    public static final int JWT_ERRCODE_FAIL = 1006;//驗證不經過
    
    public static SecretKey generalKey() {
        try {
            byte[] encodedKey=JWT_SECERT.getBytes("UTF-8");
            SecretKey key=new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
            return key;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 簽發JWT,即建立token的方法
     * @param id    jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊
     * @param iss    jwt簽發者
     * @param subject    jwt所面向的用戶,payload中記錄的public,claims,當前環境中就是用戶的登陸名
     * @param ttlMills    有效期,單位毫秒
     * @return
     */
    public static String createJWT(String id,String iss,String subject,long ttlMillis) {
        //加密算法
        SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now=new Date(nowMillis);
        SecretKey secretKey=generalKey();
        //建立JWT的構造器用於生成token
        JwtBuilder builder=Jwts.builder()
                .setId(id)
                .setIssuer(iss)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey);
        if(ttlMillis >= 0) {
            long expMillis =nowMillis+ttlMillis;
            Date exDate = new Date(expMillis);
            builder.setExpiration(exDate);
        }
        return builder.compact();
    }
    
    /**
     * 驗證JWT
     * @param jwtStr
     * @return
     */
    public static JWTResult validateJWT(String jwtStr) {
        JWTResult checkResult=new JWTResult();
        Claims claims=null;
        try {
            claims=parseJWT(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setSuccess(false);
            checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
        } catch (SignatureException e) {
            checkResult.setSuccess(true);
            checkResult.setErrCode(JWT_ERRCODE_FAIL);
        }
        return checkResult;
    }
    
    /**
     * 解析JWT字符串
     * @param jwt    就是token
     * @return
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey=generalKey();
        //getBody獲取值就是token中記錄的payload數據,就是其中保存的claims
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    }
    
    /**
     * 生成subject信息
     * @param subObj
     * @return
     */
    public static String generalSubject(Object subObj) {
        try {
            return MAPPER.writeValueAsString(subObj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

6)寫個簡單的controller實踐

package cn.zytao.taosir.controller;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.zytao.taosir.JWTResponseData;
import cn.zytao.taosir.JWTResult;
import cn.zytao.taosir.JWTSubject;
import cn.zytao.taosir.JWTUsers;
import cn.zytao.taosir.JWTUtils;

@RestController
public class JWTController {

    @RequestMapping("testAll")
    public Object testAll(HttpServletRequest request) {
        String token=request.getHeader("Authorization");
        JWTResult result=JWTUtils.validateJWT(token);
        
        JWTResponseData responseData=new JWTResponseData();
        
        if(result.isSuccess()) {
            responseData.setCode(200);
            responseData.setData(result.getClaims().getSubject());
            String newToken=JWTUtils.createJWT(result.getClaims().getId(), result.getClaims().getIssuer(), result.getClaims().getSubject(), 1*60*1000);
            responseData.setToken(newToken);
            return responseData;
        }else {
            responseData.setCode(500);
            responseData.setMsg("用戶未登陸");
            return responseData;
        }
    }
    
    @RequestMapping("login")
    public Object login(String username,String password) {
        JWTResponseData responseData=null;
        //認證用戶信息
        if(JWTUsers.isLogin(username, password)) {
            JWTSubject subject=new JWTSubject(username);
            String jwtToken = JWTUtils.createJWT(UUID.randomUUID().toString(),"sxt-test-jwt", JWTUtils.generalSubject(subject), 1*60*1000);
            responseData=new JWTResponseData();
            responseData.setCode(200);
            responseData.setData(null);
            responseData.setMsg("登陸成功");
            responseData.setToken(jwtToken);
        }else {
            responseData=new JWTResponseData();
            responseData.setCode(500);
            responseData.setData(null);
            responseData.setMsg("登陸失敗");
            responseData.setToken(null);
        }
        return responseData;
    }
}

7)Postman查看狀況

相關文章
相關標籤/搜索