需求分析
在分享源码之前,先将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中权限体系中基础的架构和思路以及相关源码,因为篇幅关系,具体的权限校验流程及代码将在下一篇文章中分享。
易族智汇(javashop)原创文章