樂優商城第十八天(授權中心與登陸)
阿新 • • 發佈:2019-01-05
很早之前,聽人家做淘淘商城的人一直說單點登入,但是一直不明白單點登陸是什麼,看百度百科
如果是這樣的話,那麼我們這個應該也算是一種單點登陸的解決方案。
我們的登陸是服務端無狀態的登陸
採用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