需求分析
在分享源碼之前,先將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中權限體系中基礎的架構和思路以及相關源碼,因爲篇幅關係,具體的權限校驗流程及代碼將在下一篇文章中分享。