笔者负责的电商项目的技术体系是基于SpringBoot,为了实现一套后端能够承载ToB和ToC的业务,需要完善现有的权限管理体系。
在查看Shiro和Spring Security对比后,笔者认为Spring Security更加适合本项目使用,可以总结为以下2点:
1、基于拦截器的权限校验逻辑,可以针对ToB的业务接口来做相关的权限校验,以笔者的项目为例,ToB的接口请求路径以/openshop/api/开头,可以根据接口请求路径配置全局的ToB的拦截器;
2、Spring Security的权限管理模型更简单直观,对权限、角色和用户做了很好的解耦。
以下介绍本项目的实现步骤
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
public abstract class AbstractAuthenticationInterceptor extends HandlerInterceptorAdapter implements InitializingBean {
@Resource
private AccessDecisionManager accessDecisionManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//检查是否登录
String userId = null;
try {
userId = getUserId();
}catch (Exception e){
JsonUtil.renderJson(response,403,"{}");
return false;
}
if(StringUtils.isEmpty(userId)){
JsonUtil.renderJson(response,403,"{}");
return false;
}
//检查权限
Collection<? extends GrantedAuthority> authorities = getAttributes(userId);
Collection<ConfigAttribute> configAttributes = getAttributes(request);
return accessDecisionManager.decide(authorities,configAttributes);
}
//获取用户id
public abstract String getUserId();
//根据用户id获取用户的角色集合
public abstract Collection<? extends GrantedAuthority> getAttributes(String userId);
//查询请求需要的权限
public abstract Collection<ConfigAttribute> getAttributes(HttpServletRequest request);
}
@Component
public class AuthenticationInterceptor extends AbstractAuthenticationInterceptor {
@Resource
private SessionManager sessionManager;
@Resource
private UserPermissionService customUserService;
@Override
public String getUserId() {
return sessionManager.obtainUserId();
}
@Override
public Collection<? extends GrantedAuthority> getAttributes(String s) {
return customUserService.getAuthoritiesById(s);
}
@Override
public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) {
return customUserService.getAttributes(request);
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
集成redis维护用户session信息
@Component
public class SessionManager {
private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
@Autowired
private RedisUtils redisUtils;
public SessionManager() {
}
public UserInfoDTO obtainUserInfo() {
UserInfoDTO userInfoDTO = null;
try {
String token = this.obtainToken();
logger.info("=======token=========", token);
if (StringUtils.isEmpty(token)) {
LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());
}
userInfoDTO = (UserInfoDTO)this.redisUtils.obtain(this.obtainToken(), UserInfoDTO.class);
} catch (Exception var3) {
logger.error("obtainUserInfo ex:", var3);
}
if (null == userInfoDTO) {
LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());
}
return userInfoDTO;
}
public String obtainUserId() {
return this.obtainUserInfo().getUserId();
}
public String obtainToken() {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
return token;
}
public UserInfoDTO createSession(UserInfoDTO userInfoDTO, long expired) {
String token = UUIDUtil.obtainUUID("token.");
userInfoDTO.setToken(token);
if (expired == 0L) {
this.redisUtils.put(token, userInfoDTO);
} else {
this.redisUtils.put(token, userInfoDTO, expired);
}
return userInfoDTO;
}
public void destroySession() {
String token = this.obtainToken();
if (StringUtils.isNotBlank(token)) {
this.redisUtils.remove(token);
}
}
}
@Service
public class UserPermissionService {
@Resource
private SysUserDao userDao;
@Resource
private SysPermissionDao permissionDao;
private HashMap<String, Collection<ConfigAttribute>> map =null;
/**
* 加载资源,初始化资源变量
*/
public void loadResourceDefine(){
map = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<SysPermission> permissions = permissionDao.findAll();
for(SysPermission permission : permissions) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getName());
array.add(cfg);
map.put(permission.getUrl(), array);
}
}
/*
*
* @Author zhangs
* @Description 获取用户权限列表
* @Date 18:56 2019/11/11
**/
public List<GrantedAuthority> getAuthoritiesById(String userId) {
SysUserRspDTO user = userDao.findById(userId);
if (user != null) {
List<SysPermission> permissions = permissionDao.findByAdminUserId(user.getUserId());
List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
for (SysPermission permission : permissions) {
if (permission != null && permission.getName()!=null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
grantedAuthorities.add(grantedAuthority);
}
}
return grantedAuthorities;
}
return null;
}
/*
*
* @Author zhangs
* @Description 获取当前请求所需权限
* @Date 18:57 2019/11/11
**/
public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) throws IllegalArgumentException {
if(map !=null) map.clear();
loadResourceDefine();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
}
通过查看authorities中的权限列表是否含有configAttributes中所需的权限,判断用户是否具有请求当前资源或者执行当前操作的权限。
@Service
public class AccessDecisionManager {
public boolean decide(Collection<? extends GrantedAuthority> authorities, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return true;
}
ConfigAttribute c;
String needRole;
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authorities) {
if(needRole.trim().equals(ga.getAuthority())) {
return true;
}
}
}
return false;
}
}
@Configuration
public class WebAppConfigurer extends WebMvcConfigurerAdapter {
@Resource
private AbstractAuthenticationInterceptor authenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
//对来自/openshop/api/** 这个链接来的请求进行拦截
registry.addInterceptor(authenticationInterceptor).addPathPatterns("/openshop/api/**");
super.addInterceptors(registry);
}
}
用户表 sys_user
CREATE TABLE `sys_user` (
`user_id` varchar(64) NOT NULL COMMENT '用户ID',
`username` varchar(255) DEFAULT NULL COMMENT '登录账号',
`first_login` datetime(6) NOT NULL COMMENT '首次登录时间',
`last_login` datetime(6) NOT NULL COMMENT '上次登录时间',
`pay_pwd` varchar(100) DEFAULT NULL COMMENT '支付密码',
`chant_id` varchar(64) NOT NULL DEFAULT '-1' COMMENT '关联商户id',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
角色表 sys_role
CREATE TABLE `sys_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
用户角色关联表 sys_role_user
CREATE TABLE `sys_role_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sys_user_id` varchar(64) DEFAULT NULL,
`sys_role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
权限表 sys_premission
CREATE TABLE `sys_permission` (
`permission_id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL COMMENT '权限名称',
`description` varchar(255) DEFAULT NULL COMMENT '权限描述',
`url` varchar(255) DEFAULT NULL COMMENT '资源url',
`check_pwd` int(2) NOT NULL DEFAULT '1' COMMENT '是否检查支付密码:0不需要 1 需要',
`check_sms` int(2) NOT NULL DEFAULT '1' COMMENT '是否校验短信验证码:0不需要 1 需要',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
角色权限关联表 sys_permission_role
CREATE TABLE `sys_permission_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL,
`permission_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4