Merge remote-tracking branch 'origin/master'

# Conflicts:
#	system/system-service-impl/src/main/java/cn/iocoder/mall/admin/service/SmsYunPianPlatform.java
This commit is contained in:
sin
2019-05-18 10:35:09 +08:00
127 changed files with 816 additions and 1191 deletions

View File

@@ -12,7 +12,17 @@ import java.util.Set;
@Accessors(chain = true)
public class AdminSecurityContext {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 管理员账号
*/
private String username;
/**
* 拥有的角色编号
*/
private Set<Integer> roleIds;
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.mall.admin.sdk.interceptor;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.admin.api.constant.AdminConstants;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Admin 演示拦截器
*
* 这是个比较“奇怪”的拦截器,用于演示的管理员账号,禁止使用 POST 请求,从而实现即达到阉割版的演示的效果,又避免影响了数据
*/
@Component
public class AdminDemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (AdminConstants.USERNAME_DEMO.equals(AdminSecurityContextHolder.getContext().getUsername())
&& request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_DEMO_CAN_NOT_WRITE.getCode());
}
return true;
}
}

View File

@@ -89,6 +89,7 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
context.setAdminId(authentication.getUserId());
MallUtil.setUserId(request, authentication.getUserId()); // 记录到 request 中,避免 AdminSecurityContext 后续清理掉后,其它地方需要用到 userId
if (authorization != null) {
context.setUsername(authorization.getUsername());
context.setRoleIds(authorization.getRoleIds());
}
}
@@ -113,8 +114,4 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
requiresPermissions != null ? Arrays.asList(requiresPermissions.value()) : null);
}
private void checkPermission() {
}
}

View File

@@ -1,6 +1,6 @@
/**
* 提供 SDK 给其它服务,使用如下功能:
*
* 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.UserSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
* 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
*/
package cn.iocoder.mall.admin.sdk;
package cn.iocoder.mall.admin.sdk;

View File

@@ -17,7 +17,7 @@ import java.util.Map;
public interface AdminService {
/**
* 用户认证。认证成功后,返回认证信息
* 管理员认证。认证成功后,返回认证信息
*
* 实际上,就是用户名 + 密码登陆
*

View File

@@ -4,6 +4,8 @@ import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RefreshTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
/**
* Oauth2 服务接口
@@ -18,7 +20,20 @@ public interface OAuth2Service {
*/
OAuth2AccessTokenBO createToken(OAuth2CreateTokenDTO oauth2CreateTokenDTO);
// TODO @see 刷新 token
/**
* 基于用户移除 accessToken
*
* @param oauth2RemoveTokenDTO accessToken 信息
*/
void removeToken(OAuth2RemoveTokenByUserDTO oauth2RemoveTokenDTO);
/**
* 刷新令牌,获得新的 accessToken 信息
*
* @param oauth2RefreshTokenDTO refreshToken 信息
* @return accessToken 信息
*/
OAuth2AccessTokenBO refreshToken(OAuth2RefreshTokenDTO oauth2RefreshTokenDTO);
/**
* 通过 accessToken 获得身份信息

View File

@@ -5,16 +5,20 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Set;
@ApiModel("管理员授权 BO")
@Data
@Accessors(chain = true)
public class AdminAuthorizationBO {
public class AdminAuthorizationBO implements Serializable {
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
private Integer id;
@ApiModelProperty(value = "登陆账号", required = true, example = "1")
private String username;
@ApiModelProperty(value = "角色编号数组", required = true, example = "1")
private Set<Integer> roleIds;

View File

@@ -5,10 +5,12 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@ApiModel("OAUTH2 认证 BO")
@Data
@Accessors(chain = true)
public class OAuth2AuthenticationBO {
public class OAuth2AuthenticationBO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
private Integer userId;

View File

@@ -1,27 +0,0 @@
package cn.iocoder.mall.admin.api.bo.oauth2;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Set;
/**
* OAUTH2 认证 BO
*/
@Data
@Accessors(chain = true)
public class OAuth2AuthenticationOldBO implements Serializable {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 角色编号数组
*/
private Set<Integer> roleIds;
}

View File

@@ -2,6 +2,14 @@ package cn.iocoder.mall.admin.api.constant;
public class AdminConstants {
/**
* 账号 - 管理员
*/
public static final String USERNAME_ADMIN = "admin";
}
/**
* 账号 - 演示账号
*/
public static final String USERNAME_DEMO = "yudaoyuanma";
}

View File

