需求分析 在分享源碼之前,先將b2b2c系統中許可權模塊的需求整理、明確,方便源碼的理解。 業務需求 b2b2c電子商務系統中許可權主要有三個角色:買家、賣家、平臺管理員。 其中賣家角色中又有店員,可以設置店員管理不同的許可權(如商品和訂單的許可權分派給不同的店員),同理平臺管理員也需要進行上述精細許可權的管 ...
需求分析
在分享源碼之前,先將b2b2c系統中許可權模塊的需求整理、明確,方便源碼的理解。
業務需求
-
b2b2c電子商務系統中許可權主要有三個角色:買家、賣家、平臺管理員。
-
其中賣家角色中又有店員,可以設置店員管理不同的許可權(如商品和訂單的許可權分派給不同的店員),同理平臺管理員也需要進行上述精細許可權的管理,買家許可權相對比較單一。
-
如果禁用了某個店員或管理員,則這個用戶需要立刻被登出,保證數據安全性
技術需求
-
去中心化
javashop電商系統採用去中心化、容器化的部署方案,考慮性能及擴展性,鑒權需要採用token的方式,不能採用有中心的session方案
-
公用能力抽象
b2b2c電商體系中存在三端(買家、賣家、管理端),出於性能、穩定性考慮,這三端在部署上是分離的,體現為買家API、賣家API、管理端API,許可權本質上就是攔截這三端的api請求,進行鑒權,這三種角色的鑒權既有通用的邏輯又有個性化的邏輯:
-
通用:token的生成和解析
-
個性化:許可權數據源不同(SecurityMetadataSource)
具體體現就是角色和許可權綁定關係的來源不同:賣家端來自賣家的許可權設置,平臺的來自管理端的許可權設置。
這就要求在架構和代碼實現上做的該重用的重用,該分離的分離。
架構思路
Token解析架構思路:
-
兩個介面分別對應token的解析和token的生成
-
預設實現了一個jwt的實現類
安全認證領域模型架構
-
AuthUser是最上層的可被認證用戶介面
-
User為基礎實現
-
Buyer,Seller,Admin為具體業務實現
基於JWT的許可權認證源碼
TokenManager
Token的業務類介面,有兩個核心的方法:創建和解析token,擴展性的考慮,介面層面並未體現jwt的依賴:
/** * token業務管理介面 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019/12/25 */ public interface TokenManager { /** * 創建token * @param user * @return */ Token create(AuthUser user); /** * 解析token * @param token * @return 用戶對象 */ <T> T parse(Class<T> clz, String token) throws TokenParseException; }
TokenManagerImpl
token業務類基於jwt的實現:
/** * token管理基於twt的實現 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019/12/25 */ @Service public class TokenManagerImpl implements TokenManager { @Autowired private JavashopConfig javashopConfig; @Override public Token create(AuthUser user) { JwtTokenCreater tokenCreater = new JwtTokenCreater(javashopConfig.getTokenSecret()); tokenCreater.setAccessTokenExp(javashopConfig.getAccessTokenTimeout()); tokenCreater.setRefreshTokenExp(javashopConfig.getRefreshTokenTimeout()); return tokenCreater.create(user); } @Override public <T> T parse(Class<T> clz, String token) throws TokenParseException { JwtTokenParser tokenParser = new JwtTokenParser(javashopConfig.getTokenSecret()); return tokenParser.parse(clz, token); } }
Token創建介面
/** * Token創建介面 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019-06-21 */ public interface TokenCreater { /** * 創建token * @param user 用戶 * @return token */ Token create(AuthUser user); }
Token 解析器
/** * Token 解析器 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019-06-21 */ public interface TokenParser { /** * 解析token * @param token * @return 用戶對象 */ <T> T parse(Class<T> clz, String token) throws TokenParseException; }
JwtTokenCreater
基於jwt token的創建實現:
/** * Jwt token 創建實現 * * @author kingapex * @version 1.0 * @since 7.1.0 * 2019-06-21 */ public class JwtTokenCreater implements TokenCreater { /** * jwt秘鑰,需要在構造器中初始化 */ private String secret; /** * 訪問token的有效期,在構造器中初始化,可以通過setter改變 */ private int accessTokenExp; /** * 刷新token的有效期,在構造器中初始化,可以通過setter改變 */ private int refreshTokenExp; /** * 在構造器中初始化參數、預設值 * @param secret */ public JwtTokenCreater(String secret) { this.secret = secret; accessTokenExp=60*60; //預設session失效時間為1小時:60秒 x 60 (=1分鐘) * 60 (=1小時) refreshTokenExp = 60 * 60 * 60; } @Override public Token create(AuthUser user) { ObjectMapper oMapper = new ObjectMapper(); Map buyerMap = oMapper.convertValue(user, HashMap.class); String accessToken = Jwts.builder() .setClaims(buyerMap) .setSubject("user") .setExpiration( new Date(System.currentTimeMillis() + accessTokenExp * 1000)) .signWith(SignatureAlgorithm.HS512, secret.getBytes()) .compact(); String refreshToken = Jwts.builder() .setClaims(buyerMap) .setSubject("user") .setExpiration( new Date(System.currentTimeMillis() +(accessTokenExp+ refreshTokenExp) * 1000)) .signWith(SignatureAlgorithm.HS512, secret.getBytes()) .compact(); Token token = new Token(); token.setAccessToken(accessToken); token.setRefreshToken(refreshToken); return token; } public JwtTokenCreater setSecret(String secret) { this.secret = secret; return this; } public JwtTokenCreater setAccessTokenExp(int accessTokenExp) { this.accessTokenExp = accessTokenExp; return this; } public JwtTokenCreater setRefreshTokenExp(int refreshTokenExp) { this.refreshTokenExp = refreshTokenExp; return this; }
JwtTokenParser
基於jwt的token解析器
/** * jwt token解析器 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019-06-24 */ public class JwtTokenParser implements TokenParser { /** * jwt秘鑰,需要在構造器中初始化 */ private String secret; private Claims claims; public JwtTokenParser(String secret) { this.secret = secret; } @Override public <T> T parse(Class<T> clz, String token) throws TokenParseException { try { claims = Jwts.parser() .setSigningKey(secret.getBytes()) .parseClaimsJws(token).getBody(); T t = BeanUtil.mapToBean(clz, claims); return t; } catch (Exception e) { throw new TokenParseException(e); } }
AuthUser
認證用戶介面
/** * 認證用戶介面 * @author kingapex * @version 1.0 * @since 7.1.0 * 2019-06-21 */ public interface AuthUser { List<String> getRoles(); void setRoles(List<String> roles); }
基於上述介面實現三種角色 :Buyer,Seller,Admin
User:
基類
/** * 用戶 * Created by kingapex on 2018/3/8. * * @author kingapex * @version 1.0 * @since 6.4.0 * 2018/3/8 */ public class User implements AuthUser { /** * 會員id */ private Integer uid; /** * 唯一標識 */ private String uuid; /** * 用戶名 */ private String username; /** * 角色 */ private List<String> roles; public User() { roles = new ArrayList<>(); } /** * 為用戶定義角色 * * @param roles 角色集合 */ public void add(String... roles) { for (String role : roles) { this.roles.add(role); } } //getter setter 忽略。。。 }
/** * 買家 * Created by kingapex on 2018/3/11. * * @author kingapex * @version 1.0 * @since 7.0.0 * 2018/3/11 */ public class Buyer extends User { /** * 定義買家的角色 */ public Buyer() { this.add(Role.BUYER.name()); } } public class Seller extends Buyer { /** * 賣家id */ private Integer sellerId; /** * 賣家店鋪名稱 */ private String sellerName; /** * 是否是自營 0 不是 1是 */ private Integer selfOperated; public Seller() { //seller有 買家的角色和賣賓角色 add( Role.SELLER.name()); } } /** * 管理員角色 * * @author zh * @version v7.0 * @date 18/6/27 上午10:09 * @since v7.0 */ public class Admin extends User { /** * 是否是超級管理員 */ private Integer founder; /** * 角色 */ private List<String> roles; //getter setter 忽略。。。 }
以上是javashop中許可權體系中基礎的架構和思路以及相關源碼,因為篇幅關係,具體的許可權校驗流程及代碼將在下一篇文章中分享。