1. 修改在线会话的实现

2. 接入到会员管理 OAuth2.0
This commit is contained in:
YunaiV
2022-05-10 23:20:15 +08:00
parent 6ed624861d
commit 5cf68961e1
29 changed files with 368 additions and 162 deletions

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
import org.springframework.stereotype.Service;
@ -22,7 +23,9 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
@Override
public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
return null;
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId());
return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
}
@Override
@ -30,4 +33,16 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
return OAuth2TokenConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken));
}
@Override
public OAuth2AccessTokenRespDTO removeAccessToken(String accessToken) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken);
return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
}
@Override
public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
}
}

View File

@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
@ -70,14 +71,15 @@ public class AuthController {
public CommonResult<Boolean> logout(HttpServletRequest request) {
String token = obtainAuthorization(request, securityProperties.getTokenHeader());
if (StrUtil.isNotBlank(token)) {
authService.logout(token);
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
}
return success(true);
}
@PostMapping("/refresh-token")
@ApiOperation("刷新令牌")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 TODO 接口文档
@ApiImplicitParam(name = "refreshToken", value = "刷新令牌", required = true, dataTypeClass = String.class)
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
return success(authService.refreshToken(refreshToken));
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.system.controller.admin.auth;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - OAuth2.0 令牌")
@RestController
@RequestMapping("/system/oauth2-token")
public class OAuth2TokenController {
@Resource
private OAuth2TokenService oauth2TokenService;
@Resource
private AdminAuthService authService;
@GetMapping("/page")
@ApiOperation(value = "获得访问令牌分页", notes = "只返回有效期内的")
@PreAuthorize("@ss.hasPermission('system:oauth2-token:page')")
public CommonResult<PageResult<OAuth2AccessTokenRespVO>> getAccessTokenPage(@Valid OAuth2AccessTokenPageReqVO reqVO) {
PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);
return success(OAuth2TokenConvert.INSTANCE.convert(pageResult));
}
@DeleteMapping("/delete")
@ApiOperation("删除访问令牌")
@ApiImplicitParam(name = "accessToken", value = "访问令牌", required = true, dataTypeClass = String.class, example = "tudou")
@PreAuthorize("@ss.hasPermission('system:oauth2-token:delete')")
public CommonResult<Boolean> deleteAccessToken(@RequestParam("accessToken") String accessToken) {
authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType());
return success(true);
}
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ApiModel("管理后台 - 访问令牌分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class OAuth2AccessTokenPageReqVO extends PageParam {
@ApiModelProperty(value = "用户编号", required = true, example = "666")
private Long userId;
@ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
private Integer userType;
@ApiModelProperty(value = "客户端编号", required = true, example = "2")
private Long clientId;
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@ApiModel("管理后台 - 访问令牌 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2AccessTokenRespVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "访问令牌", required = true, example = "tudou")
private String accessToken;
@ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
private String refreshToken;
@ApiModelProperty(value = "用户编号", required = true, example = "666")
private Long userId;
@ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举")
private Integer userType;
@ApiModelProperty(value = "客户端编号", required = true, example = "2")
private Long clientId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ApiModelProperty(value = "过期时间", required = true)
private Date expiresTime;
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.system.convert.auth;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -12,4 +15,8 @@ public interface OAuth2TokenConvert {
OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean);
PageResult<OAuth2AccessTokenRespVO> convert(PageResult<OAuth2AccessTokenDO> page);
OAuth2AccessTokenRespDTO convert2(OAuth2AccessTokenDO bean);
}

View File

@ -1,9 +1,13 @@
package cn.iocoder.yudao.module.system.dal.mysql.auth;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List;
@Mapper
@ -17,4 +21,13 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
}
default PageResult<OAuth2AccessTokenDO> selectPage(OAuth2AccessTokenPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
.eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())
.eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType())
.eqIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId())
.gt(OAuth2AccessTokenDO::getExpiresTime, new Date())
.orderByDesc(OAuth2AccessTokenDO::getId));
}
}

View File

@ -25,8 +25,9 @@ public interface AdminAuthService {
* 基于 token 退出登录
*
* @param token token
* @param logType 登出类型
*/
void logout(String token);
void logout(String token, Integer logType);
/**
* 短信验证码发送

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.service.common.CaptchaService;
import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting;
@ -51,6 +52,8 @@ public class AdminAuthServiceImpl implements AdminAuthService {
private OAuth2TokenService oauth2TokenService;
@Resource
private SocialUserService socialUserService;
@Resource
private MemberService memberService;
@Resource
private Validator validator;
@ -209,23 +212,27 @@ public class AdminAuthServiceImpl implements AdminAuthService {
}
@Override
public void logout(String token) {
public void logout(String token, Integer logType) {
// 删除访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
if (accessTokenDO == null) {
return;
}
// 删除成功,则记录登出日志
createLogoutLog(accessTokenDO.getUserId());
createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
}
private void createLogoutLog(Long userId) {
private void createLogoutLog(Long userId, Integer userType, Integer logType) {
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setLogType(logType);
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUsername(getUsername(userId));
reqDTO.setUserType(getUserType().getValue());
reqDTO.setUserType(userType);
if (ObjectUtil.notEqual(getUserType(), userType)) {
reqDTO.setUsername(getUsername(userId));
} else {
reqDTO.setUsername(memberService.getMemberUserMobile(userId));
}
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(ServletUtils.getClientIP());
reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.system.service.auth;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
/**
@ -64,4 +66,12 @@ public interface OAuth2TokenService {
*/
OAuth2AccessTokenDO removeAccessToken(String accessToken);
/**
* 获得访问令牌分页
*
* @param reqVO 请求
* @return 访问令牌分页
*/
PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO);
}

View File

@ -4,8 +4,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
@ -125,6 +127,11 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return accessTokenDO;
}
@Override
public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {
return oauth2AccessTokenMapper.selectPage(reqVO);
}
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())

View File

@ -221,7 +221,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO);
// 调用
authService.logout(token);
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
// 校验调用参数
verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
&& o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
@ -234,7 +234,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
String token = randomString();
// 调用
authService.logout(token);
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
// 校验调用参数
verify(loginLogService, never()).createLoginLog(any());
}