!42 接入第三方登录(已接入Gitee[理论justAuth支持都可以接入]

Merge pull request !42 from weir/oauth2
This commit is contained in:
芋道源码 2021-09-28 01:29:18 +00:00 committed by Gitee
commit b39086b062
29 changed files with 1081 additions and 46 deletions

View File

@ -2426,3 +2426,21 @@ INSERT INTO `tool_test_demo` VALUES (107, '哈哈哈哈', 1, 0, 1, 'biubiubui',
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
-- ----------------------------
-- Table structure for sys_user_social
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_social`;
CREATE TABLE `sys_user_social` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键(自增策略)',
`user_id` bigint NOT NULL COMMENT '系统用户ID',
`social_user_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三用户ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.adminserver.modules.system.controller.auth;
import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
@ -19,6 +20,8 @@ import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -46,6 +49,8 @@ public class SysAuthController {
private SysRoleService roleService;
@Resource
private SysPermissionService permissionService;
@Resource
private SysUserSessionService sysUserSessionService;
@PostMapping("/login")
@ApiOperation("使用账号密码登录")
@ -56,6 +61,17 @@ public class SysAuthController {
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@RequestMapping("/auth2/login/{oauthType}")
@ApiOperation("第三方登录")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<SysAuthLoginRespVO> login(@PathVariable String oauthType) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//TODO NPE
String token = sysUserSessionService.getSessionId(authentication.getName());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@GetMapping("/get-permission-info")
@ApiOperation("获取登陆用户的权限信息")
public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() {

View File

@ -0,0 +1,7 @@
package cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user;
/**
* @author weir
*/
public class AuthUser {
}

View File

@ -1,5 +1,8 @@
package cn.iocoder.yudao.adminserver.modules.system.convert.auth;
import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.SysUserCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.enums.common.SysSexEnum;
import cn.iocoder.yudao.framework.security.core.Auth2LoginUser;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthMenuRespVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthPermissionInfoRespVO;
@ -10,8 +13,9 @@ import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.Sys
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.yudao.adminserver.modules.system.enums.permission.MenuIdEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.model.AuthUser;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import org.slf4j.LoggerFactory;
@ -21,7 +25,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Mapper
@Mapper(uses = SysAuthConvert.UserSexTransform.class)
public interface SysAuthConvert {
SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class);
@ -29,6 +33,7 @@ public interface SysAuthConvert {
@Mapping(source = "updateTime", target = "updateTime", ignore = true)
// 字段相同但是含义不同忽略
LoginUser convert(SysUserDO bean);
Auth2LoginUser getAuth2LoginUser(SysUserDO bean);
default SysAuthPermissionInfoRespVO convert(SysUserDO user, List<SysRoleDO> roleList, List<SysMenuDO> menuList) {
return SysAuthPermissionInfoRespVO.builder()
@ -44,6 +49,16 @@ public interface SysAuthConvert {
LoginUser convert(SysUserProfileUpdatePasswordReqVO reqVO);
@Mappings(
@Mapping(target = "sex", source = "gender")
)
SysUserCreateReqVO convert(AuthUser authUser);
@Mappings(
@Mapping(target = "thirdPartyUserId", source = "uuid")
)
Auth2LoginUser getLoginUser(AuthUser authUser);
/**
* 将菜单列表构建成菜单树
*
@ -76,4 +91,24 @@ public interface SysAuthConvert {
return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
}
public class UserSexTransform {
public int toInt (AuthUserGender gender){
switch (gender) {
case MALE:
return SysSexEnum.MALE.getSex();
case FEMALE:
return SysSexEnum.FEMALE.getSex();
default:
return SysSexEnum.UNKNOWN.getSex();
}
}
public AuthUserGender strToBoolean(int sex){
if(sex == SysSexEnum.UNKNOWN.getSex()) {
return AuthUserGender.UNKNOWN;
}
return AuthUserGender.getRealGender(String.valueOf(sex));
}
}
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.adminserver.modules.system.convert.auth.config;
import cn.iocoder.yudao.adminserver.modules.system.convert.auth.handler.DefaultSignUpUrlAuthenticationSuccessHandler;
import cn.iocoder.yudao.framework.security.core.handler.AbstractSignUpUrlAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author weir
*/
@Configuration
public class AuthConfig {
@Bean
public AbstractSignUpUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
AbstractSignUpUrlAuthenticationSuccessHandler successHandler = new DefaultSignUpUrlAuthenticationSuccessHandler();
successHandler.setDefaultTargetUrl("/api/callback");
return successHandler;
}
}

View File

@ -0,0 +1,112 @@
/*
* MIT License
* Copyright (c) 2020-2029 YongWu zheng (dcenter.top and gitee.com/pcore and github.com/ZeroOrInfinity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package cn.iocoder.yudao.adminserver.modules.system.convert.auth.handler;
import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthLoginRespVO;
import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.security.core.Auth2LoginUser;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.handler.AbstractSignUpUrlAuthenticationSuccessHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static java.util.Collections.singleton;
import static top.dcenter.ums.security.core.oauth.util.MvcUtil.*;
/**
* @author weir
*/
public class DefaultSignUpUrlAuthenticationSuccessHandler extends AbstractSignUpUrlAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Autowired
private SysUserSessionService userSessionService;
@Resource
private SysPermissionService permissionService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
final Object principal = authentication.getPrincipal();
String token = userSessionService.createUserSession(defaultHandleUserRoles((LoginUser) principal), getClientIP(), getUserAgent());
if(StringUtils.isNotBlank(token)) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
if (principal instanceof Auth2LoginUser) {
new DefaultRedirectStrategy().sendRedirect(request, response, getUrl() + token);
return;
}
if (isAjaxOrJson(request)) {
responseWithJson(response, HttpStatus.OK.value(), toJsonString(success(SysAuthLoginRespVO.builder().token(token).build())));
return;
}
try {
requestCache.saveRequest(request, response);
super.setRequestCache(requestCache);
super.onAuthenticationSuccess(request, response, authentication);
} catch (ServletException e) {
e.printStackTrace();
}
}
private String getUrl() {
return "http://localhost/oauthLogin/gitee?token=";
}
/**
* 默认处理方式处理用户角色列表建议角色权限前置到 UserDetails
*
* @param loginUser 用户
* @return
*/
private LoginUser defaultHandleUserRoles(LoginUser loginUser) {
Set<Long> roleIds = loginUser.getRoleIds();
if (roleIds == null || roleIds.isEmpty()) {
Set<Long> userRoleIds = permissionService.getUserRoleIds(loginUser.getId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
loginUser.setRoleIds(userRoleIds);
}
return loginUser;
}
@Override
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}

View File

@ -0,0 +1,13 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import me.zhyd.oauth.model.AuthUser;
/**
* @author weir
*/
@Data
@TableName("user_connection")
public class SocialUserDO extends AuthUser {
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 系统用户和第三方用户关联表
* @author weir
*/
@TableName("sys_user_social")
@Data
public class SysUserSocialDO extends BaseDO {
/**
* 自增主键
*/
@TableId
private Long id;
/**
* 用户 ID
*/
private Long userId;
/**
* 角色 ID
*/
private String socialUserId;
}

View File

@ -0,0 +1,11 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysUserSocialDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysSysUserSocialMapper extends BaseMapperX<SysUserSocialDO> {
}

View File

@ -0,0 +1,11 @@
package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysUserSocialDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserSocialMapper extends BaseMapperX<SysUserSocialDO> {
}

View File

@ -11,10 +11,12 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum SysSexEnum {
MALE(1), //
FEMALE(2); //
/** 男 */
MALE(1),
/** 女 */
FEMALE(2),
/* 未知 */
UNKNOWN(3);
/**
* 性别
*/

View File

@ -45,6 +45,13 @@ public interface SysUserSessionService {
*/
LoginUser getLoginUser(String sessionId);
/**
* 获取当前登录用户信息
* @param username 用户名称
* @return 在线用户
*/
String getSessionId(String username);
/**
* 获得 Session 超时时间单位毫秒
*

View File

@ -18,7 +18,11 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.logger.SysLoginResultEn
import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.yudao.adminserver.modules.system.service.logger.SysLoginLogService;
import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -34,6 +38,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
*
* @author 芋道源码
*/
@Slf4j
@Service
public class SysUserSessionServiceImpl implements SysUserSessionService {
@ -91,6 +96,15 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
return loginUserRedisDAO.get(sessionId);
}
@Override
public String getSessionId(String username) {
QueryWrapper<SysUserSessionDO> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
wrapper.orderByDesc("create_time");
SysUserSessionDO sysUserSessionDO = userSessionMapper.selectOne(wrapper);
return sysUserSessionDO.getId();
}
@Override
public Long getSessionTimeoutMillis() {
return securityProperties.getSessionTimeout().toMillis();

View File

@ -0,0 +1,237 @@
/*
* MIT License
* Copyright (c) 2020-2029 YongWu zheng (dcenter.top and gitee.com/pcore and github.com/ZeroOrInfinity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package cn.iocoder.yudao.adminserver.modules.system.service.auth.impl;
import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.SysUserCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.convert.auth.SysAuthConvert;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
import cn.iocoder.yudao.framework.security.core.Auth2LoginUser;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import com.google.common.collect.Sets;
import me.zhyd.oauth.model.AuthUser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import top.dcenter.ums.security.core.oauth.enums.ErrorCodeEnum;
import top.dcenter.ums.security.core.oauth.exception.RegisterUserFailureException;
import top.dcenter.ums.security.core.oauth.exception.UserNotExistException;
import top.dcenter.ums.security.core.oauth.service.UmsUserDetailsService;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 用户密码与手机短信登录与注册服务<br><br>
* 1. 用于第三方登录与手机短信登录逻辑<br><br>
* 2. 用于用户密码登录逻辑<br><br>
* 3. 用户注册逻辑<br><br>
* @author YongWu zheng
* @version V1.0 Created by 2020/9/20 11:06
*/
@Service
public class UserDetailsServiceImpl implements UmsUserDetailsService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired(required = false)
private UserCache userCache;
@Resource
private SysUserService userService;
/**
* 用于密码加解密
*/
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SysPermissionService permissionService;
@SuppressWarnings("AlibabaUndefineMagicConstant")
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try
{
// 从缓存中查询用户信息:
// 从缓存中查询用户信息
if (this.userCache != null)
{
UserDetails userDetails = this.userCache.getUserFromCache(username);
if (userDetails != null)
{
return userDetails;
}
}
// 根据用户名获取用户信息
// 获取 username 对应的 SysUserDO
SysUserDO user = userService.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
// 创建 LoginUser 对象
Auth2LoginUser loginUser = SysAuthConvert.INSTANCE.getAuth2LoginUser(user);
//TODO 登录日志等可以和用户名密码等兼容处理
return loginUser;
}
catch (Exception e)
{
String msg = String.format("第三方登录 ======>: 登录用户名:%s, 登录失败: %s", username, e.getMessage());
log.error(msg);
throw new UserNotExistException(ErrorCodeEnum.QUERY_USER_INFO_ERROR, e, username);
}
}
@Override
public UserDetails registerUser(@NonNull AuthUser authUser, @NonNull String username,
@NonNull String defaultAuthority, String decodeState) throws RegisterUserFailureException {
// 这里的 decodeState 可以根据自己实现的 top.dcenter.ums.security.core.oauth.service.Auth2StateCoder 接口的逻辑来传递必要的参数.
// 比如: 第三方登录成功后的跳转地址
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 假设 decodeState 就是 redirectUrl, 我们直接把 redirectUrl 设置到 request
// 后续经过成功处理器时直接从 requestAttributes.getAttribute("redirectUrl", RequestAttributes.SCOPE_REQUEST) 获取并跳转
if (requestAttributes != null) {
requestAttributes.setAttribute("redirectUrl", decodeState, RequestAttributes.SCOPE_REQUEST);
}
//返回用户
LoginUser loginUser = doRegistUser(authUser);
log.info("第三方用户注册 ======>: 用户名:{}, 注册成功", username);
// 把用户信息存入缓存
if (userCache != null)
{
userCache.putUserInCache(loginUser);
}
return loginUser;
}
private LoginUser doRegistUser(AuthUser authUser) {
SysUserCreateReqVO reqVO = SysAuthConvert.INSTANCE.convert(authUser);
if (StringUtils.isEmpty(reqVO.getPassword())) {
reqVO.setPassword(getDefaultPassword());
}
//添加用户
Long sysUserId = userService.createUser(reqVO);
//关联第三方用户
Long userId = userService.bindSocialUSer(sysUserId, authUser.getUuid());
//赋予默认角色权限三方登录默认部分
permissionService.assignUserRole(userId, getDefaultRoles());
LoginUser loginUser = SysAuthConvert.INSTANCE.getLoginUser(authUser);
loginUser.setRoleIds(getDefaultRoles());
loginUser.setPassword(getDefaultPassword());
loginUser.setId(sysUserId);
return loginUser;
}
private String getDefaultPassword() {
return "123456";
}
protected Set<Long> getDefaultRoles() {
return Sets.newHashSet(1L);
}
@NonNull
public UserDetails registerUser(@NonNull String mobile, Map<String, String> otherParamMap) throws RegisterUserFailureException {
// 用户信息持久化逻辑
// ...
log.info("Demo ======>: 手机短信登录用户 {}:注册成功", mobile);
User user = new User(mobile,
passwordEncoder.encode("admin"),
true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_USER")
);
// 把用户信息存入缓存
if (userCache != null)
{
userCache.putUserInCache(user);
}
return user;
}
/**
* {@link #existedByUsernames(String...)} usernames 生成规则.
* 如需自定义重新实现此逻辑
* @param authUser 第三方用户信息
* @return 返回一个 username 数组
*/
@Override
public String[] generateUsernames(AuthUser authUser) {
return new String[]{
authUser.getUsername(),
// providerId = authUser.getSource()
authUser.getUsername() + "_" + authUser.getSource(),
// providerUserId = authUser.getUuid()
authUser.getUsername() + "_" + authUser.getSource() + "_" + authUser.getUuid()
};
}
@Override
public UserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
UserDetails userDetails = loadUserByUsername(userId);
User.withUserDetails(userDetails);
return userDetails;
}
@Override
public List<Boolean> existedByUsernames(String... usernames) throws UsernameNotFoundException {
// ... 在本地账户上查询 userIds 是否已被使用
List<Boolean> list = new ArrayList<>();
for (String username : usernames) {
SysUserDO userDO = userService.getUserByUsername(username);
list.add(userDO != null);
}
return list;
}
}

View File

@ -34,6 +34,14 @@ public interface SysUserService {
*/
Long createUser(SysUserCreateReqVO reqVO);
/**
* 绑定第三方用户
* @param sysUserId 系统用户ID
* @param socialUSerId 第三方唯一标识
* @return
*/
Long bindSocialUSer(Long sysUserId, String socialUSerId);
/**
* 修改用户
*

View File

@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysUserSocialDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysUserSocialMapper;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -49,6 +51,8 @@ public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserMapper userMapper;
@Resource
private SysUserSocialMapper userSocialMapper;
@Resource
private SysDeptService deptService;
@ -74,6 +78,15 @@ public class SysUserServiceImpl implements SysUserService {
return user.getId();
}
@Override
public Long bindSocialUSer(Long sysUserId, String socialUSerId) {
SysUserSocialDO userSocialDO = new SysUserSocialDO();
userSocialDO.setUserId(sysUserId);
userSocialDO.setSocialUserId(socialUSerId);
userSocialMapper.insert(userSocialDO);
return userSocialDO.getUserId();
}
@Override
public void updateUser(SysUserUpdateReqVO reqVO) {
// 校验正确性

View File

@ -167,3 +167,221 @@ yudao:
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
demo: false # 关闭演示模式
# ums core
ums:
one-click-login:
# 一键登录是否开启, 默认 false
enable: true
# 一键登录请求处理 url, 默认 /authentication/one-click
login-processing-url: /authentication/one-click
# token 参数名称, 默认: accessToken
token-param-name: accessToken
# 其他请求参数名称列表(包括请求头名称), 此参数会传递到 OneClickLoginService.callback(String, Map)
# 与 UserDetailsRegisterService.registerUser(String, Map); 默认为: 空
other-param-names:
- imei
# ================ 第三方授权登录相关配置 ================
oauth:
# 第三方授权登录后如未注册用户是否支持自动注册功能, 默认: true
auto-sign-up: true
# 第三方授权登录后如未注册用户不支持自动注册功能, 则跳转到此 url 进行注册逻辑, 此 url 必须开发者自己实现; 默认: /signUp.html;
# 注意: 当 autoSignUp = false 时, 此属性才生效.
# 例如: 1. 设置值 "/signUp", 则跳转指定到 "/signUp" 进行注册.
# 2. 想返回自定义 json 数据到前端, 这里要设置 null , 在 Auth2LoginAuthenticationFilter 设置的 AuthenticationSuccessHandler
# 上处理返回 json; 判断是否为临时用户的条件是: Authentication.getPrincipal() 是否为 TemporaryUser 类型.
sign-up-url: null
# 用于第三方授权登录时, 未开启自动注册且用户是第一次授权登录的临时用户密码, 默认为: "". 注意: 生产环境更换密码
temporary-user-password: ""
# 用于第三方授权登录时, 未开启自动注册且用户是第一次授权登录的临时用户的默认权限, 多个权限用逗号分开, 默认为: "ROLE_TEMPORARY_USER"
temporary-user-authorities: ROLE_TEMPORARY_USER
# 抑制反射警告, 支持 JDK11, 默认: false , 在确认 WARNING: An illegal reflective access operation has occurred 安全后, 可以打开此设置, 可以抑制反射警告.
suppress-reflect-warning: true
# 第三方服务商: providerId, 支持所有 JustAuth 支持的第三方授权登录, 目前有 32 家第三方授权登录
github:
# 根据是否有设置 clientId 来动态加载相应 JustAuth 的 AuthXxxRequest
client-id: 4d4ee00e82f669f2ea8d
client-secret: 4050be113a83556b63bd991d606fded437b05235
scopes:
# - 'repo:status'
# - 'public_repo'
# - 'repo:invite'
# - 'user:follow'
gitee:
client-id: eb5e3298cc10ead57cd40f9f36e7154ab2ea54dcb519684d7e10ca3fec5b1c1a
client-secret: 495b5542dc007e2d46d316c527bd05dac586f2ec31b96c551e164387b5edb1cb
scopes:
- user_info
# # 自定义 OAuth2 Login
# customize:
# client-id: c971cf1634460e18310a5d7cb0f55d7d143a72015b2f29aee6a0e8911efac7eb
# client-secret: 309c9521721e3eb385a99a6bde2755f3107c7e15f3b8e0527c9f3ea4d1ce33bb
# # 自定义第三方授权登录时, 当 Auth2Properties#customize 时有效, 此字段必须以驼峰方式命名.
# # 比如此字段的值为 umsCustomize, 那么 /auth2/authorization/customize 会替换为 /auth2/authorization/umsCustomize
# customize-provider-id: giteeCustomize
# # 自定义第三方授权登录, 当 Auth2Properties#customize 时有效, 设置第三方是否在国外, 默认: false.
# # 如果为 false 时, 设置 {@link HttpConfig} 的超时时间为 ums.oauth.proxy.timeout 的值.
# # 如果为 true 时, 设置 {@link HttpConfig} 的超时时间为 ums.oauth.proxy.foreignTimeout 的值.
# customize-is-foreign: false
# 第三方登录授权登录 url 前缀, 不包含 ServletContextPath默认为 /auth2/authorization.
auth-login-url-prefix: /api/auth2/authorization
# 第三方登录回调处理 url 前缀 ,也就是 RedirectUrl 的前缀, 不包含 ServletContextPath默认为 /auth2/login.
redirect-url-prefix: /api/auth2/login
# 第三方登录回调的域名, 例如http://localhost:9090 默认为 "http://127.0.0.1"
# redirectUrl 直接由 {domain}/{servletContextPath}/{redirectUrlPrefix}/{providerId}(ums.oauth.[qq/gitee/weibo])组成
domain: http://localhost:48080
# 第三方授权登录成功后的默认权限, 多个权限用逗号分开, 默认为: "ROLE_USER"
default-authorities: ROLE_USER
# 是否支持内置的第三方登录用户表(user_connection) 和 auth_token 表. 默认: true.
# 注意: 如果为 false, 则必须重新实现 ConnectionService 接口.
enable-user-connection-and-auth-token-table: true
# 是否支持内置的第三方登录 token 表(auth_token). 默认: true.
enable-auth-token-table: true
# ================ start: 定时刷新 access token 定时任务相关配置 ================
# 是否支持定时刷新 AccessToken 定时任务. 默认: false.
# 支持分布式(分布式 IOC 容器中必须有 RedisConnectionFactory, 也就是说, 是否分布式执行依据 IOC 容器中是否有 RedisConnectionFactory)
enableRefreshTokenJob: false
# A cron-like expression. 0 * 2 * * ? 分别对应: second/minute/hour/day of month/month/day of week,
# 默认为: "0 * 2 * * ?", 凌晨 2 点启动定时任务, 支持分布式(分布式 IOC 容器中必须有 {@link RedisConnectionFactory},
# 也就是说, 是否分布式执行依据 IOC 容器中是否有 {@link RedisConnectionFactory})
refresh-token-job-cron: 0 0/5 * * * ?
# 定时刷新 accessToken 任务时, 批处理数据库的记录数. 注意: 分布式应用时, 此配置不同服务器配置必须是一样的. batchCount 大小需要根据实际生产环境进行优化
batch-count: 1000
# accessToken 的剩余有效期内进行刷新 accessToken, 默认: 24, 单位: 小时. 注意: 需要根据实际生产环境进行优化
remaining-expire-in: 24
# ================ start: 定时刷新 access token 定时任务相关配置 ================
# JustAuth 内部参数设置
just-auth:
# 忽略校验 state 参数,默认不开启。当 ignoreCheckState 为 true 时, me.zhyd.oauth.request.AuthDefaultRequest.login(AuthCallback) 将不会校验 state 的合法性。
# 使用场景:当且仅当使用自实现 state 校验逻辑时开启
# 以下场景使用方案仅作参考:
# 1. 授权、登录为同端,并且全部使用 JustAuth 实现时,该值建议设为 false;
# 2. 授权和登录为不同端实现时,比如前端页面拼装 authorizeUrl并且前端自行对state进行校验后端只负责使用code获取用户信息时该值建议设为 true;
# 如非特殊需要,不建议开启这个配置
# 该方案主要为了解决以下类似场景的问题Since: 1.15.6, See Also: https://github.com/justauth/JustAuth/issues/83
ignoreCheckState: false
# 默认 state 缓存过期时间3分钟(PT180S) 鉴于授权过程中根据个人的操作习惯或者授权平台的不同google等每个授权流程的耗时也有差异
# 不过单个授权流程一般不会太长 本缓存工具默认的过期时间设置为3分钟即程序默认认为3分钟内的授权有效超过3分钟则默认失效失效后删除
# 注意: 这是为了测试打断点时用的, 生产环境自己设置为合适数组或默认
timeout: PT1800S
# JustAuth state 缓存类型, 默认 session
cacheType: session
# JustAuth state 缓存 key 前缀
cacheKeyPrefix: 'JUST_AUTH:'
# 用于 JustAuth 的代理(HttpClient)设置
proxy:
# 是否支持代理, 默认为: false.
enable: false
# 针对国外服务可以单独设置代理类型, 默认 Proxy.Type.HTTP, enable = true 时生效.
proxy: HTTP
# 代理 host, enable = true 时生效.
hostname:
# 代理端口, enable = true 时生效.
port:
# 用于国内代理(HttpClient)超时, 默认 PT3S
timeout: PT3S
# 用于国外网站代理(HttpClient)超时, 默认 PT15S
foreign-timeout: PT150S
# =============== start: 第三方登录时用的数据库表 user_connection 与 auth_token 添加 redis cache ===============
cache:
# redisCacheManager 设置, 默认实现: 对查询结果 null 值进行缓存, 添加时更新缓存 null 值.
redis:
# 是否开启缓存, 默认 false
open: false
# 是否使用 spring IOC 容器中的 RedisConnectionFactory 默认: false
# 如果使用 spring IOC 容器中的 RedisConnectionFactory则要注意 cache.database-index 要与 spring.redis.database 一样。
use-ioc-redis-connection-factory: true
cache:
# redis cache 存放的 database index, 默认: 0
database-index: 1
# 设置缓存管理器管理的缓存的默认过期时间, 默认: 200s
default-expire-time: PT200S
# cache ttl 。使用 0 声明一个永久的缓存。 默认: 180, 单位: 秒<br>
# 取缓存时间的 20% 作为动态的随机变量上下浮动, 防止同时缓存失效而缓存击穿
entry-ttl: PT180S
# Names of the default caches to consider for caching operations defined in the annotated class.
# 此设置不对 user_connection 与 auth_token 使用的缓存名称(UCC/UCHC/UCHACC)产生影响.
cache-names:
- cacheName
# =============== end: 第三方登录时用的数据库表 user_connection 与 auth_token 添加 redis cache ===============
# =============== start: 线程池配置 ===============
executor:
# 启动第三方授权登录用户的 accessToken 的定时任务时的 Executor 属性, 注意: 需要根据实际生产环境进行优化
job-task-scheduled-executor:
# 线程池中空闲时保留的线程数, 默认: 0
core-pool-size: 0
# keep alive time, 默认: 10
keep-alive-time: 0
# keepAliveTime 时间单位, 默认: 毫秒
time-unit: milliseconds
# 线程池名称, 默认: accessTokenJob
pool-name: accessTokenJob
# 拒绝策略, 默认: ABORT
rejected-execution-handler-policy: abort
# 线程池关闭过程的超时时间, 默认: PT10S
executor-shutdown-timeout: PT10S
# 更新第三方授权登录用户的 accessToken 的执行逻辑, 向本地数据库 auth_token 表获取过期或在一定时间内过期的 token 记录,
# 用 refreshToken 向第三方服务商更新 accessToken 信息的 Executor 属性,
# 注意: 定时刷新 accessToken 的执行逻辑是多线程的, 需要根据实际生产环境进行优化
refresh-token:
# 程池中空闲时保留的线程数, 默认: 0
core-pool-size: 0
# 最大线程数, 默认: 本机核心数
maximum-pool-size: 8
# keep alive time, 默认: 5
keep-alive-time: 5
# keepAliveTime 时间单位, 默认: 秒
time-unit: seconds
# blocking queue capacity, 默认: maximumPoolSize * 2
blocking-queue-capacity: 16
# 线程池名称, 默认: refreshToken
pool-name: refreshToken
# 拒绝策略, 默认: CALLER_RUNS 注意: 一般情况下不要更改默认设置, 没有实现 RefreshToken 逻辑被拒绝执行后的处理逻辑, 除非自己实现RefreshTokenJob.refreshTokenJob() 对 RefreshToken 逻辑被拒绝执行后的处理逻辑.
rejected-execution-handler-policy: caller_runs
# 线程池关闭过程的超时时间, 默认: 10 秒
executor-shutdown-timeout: PT10S
# 第三方授权登录时, 异步更新用户的第三方授权用户信息与 token 信息的 Executor 属性,
# 注意: 第三方授权登录时是异步更新第三方用户信息与 token 信息到本地数据库时使用此配置, 需要根据实际生产环境进行优化
user-connection-update:
# 程池中空闲时保留的线程数, 默认: 5
core-pool-size: 5
# 最大线程数, 默认: 本机核心数
maximum-pool-size: 8
# keep alive time, 默认: 10
keep-alive-time:
# keepAliveTime 时间单位, 默认: 秒
time-unit: seconds
# blocking queue capacity, 默认: maximumPoolSize * 2
blocking-queue-capacity: 16
# 线程池名称, 默认: updateConnection
pool-name: updateConnection
# 拒绝策略, 默认: CALLER_RUNS 注意: 一般情况下不要更改默认设置, 除非自己实现Auth2LoginAuthenticationProvider更新逻辑;
# 改成 ABORT 也支持, 默认实现 Auth2LoginAuthenticationProvider 是异步更新被拒绝执行后, 会执行同步更新.
rejected-execution-handler-policy: caller_runs
# 线程池关闭过程的超时时间, 默认: PT10S
executor-shutdown-timeout: PT10S
# =============== end: 线程池配置 ===============
# =============== start: user_connection repository 配置 ===============
repository:
# 第三方登录用户数据库表的字段 accessToken 与 refreshToken 加密专用密码
text-encryptor-password: 7ca5d913a17b4942942d16a974e3fecc
# 第三方登录用户数据库表的字段 accessToken 与 refreshToken 加密专用 salt
text-encryptor-salt: cd538b1b077542aca5f86942b6507fe2
# 是否在启动时检查并自动创建 userConnectionTableName 与 authTokenTableName, 默认: TRUE
enableStartUpInitializeTable: true
# 其他的有: 数据库表名称, 字段名称, curd sql 语句 等设置, 一般不需要更改,
# 如果要添加字段: 具体查看 RepositoryProperties 与 Auth2JdbcUsersConnectionRepository
# =============== end: user_connection repository 配置 ===============

View File

@ -4,7 +4,7 @@ ENV = 'production'
# 芋道管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
# 根据服务器或域名修改
PUBLIC_PATH = 'http://you_ip.cn/yudao-admin/'
PUBLIC_PATH = 'http://my-pi.com:8888/yudao-admin/'
# 二级部署路径
VUE_APP_APP_NAME ='yudao-admin'

View File

@ -38,3 +38,19 @@ export function getCodeImg() {
method: 'get'
})
}
// 接入第三方登录
export function oAuthLogin(provider) {
return request({
url: '/auth2/authorization/' + provider,
method: 'get'
})
}
export function getToken(path) {
console.log({path});
return request({
url: '/auth2/login/github' + path,
method: 'get'
})
}

View File

@ -7,7 +7,7 @@ import { getToken } from '@/utils/auth'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/oauthLogin/gitee']
router.beforeEach((to, from, next) => {
NProgress.start()

View File

@ -43,6 +43,11 @@ export const constantRoutes = [
component: (resolve) => require(['@/views/login'], resolve),
hidden: true
},
{
path: '/oauthLogin/gitee',
component: (resolve) => require(['@/views/oauthLogin'], resolve),
hidden: true
},
{
path: '/404',
component: (resolve) => require(['@/views/error/404'], resolve),

View File

@ -45,6 +45,15 @@
<span v-else> 中...</span>
</el-button>
</el-form-item>
<el-form-item style="width:100%;">
<div class="oauth-login" style="display:flex">
<div class="oauth-login-item" v-for="item in oauthProviders" :key="item.code" @click="doAuth2Login(item)">
<img :src=item.img height="25px" width="25px" alt="登录" >
<span>{{item.title}}</span>
</div>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
@ -54,7 +63,7 @@
</template>
<script>
import { getCodeImg } from "@/api/login";
import { getCodeImg,oAuthLogin } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
@ -71,6 +80,23 @@ export default {
code: "",
uuid: ""
},
oauthProviders: [
{
img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.2/gitee.png",
title: "码云",
code: "gitee"
},
{
img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.2/wechat.png",
title: "微信",
code: "weixin"
},
{
img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.2/qq.png",
title: "QQ",
code: "qq"
}
],
loginRules: {
username: [
{ required: true, trigger: "blur", message: "用户名不能为空" }
@ -135,6 +161,14 @@ export default {
});
}
});
},
doAuth2Login(provider) {
console.log("开始Oauth登录...%o", provider.code);
this.loading = true;
oAuthLogin(provider.code).then((res) => {
console.log(res.url);
window.location.href = res.url;
});
}
}
};
@ -201,4 +235,21 @@ export default {
.login-code-img {
height: 38px;
}
.oauth-login {
display: flex;
cursor:pointer;
}
.oauth-login-item {
display: flex;
align-items: center;
margin-right: 10px;
}
.oauth-login-item img {
height: 25px;
width: 25px;
}
.oauth-login-item span:hover {
text-decoration: underline red;
color: red;
}
</style>

View File

@ -0,0 +1,18 @@
<template>
</template>
<script>
import { setToken } from '@/utils/auth'
export default {
created() {
const {token} = this.$route.query;
setToken(token);
this.$router.push({ path: this.redirect || "/" }).catch(() => {});
},
}
</script>
<style>
</style>

View File

@ -41,7 +41,7 @@
<podam.version>7.2.6.RELEASE</podam.version>
<jedis-mock.version>0.1.16</jedis-mock.version>
<!-- 工具类相关 -->
<lombok.version>1.18.12</lombok.version>
<lombok.version>1.18.20</lombok.version>
<mapstruct.version>1.4.1.Final</mapstruct.version>
<hutool.version>5.6.1</hutool.version>
<easyexcel.verion>2.2.7</easyexcel.verion>

View File

@ -39,6 +39,19 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- justAuth 集成第三方登录 -->
<dependency>
<groupId>top.dcenter</groupId>
<artifactId>justAuth-spring-security-starter</artifactId>
<version>1.1.25</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -1,15 +1,12 @@
package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.filter.JwtAuthenticationTokenFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AbstractSignUpUrlAuthenticationSuccessHandler;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@ -19,13 +16,15 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import top.dcenter.ums.security.core.oauth.config.Auth2AutoConfigurer;
import top.dcenter.ums.security.core.oauth.properties.Auth2Properties;
import top.dcenter.ums.security.core.oauth.properties.OneClickLoginProperties;
import javax.annotation.Resource;
@ -48,7 +47,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
* 自定义用户认证逻辑
*/
@Resource
private UserDetailsService userDetailsService;
private SecurityAuthFrameworkService userDetailsService;
/**
* Spring Security 加密器
*/
@ -75,6 +74,15 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
@Resource
private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Autowired
private Auth2AutoConfigurer auth2AutoConfigurer;
@Autowired
private Auth2Properties auth2Properties;
@Autowired
private OneClickLoginProperties oneClickLoginProperties;
@Autowired
private AbstractSignUpUrlAuthenticationSuccessHandler authenticationSuccessHandler;
/**
* 由于 Spring Security 创建 AuthenticationManager 对象时没声明 @Bean 注解导致无法被注入
* 通过覆写父类的该方法添加 @Bean 注解解决该问题
@ -114,7 +122,11 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// ========= start: 使用 justAuth-spring-security-starter 必须步骤 =========
// 添加 Auth2AutoConfigurer 使 OAuth2(justAuth) login 生效.
.apply(this.auth2AutoConfigurer).and()
// 开启跨域
.cors().and()
// CSRF 禁用因为不使用 Session
@ -123,39 +135,59 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 一堆自定义的 Spring Security 处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler).and()
.accessDeniedHandler(accessDeniedHandler).and()
.formLogin().loginPage(api("/login")).successHandler(authenticationSuccessHandler).and()
// 设置每个请求的权限
.authorizeRequests()
// 登陆的接口可匿名访问
.antMatchers(api("/login")).anonymous()
// 通用的接口可匿名访问 TODO 芋艿需要抽象出去
.antMatchers(api("/system/captcha/**")).anonymous()
// 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问
.antMatchers(api("/infra/file/get/**")).anonymous()
// Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
// Spring Boot Admin Server 的安全配置 TODO 芋艿需要抽象出去
.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous()
// Spring Boot Actuator 的安全配置
.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous()
// Druid 监控 TODO 芋艿需要抽象出去
.antMatchers("/druid/**").anonymous()
// 短信回调 API TODO 芋艿需要抽象出去
.antMatchers(api("/system/sms/callback/**")).anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
// 登陆的接口可匿名访问
.antMatchers(api("/login")).anonymous()
// 通用的接口可匿名访问 TODO 芋艿需要抽象出去
.antMatchers(api("/system/captcha/**")).anonymous()
// 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问
.antMatchers(api("/infra/file/get/**")).anonymous()
// Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/favicon.ico").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
// Spring Boot Admin Server 的安全配置 TODO 芋艿需要抽象出去
.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous()
// Spring Boot Actuator 的安全配置
.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous()
// Druid 监控 TODO 芋艿需要抽象出去
.antMatchers("/druid/**").anonymous()
// 短信回调 API TODO 芋艿需要抽象出去
.antMatchers(api("/system/sms/callback/**")).anonymous()
// oAuth2 auth2/login/gitee
.antMatchers(api("/auth2/login/**")).anonymous()
.antMatchers(api("/auth2/authorization/**")).anonymous()
.antMatchers("/api/callback/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
// 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// // 放行第三方登录入口地址与第三方登录回调地址
// // @formatter:off
// httpSecurity.authorizeRequests()
// .antMatchers(HttpMethod.GET,
// auth2Properties.getRedirectUrlPrefix() + "/*",
// auth2Properties.getAuthLoginUrlPrefix() + "/*")
// .permitAll();
// httpSecurity.authorizeRequests()
// .antMatchers(HttpMethod.POST,
// oneClickLoginProperties.getLoginProcessingUrl())
// .permitAll();
// // @formatter:on
// // ========= end: 使用 justAuth-spring-security-starter 必须步骤 =========
}
private String api(String url) {

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.framework.security.core;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.model.AuthToken;
/**
* @author weir
*/
@Data
public class Auth2LoginUser extends LoginUser {
/**
* 是否为临时注册的临时用户
*/
private boolean isTempUser;
/**
* 用户第三方系统的唯一id在调用方集成该组件时可以用uuid + source唯一确定一个用户
*/
private String thirdPartyUserId;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 用户网址
*/
private String blog;
/**
* 所在公司
*/
private String company;
/**
* 位置
*/
private String location;
/**
* 用户邮箱
*/
private String email;
/**
* 用户备注各平台中的用户个人介绍
*/
private String remark;
/**
* 性别
*/
private AuthUserGender gender;
/**
* 用户来源
*/
private String source;
/**
* 用户授权的token信息
*/
private AuthToken token;
/**
* 第三方平台返回的原始用户信息
*/
private JSONObject rawUserInfo;
}

View File

@ -8,6 +8,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
@ -69,7 +70,7 @@ public class LoginUser implements UserDetails {
@Override
@JsonIgnore// 避免序列化
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
return new HashSet<>();
}
@Override

View File

@ -0,0 +1,61 @@
/*
* MIT License
* Copyright (c) 2020-2029 YongWu zheng (dcenter.top and gitee.com/pcore and github.com/ZeroOrInfinity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package cn.iocoder.yudao.framework.security.core.handler;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import top.dcenter.ums.security.core.oauth.userdetails.TemporaryUser;
import top.dcenter.ums.security.core.vo.ResponseResult;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.util.StringUtils.hasText;
import static top.dcenter.ums.security.core.oauth.util.MvcUtil.*;
/**
* @author weir
*/
public class AbstractSignUpUrlAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (requestCache.getRequest(request, response) != null) {
requestCache.getRequest(request, response);
}
super.onAuthenticationSuccess(request,response,authentication);
}
@Override
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}