1. 程式人生 > >樂優商城第十八天(授權中心與登陸)

樂優商城第十八天(授權中心與登陸)

很早之前,聽人家做淘淘商城的人一直說單點登入,但是一直不明白單點登陸是什麼,看百度百科


如果是這樣的話,那麼我們這個應該也算是一種單點登陸的解決方案。

我們的登陸是服務端無狀態的登陸

採用jwt+rsa對稱式加密演算法來生成一個令牌,儲存在瀏覽器,瀏覽器下次可以通過攜帶cookie來,我們用公鑰對其解密,如果能夠解出其中的資訊,沒那麼證明這個令牌是正確的,這個人已經登陸。

jwt生成的token是3部分組成的,

頭部,協議資訊以及加密方式

載荷,使用者的資訊

簽名,前兩部分,再加上金鑰,加密生成的,用於驗證整個資料的完整性和可靠性。

我們這裡用到了幾個工具類

RsA加密,生成公鑰,私鑰的方法

/**
 * Created by ace on 2018/5/10.
* * @author HuYi.Zhang */ public class RsaUtils { /** * 從檔案中讀取公鑰 * * @param filename 公鑰儲存路徑,相對於classpath * @return 公鑰物件 * @throws Exception */ public static PublicKey getPublicKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPublicKey(bytes); }
/** * 從檔案中讀取金鑰 * * @param filename 私鑰儲存路徑,相對於classpath * @return 私鑰物件 * @throws Exception */ public static PrivateKey getPrivateKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPrivateKey(bytes); } /** * 獲取公鑰 * * @param bytes 公鑰的位元組形式 * @return *
@throws Exception */ public static PublicKey getPublicKey(byte[] bytes) throws Exception { X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); } /** * 獲取金鑰 * * @param bytes 私鑰的位元組形式 * @return * @throws Exception */ public static PrivateKey getPrivateKey(byte[] bytes) throws Exception { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); } /** * 根據密文,生存rsa公鑰和私鑰,並寫入指定檔案 * * @param publicKeyFilename 公鑰檔案路徑 * @param privateKeyFilename 私鑰檔案路徑 * @param secret 生成金鑰的密文 * @throws IOException * @throws NoSuchAlgorithmException */ public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(secret.getBytes()); keyPairGenerator.initialize(1024, secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair(); // 獲取公鑰並寫出 byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); writeFile(publicKeyFilename, publicKeyBytes); // 獲取私鑰並寫出 byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); writeFile(privateKeyFilename, privateKeyBytes); } private static byte[] readFile(String fileName) throws Exception { return Files.readAllBytes(new File(fileName).toPath()); } private static void writeFile(String destPath, byte[] bytes) throws IOException { File dest = new File(destPath); if (!dest.exists()) { dest.createNewFile(); } Files.write(dest.toPath(), bytes); } }

載荷中的資訊,儲存在一個類中

public abstract class JwtConstans {
public static final String JWT_KEY_ID = "id";
public static final String JWT_KEY_USER_NAME = "username";
}

jwt生成token的方法

/**
 * @author: HuYi.Zhang
 * @create: 2018-05-26 15:43
 **/
public class JwtUtils {
/**
     * 私鑰加密token
     *
     * @param userInfo      載荷中的資料
* @param privateKey    私鑰
* @param expireMinutes 過期時間,單位秒
* @return
     * @throws Exception
*/
public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {
return Jwts.builder()
.claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
.claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
    }
/**
     * 私鑰加密token
     *
     * @param userInfo      載荷中的資料
* @param privateKey    私鑰位元組陣列
* @param expireMinutes 過期時間,單位秒
* @return
     * @throws Exception
*/
public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {
return Jwts.builder()
.claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
.claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
.signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
.compact();
    }
/**
     * 公鑰解析token
     *
     * @param token     使用者請求中的token
     * @param publicKey 公鑰
* @return
     * @throws Exception
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }
/**
     * 公鑰解析token
     *
     * @param token     使用者請求中的token
     * @param publicKey 公鑰位元組陣列
* @return
     * @throws Exception
*/
private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
.parseClaimsJws(token);
    }
/**
     * 獲取token中的使用者資訊
*
     * @param token     使用者請求中的令牌
* @param publicKey 公鑰
* @return 使用者資訊
* @throws Exception
*/
public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
return new UserInfo(
ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }
/**
     * 獲取token中的使用者資訊
*
     * @param token     使用者請求中的令牌
* @param publicKey 公鑰
* @return 使用者資訊
* @throws Exception
*/
public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
return new UserInfo(
ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }
}

