demand analysis
Before sharing the source code, first organize and clarify the requirements of the permission module in the b2b2c system to facilitate the understanding of the source code.
Business needs
-
There are three main roles in the b2b2c e-commerce system: buyers, sellers, and platform administrators.
-
Among them, there is a clerk in the seller role, and you can set different permissions for the clerk to manage (for example, the permissions of products and orders are assigned to different clerks). Similarly, the platform administrator also needs to manage the above-mentioned fine permissions, and the buyer permissions are relatively simple.
-
If a clerk or administrator is disabled, the user needs to be logged out immediately to ensure data security
Technical requirements
- decentralization
The javashop e-commerce system adopts a decentralized and containerized deployment scheme. Considering performance and scalability, the authentication needs to use the token method, and the centralized session scheme cannot be used.
- Common Capability Abstraction
There are three terminals (buyer, seller, management terminal) in the b2b2c e-commerce system. For performance and stability reasons, these three terminals are separated in deployment, which is reflected in buyer API, seller API, management terminal API, and permissions. In essence, it intercepts the api requests from these three terminals and performs authentication. The authentication of these three roles has both general logic and personalized logic:
- General: token generation and parsing
- Personalization: different permissions data sources (SecurityMetadataSource)
The specific embodiment is that the source of the binding relationship between roles and permissions is different: the seller's permission setting comes from the seller, and the platform's permission setting comes from the management side.
This requires the reuse of the reuse and the separation of the separation in architecture and code implementation.
Architecture ideas
Token parsing architecture idea:
- The two interfaces correspond to token parsing and token generation respectively
- A jwt implementation class is implemented by default
Security Authentication Domain Model Architecture
- AuthUser is the top-level authenticated user interface
- User-based implementation
- Buyer, Seller, Admin are implemented for specific business
JWT-based authority authentication source code
TokenManager
The business class interface of Token has two core methods: creating and parsing the token. Considering scalability, the interface level does not reflect the dependency of 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
The implementation of the token business class based on 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 creation interface
/**
* 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 parser
/**
* 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
Create implementation based on 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-based token parser
/**
* 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
Authentication User Interface
/**
* 认证用户接口
* @author kingapex
* @version 1.0
* @since 7.1.0
* 2019-06-21
*/
public interface AuthUser {
List<String> getRoles();
void setRoles(List<String> roles);
}
Three roles are implemented based on the above interface: Buyer, Seller, Admin
User:
base class
/**
* 用户
* 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 忽略。。。
}
The above is the basic structure, ideas and related source code of the permission system in javashop. Because of the space, the specific permission verification process and code will be shared in the next article.
Original article by javashop