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