將物件轉化為各種資料型別的工具類

public class ObjectUtils {
public static String toString(Object obj) {
if (obj == null) {
return null;
        }
return obj.toString();
    }
public static Long toLong(Object obj) {
if (obj == null) {
return 0L;
        }
if (obj instanceof Double || obj instanceof Float) {
return Long.valueOf(StringUtils.substringBefore(obj.toString(), "."));
        }
if (obj instanceof Number) {
return Long.valueOf(obj.toString());
        }
if (obj instanceof String) {
return Long.valueOf(obj.toString());
        } else {
return 0L;
        }
    }
public static Integer toInt(Object obj) {
return toLong(obj).intValue();
    }
}

我們再建立一個實體類,來接受解析後的使用者資訊

public class UserInfo {
private Long id;
private String username;

當用戶第一次請求的時候,我們會驗證使用者名稱和密碼,並生成token

/**
 * 登陸獲取令牌的方法
* @param username
 * @param password
 * @param request
 * @param response
 * @return
 */
@PostMapping("accredit")
public ResponseEntity<Void> authorization(
@RequestParam("username") String username,
@RequestParam("password") String password,
HttpServletRequest request,
HttpServletResponse response
        ){
String token = this.authService.getToken(username,password);
if (StringUtils.isBlank(token)){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
//將令牌放到cookie,httponly設定為true,防止js修改
CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),token,jwtProperties.getCookieMaxAge(),null,true);
return ResponseEntity.status(HttpStatus.OK).build();
}

service

/**
 *獲取令牌的方法
* @param username
 * @param password
 * @return
 */
public String getToken(String username, String password) {
try {
ResponseEntity<User> userResponseEntity = this.userClient.queryUser(username, password);
if (!userResponseEntity.hasBody()) {
logger.info("使用者資訊不存在,{}", username);
return null;
        }
User user = userResponseEntity.getBody();
//生成令牌
UserInfo userInfo = new UserInfo();
BeanUtils.copyProperties(user, userInfo);
String token = JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
return token;
    } catch (Exception e) {
logger.error("生成令牌的過程中出錯");
return null;
    }
}

這個時候還需要一個工具類,cookieUtils

/**
 * 
 * Cookie 工具類
*
 */
public final class CookieUtils {
static final Logger logger = LoggerFactory.getLogger(CookieUtils.class);
/**
    * 得到Cookie的值, 不編碼
* 
    * @param request
    * @param cookieName
    * @return
    */
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
   }
/**
    * 得到Cookie的值,
    * 
    * @param request
    * @param cookieName
    * @return
    */
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null){
return null;         
      }
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
if (isDecoder) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
               } else {
retValue = cookieList[i].getValue();
               }
break;
            }
         }
      } catch (UnsupportedEncodingException e) {
logger.error("Cookie Decode Error.", e);
      }
return retValue;
   }
/**
    * 得到Cookie的值,
    * 
    * @param request
    * @param cookieName
    * @return
    */
public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null){
return null;         
      }
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
break;
            }
         }
      } catch (UnsupportedEncodingException e) {
logger.error("Cookie Decode Error.", e);
      }
return retValue;
   }
/**
    * 生成cookie,並指定編碼
* @param request 請求
* @param response 響應
* @param cookieName name
    * @param