@@ -17,8 +17,9 @@ public enum AdminErrorCodeEnum {
OAUTH2_INVALID_TOKEN_INVALID(1002001013, "访问令牌已失效"),
OAUTH2_NOT_LOGIN(1002001015, "账号未登陆"),
OAUTH2_INVALID_TOKEN_ERROR_USER_TYPE(1002001016, "访问令牌用户类型不正确"),
OAUTH_INVALID_TOKEN(1002001020, ""), // 预留
OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND(1002001017, "刷新令牌不存在"),
OAUTH_INVALID_REFRESH_TOKEN_EXPIRED(1002001018, "访问令牌已过期"),
OAUTH_INVALID_REFRESH_TOKEN_INVALID(1002001019, "刷新令牌已失效"),
// ========== 管理员模块 1002002000 ==========
ADMIN_USERNAME_NOT_REGISTERED(1002002000, "账号不存在"),
@@ -30,6 +31,8 @@ public enum AdminErrorCodeEnum {
ADMIN_ADMIN_STATUS_CAN_NOT_UPDATE(1002002005, "管理员的账号状态不允许变更"),
ADMIN_ASSIGN_ROLE_NOT_EXISTS(1002002006, "分配员工角色时,有角色不存在"),
ADMIN_INVALID_PERMISSION(1002002007, "没有该操作权限"),
ADMIN_ADMIN_CAN_NOT_UPDATE(1002002008, "管理员的账号不允许变更"),
ADMIN_DEMO_CAN_NOT_WRITE(1002002009, "演示账号暂不允许写操作。欢迎加入我们的交流群http://t.cn/EKEr5WE"),
// ========== 资源模块 1002003000 ==========
RESOURCE_NAME_DUPLICATE(1002003000, "已经存在该名字的资源"),

View File

@@ -8,11 +8,12 @@ import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 创建 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2CreateTokenDTO {
public class OAuth2CreateTokenDTO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")

View File

@@ -9,11 +9,12 @@ import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 身份验证 DTO")
@Data
@Accessors(chain = true)
public class OAuth2GetTokenDTO {
public class OAuth2GetTokenDTO implements Serializable {
@ApiModelProperty(value = "accessToken", required = true, example = "001e8f49b20e47f7b3a2de774497cd50")
@NotEmpty(message = "accessToken 不能为空")

View File

@@ -0,0 +1,28 @@
package cn.iocoder.mall.admin.api.dto.oauth2;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.admin.api.constant.ResourceTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 刷新 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2RefreshTokenDTO implements Serializable {
@ApiModelProperty(value = "refreshToken", required = true, example = "001e8f49b20e47f7b3a2de774497cd50")
@NotEmpty(message = "refreshToken 不能为空")
private String refreshToken;
@ApiModelProperty(value = "用户类型", required = true, example = "1", notes = "参见 ResourceTypeEnum 枚举")
@NotNull(message = "用户类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.mall.admin.api.dto.oauth2;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.admin.api.constant.ResourceTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 移除 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2RemoveTokenByUserDTO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")
private Integer userId;
@ApiModelProperty(value = "用户类型", required = true, example = "1", notes = "参见 ResourceTypeEnum 枚举")
@NotNull(message = "用户类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
}

View File

@@ -2,17 +2,12 @@ package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationOldBO;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.stream.Collectors;
@Mapper
public interface OAuth2Convert {
@@ -28,15 +23,8 @@ public interface OAuth2Convert {
.setExpiresIn(Math.max((int) ((oauth2AccessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis()) / 1000), 0));
}
@Mappings({})
OAuth2AuthenticationOldBO convertToAuthenticationOld(OAuth2AccessTokenDO oauth2AccessTokenDO);
@Mappings({})
OAuth2AuthenticationBO convertToAuthentication(OAuth2AccessTokenDO oauth2AccessTokenDO);
default OAuth2AuthenticationOldBO convertToAuthenticationOld(OAuth2AccessTokenDO oauth2AccessTokenDO, List<AdminRoleDO> adminRoleDOs) {
return convertToAuthenticationOld(oauth2AccessTokenDO)
.setRoleIds(adminRoleDOs.stream().map(AdminRoleDO::getRoleId).collect(Collectors.toSet()));
}
}

View File

@@ -8,9 +8,16 @@ import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO> {
default int updateToInvalidByAdminId(Integer adminId) {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("admin_id", adminId).eq("valid", true);
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}
default int updateToInvalidByRefreshToken(String refreshToken) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("refresh_token", refreshToken).eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}

View File

@@ -8,9 +8,10 @@ import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
default int updateToInvalidByAdminId(Integer adminId) {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2RefreshTokenDO> query = new QueryWrapper<OAuth2RefreshTokenDO>()
.eq("admin_id", adminId).eq("valid", true);
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2RefreshTokenDO().setValid(false), query);
}

View File

@@ -16,6 +16,7 @@ import cn.iocoder.mall.admin.api.constant.AdminConstants;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.dto.admin.*;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
import cn.iocoder.mall.admin.convert.AdminConvert;
import cn.iocoder.mall.admin.dao.AdminMapper;
import cn.iocoder.mall.admin.dao.AdminRoleMapper;
@@ -96,9 +97,14 @@ public class AdminServiceImpl implements AdminService {
@Override
public Boolean updateAdmin(Integer adminId, AdminUpdateDTO adminUpdateDTO) {
// 校验账号存在
if (adminMapper.selectById(adminUpdateDTO.getId()) == null) {
AdminDO admin = adminMapper.selectById(adminUpdateDTO.getId());
if (admin == null) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())
|| AdminConstants.USERNAME_DEMO.equals(admin.getUsername())) { // 特殊账号,不允许编辑
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_ADMIN_CAN_NOT_UPDATE.getCode());
}
// 校验账号唯一
AdminDO usernameAdmin = adminMapper.selectByUsername(adminUpdateDTO.getUsername());
if (usernameAdmin != null && !usernameAdmin.getId().equals(adminUpdateDTO.getId())) {
@@ -120,7 +126,8 @@ public class AdminServiceImpl implements AdminService {
if (admin == null) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())) {
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())
|| AdminConstants.USERNAME_DEMO.equals(admin.getUsername())) { // 特殊账号,不允许编辑
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_ADMIN_STATUS_CAN_NOT_UPDATE.getCode());
}
// 如果状态相同,则返回错误
@@ -132,7 +139,7 @@ public class AdminServiceImpl implements AdminService {
adminMapper.updateById(updateAdmin);
// 如果是关闭管理员,则标记 token 失效。否则,管理员还可以继续蹦跶
if (CommonStatusEnum.DISABLE.getValue().equals(adminUpdateStatusDTO.getStatus())) {
oauth2Service.removeToken(adminUpdateStatusDTO.getId());
oauth2Service.removeToken(new OAuth2RemoveTokenByUserDTO().setUserId(adminId).setUserType(UserTypeEnum.ADMIN.getValue()));
}
// TODO 插入操作日志
// 返回成功
@@ -234,8 +241,11 @@ public class AdminServiceImpl implements AdminService {
}
}
}
// 获得用户
AdminDO admin = adminMapper.selectById(adminId);
// 返回成功
return new AdminAuthorizationBO().setId(adminId).setRoleIds(adminRoleIds);
return new AdminAuthorizationBO().setId(adminId).setUsername(admin.getUsername())
.setRoleIds(adminRoleIds);
}
private String encodePassword(String password) {

View File

@@ -7,6 +7,8 @@ import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RefreshTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
import cn.iocoder.mall.admin.convert.OAuth2Convert;
import cn.iocoder.mall.admin.dao.OAuth2AccessTokenMapper;
import cn.iocoder.mall.admin.dao.OAuth2RefreshTokenMapper;
@@ -59,17 +61,37 @@ public class OAuth2ServiceImpl implements OAuth2Service {
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
/**
* 移除管理员对应的 Token
*
* @param adminId 管理员编号
*/
@Override
@Transactional
public void removeToken(Integer adminId) {
public void removeToken(OAuth2RemoveTokenByUserDTO oauth2RemoveTokenByUserDTO) {
Integer userId = oauth2RemoveTokenByUserDTO.getUserId();
Integer userType = oauth2RemoveTokenByUserDTO.getUserType();
// 设置 access token 失效
oauth2AccessTokenMapper.updateToInvalidByAdminId(adminId);
oauth2AccessTokenMapper.updateToInvalid(userId, userType);
// 设置 refresh token 失效
oauth2RefreshTokenMapper.updateToInvalidByAdminId(adminId);
oauth2RefreshTokenMapper.updateToInvalid(userId, userType);
}
@Override
public OAuth2AccessTokenBO refreshToken(OAuth2RefreshTokenDTO oauth2RefreshTokenDTO) {
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(oauth2RefreshTokenDTO.getRefreshToken());
// 校验刷新令牌是否合法
if (refreshTokenDO == null) { // 不存在
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND.getCode());
}
if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_EXPIRED.getCode());
}
if (!refreshTokenDO.getValid()) { // 无效
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_INVALID.getCode());
}
// 标记 refreshToken 对应的 accessToken 都不合法
oauth2AccessTokenMapper.updateToInvalidByRefreshToken(oauth2RefreshTokenDTO.getRefreshToken());
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO.getUserId(), refreshTokenDO.getUserType(),
refreshTokenDO.getId());
// 转换返回
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
@Override

View File

@@ -1,5 +0,0 @@
/**
* @author Sin
* @time 2019/5/16 10:52 AM
*/
package cn.iocoder.mall.admin;