Bladeren bron

新增第三方登录

mrbird 6 jaren geleden
bovenliggende
commit
a89de50aa8
22 gewijzigde bestanden met toevoegingen van 683 en 30 verwijderingen
  1. 9 0
      febs-auth/pom.xml
  2. 17 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsAuthorizationServerConfigure.java
  3. 148 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/controller/SocialLoginController.java
  4. 20 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/entity/BindUser.java
  5. 45 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/entity/UserConnection.java
  6. 35 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/manager/UserManager.java
  7. 11 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserConnectionMapper.java
  8. 8 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserRoleMapper.java
  9. 5 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsAuthProperties.java
  10. 45 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/UserConnectionService.java
  11. 12 1
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/FebsUserDetailService.java
  12. 3 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/OAuthClientDetailsServiceImpl.java
  13. 211 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/SocialLoginService.java
  14. 51 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/UserConnectionServiceImpl.java
  15. 3 1
      febs-auth/src/main/resources/febs-auth.properties
  16. 20 0
      febs-auth/src/main/resources/templates/error.html
  17. 22 0
      febs-auth/src/main/resources/templates/result.html
  18. 4 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/FebsConstant.java
  19. 4 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/ParamsConstant.java
  20. 10 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/SocialConstant.java
  21. 0 8
      febs-server/febs-server-system/src/main/java/cc/mrbird/febs/server/system/service/IUserService.java
  22. 0 20
      febs-server/febs-server-system/src/main/java/cc/mrbird/febs/server/system/service/impl/UserServiceImpl.java

+ 9 - 0
febs-auth/pom.xml

@@ -48,6 +48,15 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.xkcoding</groupId>
+            <artifactId>justauth-spring-boot-starter</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 17 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsAuthorizationServerConfigure.java

@@ -15,6 +15,9 @@ import org.springframework.security.oauth2.config.annotation.configurers.ClientD
 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
+import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
+import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
+import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
 import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
 import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
 import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
@@ -98,4 +101,18 @@ public class FebsAuthorizationServerConfigure extends AuthorizationServerConfigu
         return accessTokenConverter;
     }
 
+    @Bean
+    public ResourceOwnerPasswordTokenGranter resourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory) {
+        DefaultTokenServices defaultTokenServices = defaultTokenServices();
+        if (properties.getEnableJwt()) {
+            defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
+        }
+        return new ResourceOwnerPasswordTokenGranter(authenticationManager, defaultTokenServices, redisClientDetailsService, oAuth2RequestFactory);
+    }
+
+    @Bean
+    public DefaultOAuth2RequestFactory oAuth2RequestFactory() {
+        return new DefaultOAuth2RequestFactory(redisClientDetailsService);
+    }
+
 }

+ 148 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/controller/SocialLoginController.java

@@ -0,0 +1,148 @@
+package cc.mrbird.febs.auth.controller;
+
+import cc.mrbird.febs.auth.entity.BindUser;
+import cc.mrbird.febs.auth.entity.UserConnection;
+import cc.mrbird.febs.auth.service.impl.SocialLoginService;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.exception.FebsException;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+@Slf4j
+@Controller
+@RequestMapping("social")
+public class SocialLoginController {
+
+    private static final String TYPE_LOGIN = "login";
+    private static final String TYPE_BIND = "bind";
+
+    @Autowired
+    private SocialLoginService socialLoginService;
+    @Value("${febs.frontUrl}")
+    private String frontUrl;
+
+    /**
+     * 登录
+     *
+     * @param oauthType 第三方登录类型
+     * @param response  response
+     */
+    @ResponseBody
+    @GetMapping("/login/{oauthType}/{type}")
+    public void renderAuth(@PathVariable String oauthType, @PathVariable String type, HttpServletResponse response) throws IOException, FebsException {
+        AuthRequest authRequest = socialLoginService.renderAuth(oauthType);
+        response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState()) + "::" + type);
+    }
+
+    /**
+     * 登录成功后的回调
+     *
+     * @param oauthType 第三方登录类型
+     * @param callback  携带返回的信息
+     * @return String
+     */
+    @GetMapping("/{oauthType}/callback")
+    public String login(@PathVariable String oauthType, AuthCallback callback, String state, Model model) {
+        try {
+            FebsResponse febsResponse = null;
+            String type = StringUtils.substringAfterLast(state, "::");
+            if (StringUtils.equals(type, TYPE_BIND)) {
+                febsResponse = socialLoginService.resolveBind(oauthType, callback);
+            } else {
+                febsResponse = socialLoginService.resolveLogin(oauthType, callback);
+            }
+            model.addAttribute("response", febsResponse);
+            model.addAttribute("frontUrl", frontUrl);
+            return "result";
+        } catch (Exception e) {
+            String errorMessage = FebsUtil.containChinese(e.getMessage()) ? e.getMessage() : "第三方登录失败";
+            model.addAttribute("error", e.getMessage());
+            return "error";
+        }
+    }
+
+    /**
+     * 绑定并登录
+     *
+     * @param bindUser bindUser
+     * @param authUser authUser
+     * @return FebsResponse
+     */
+    @ResponseBody
+    @PostMapping("bind/login")
+    public FebsResponse bindLogin(@Valid BindUser bindUser, AuthUser authUser) throws FebsException {
+        OAuth2AccessToken oAuth2AccessToken = this.socialLoginService.bindLogin(bindUser, authUser);
+        return new FebsResponse().data(oAuth2AccessToken);
+    }
+
+    /**
+     * 注册并登录
+     *
+     * @param registUser registUser
+     * @param authUser   authUser
+     * @return FebsResponse
+     */
+    @ResponseBody
+    @PostMapping("sign/login")
+    public FebsResponse signLogin(@Valid BindUser registUser, AuthUser authUser) throws FebsException {
+        OAuth2AccessToken oAuth2AccessToken = this.socialLoginService.signLogin(registUser, authUser);
+        return new FebsResponse().data(oAuth2AccessToken);
+    }
+
+    /**
+     * 绑定
+     *
+     * @param bindUser bindUser
+     * @param authUser authUser
+     */
+    @ResponseBody
+    @PostMapping("bind")
+    public void bind(BindUser bindUser, AuthUser authUser) throws FebsException {
+        this.socialLoginService.bind(bindUser, authUser);
+    }
+
+    /**
+     * 解绑
+     *
+     * @param bindUser  bindUser
+     * @param oauthType oauthType
+     */
+    @ResponseBody
+    @DeleteMapping("unbind")
+    public void unbind(BindUser bindUser, String oauthType) throws FebsException {
+        this.socialLoginService.unbind(bindUser, oauthType);
+    }
+
+    /**
+     * 根据用户名获取绑定关系
+     *
+     * @param username 用户名
+     * @return FebsResponse
+     */
+    @ResponseBody
+    @GetMapping("connections/{username}")
+    public FebsResponse findUserConnections(@NotBlank(message = "{required}") @PathVariable String username) {
+        List<UserConnection> userConnections = this.socialLoginService.findUserConnections(username);
+        return new FebsResponse().data(userConnections);
+    }
+}

+ 20 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/entity/BindUser.java

@@ -0,0 +1,20 @@
+package cc.mrbird.febs.auth.entity;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * @author MrBird
+ */
+@Data
+public class BindUser implements Serializable {
+
+    private static final long serialVersionUID = -3890998115990166651L;
+
+    @NotBlank(message = "{required}")
+    private String bindUsername;
+    @NotBlank(message = "{required}")
+    private String bindPassword;
+}

+ 45 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/entity/UserConnection.java

@@ -0,0 +1,45 @@
+package cc.mrbird.febs.auth.entity;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_user_connection")
+public class UserConnection {
+
+    @TableId(value = "USER_NAME")
+    @NotBlank(message = "{required}")
+    private String userName;
+
+    @TableId(value = "PROVIDER_NAME")
+    @NotBlank(message = "{required}")
+    private String providerName;
+
+    @TableId(value = "PROVIDER_USER_ID")
+    @NotBlank(message = "{required}")
+    private String providerUserId;
+
+    @TableField(value = "PROVIDER_USER_NAME")
+    private String providerUserName;
+
+    @TableField(value = "NICK_NAME")
+    private String nickName;
+
+    @TableField("IMAGE_URL")
+    private String imageUrl;
+
+    @TableField("LOCATION")
+    private String location;
+
+    @TableField("REMARK")
+    private String remark;
+
+}

+ 35 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/manager/UserManager.java

@@ -2,11 +2,17 @@ package cc.mrbird.febs.auth.manager;
 
 import cc.mrbird.febs.auth.mapper.MenuMapper;
 import cc.mrbird.febs.auth.mapper.UserMapper;
+import cc.mrbird.febs.auth.mapper.UserRoleMapper;
+import cc.mrbird.febs.common.entity.constant.FebsConstant;
 import cc.mrbird.febs.common.entity.system.Menu;
 import cc.mrbird.febs.common.entity.system.SystemUser;
+import cc.mrbird.febs.common.entity.system.UserRole;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -16,12 +22,15 @@ import java.util.stream.Collectors;
  * @author MrBird
  */
 @Service
+@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
 public class UserManager {
 
     @Autowired
     private UserMapper userMapper;
     @Autowired
     private MenuMapper menuMapper;
+    @Autowired
+    private UserRoleMapper userRoleMapper;
 
     /**
      * 通过用户名查询用户信息
@@ -43,4 +52,30 @@ public class UserManager {
         List<Menu> userPermissions = menuMapper.findUserPermissions(username);
         return userPermissions.stream().map(Menu::getPerms).collect(Collectors.joining(","));
     }
+
+    /**
+     * 注册用户
+     *
+     * @param username username
+     * @param password password
+     * @return SystemUser SystemUser
+     */
+    @Transactional
+    public SystemUser registUser(String username,String password) {
+        SystemUser systemUser = new SystemUser();
+        systemUser.setUsername(username);
+        systemUser.setPassword(password);
+        systemUser.setCreateTime(new Date());
+        systemUser.setStatus(SystemUser.STATUS_VALID);
+        systemUser.setSex(SystemUser.SEX_UNKNOW);
+        systemUser.setAvatar(SystemUser.DEFAULT_AVATAR);
+        systemUser.setDescription("注册用户");
+        this.userMapper.insert(systemUser);
+
+        UserRole userRole = new UserRole();
+        userRole.setUserId(systemUser.getUserId());
+        userRole.setRoleId(FebsConstant.REGISTER_ROLE_ID); // 注册用户角色 ID
+        this.userRoleMapper.insert(userRole);
+        return systemUser;
+    }
 }

+ 11 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserConnectionMapper.java

@@ -0,0 +1,11 @@
+package cc.mrbird.febs.auth.mapper;
+
+import cc.mrbird.febs.auth.entity.UserConnection;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @author MrBird
+ */
+public interface UserConnectionMapper extends BaseMapper<UserConnection> {
+
+}

+ 8 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserRoleMapper.java

@@ -0,0 +1,8 @@
+package cc.mrbird.febs.auth.mapper;
+
+import cc.mrbird.febs.common.entity.system.UserRole;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface UserRoleMapper extends BaseMapper<UserRole> {
+
+}

+ 5 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsAuthProperties.java

@@ -30,4 +30,9 @@ public class FebsAuthProperties {
      * 是否使用 JWT令牌
      */
     private Boolean enableJwt;
+
+    /**
+     * 社交登录所使用的 Client
+     */
+    private String socialLoginClientId;
 }

+ 45 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/UserConnectionService.java

@@ -0,0 +1,45 @@
+package cc.mrbird.febs.auth.service;
+
+
+import cc.mrbird.febs.auth.entity.UserConnection;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+public interface UserConnectionService extends IService<UserConnection> {
+
+    /**
+     * 根据条件查询关联关系
+     *
+     * @param providerName   平台名称
+     * @param providerUserId 平台用户ID
+     * @return 关联关系
+     */
+    UserConnection selectByCondition(String providerName, String providerUserId);
+
+    /**
+     * 根据条件查询关联关系
+     *
+     * @param username 用户名
+     * @return 关联关系
+     */
+    List<UserConnection> selectByCondition(String username);
+
+    /**
+     * 新增
+     *
+     * @param userConnection userConnection
+     */
+    void createUserConnection(UserConnection userConnection);
+
+    /**
+     * 删除
+     *
+     * @param username     username 用户名
+     * @param providerName providerName 平台名称
+     */
+    void deleteByCondition(String username, String providerName);
+}

+ 12 - 1
febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/FebsUserDetailService.java

@@ -2,7 +2,10 @@ package cc.mrbird.febs.auth.service.impl;
 
 import cc.mrbird.febs.auth.manager.UserManager;
 import cc.mrbird.febs.common.entity.FebsAuthUser;
+import cc.mrbird.febs.common.entity.constant.ParamsConstant;
+import cc.mrbird.febs.common.entity.constant.SocialConstant;
 import cc.mrbird.febs.common.entity.system.SystemUser;
+import cc.mrbird.febs.common.utils.HttpContextUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -13,6 +16,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * @author MrBird
  */
@@ -26,13 +31,19 @@ public class FebsUserDetailService implements UserDetailsService {
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        HttpServletRequest httpServletRequest = HttpContextUtil.getHttpServletRequest();
         SystemUser systemUser = userManager.findByName(username);
         if (systemUser != null) {
             String permissions = userManager.findUserPermissions(systemUser.getUsername());
             boolean notLocked = false;
             if (StringUtils.equals(SystemUser.STATUS_VALID, systemUser.getStatus()))
                 notLocked = true;
-            FebsAuthUser authUser = new FebsAuthUser(systemUser.getUsername(), systemUser.getPassword(), true, true, true, notLocked,
+            String password = systemUser.getPassword();
+            String loginType = (String) httpServletRequest.getAttribute(ParamsConstant.LOGIN_TYPE);
+            if (StringUtils.equals(loginType, SocialConstant.SOCIAL_LOGIN)) {
+                password = passwordEncoder.encode(SocialConstant.SOCIAL_LOGIN_PASSWORD);
+            }
+            FebsAuthUser authUser = new FebsAuthUser(systemUser.getUsername(), password, true, true, true, notLocked,
                     AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
 
             BeanUtils.copyProperties(systemUser, authUser);

+ 3 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/OAuthClientDetailsServiceImpl.java

@@ -59,6 +59,7 @@ public class OAuthClientDetailsServiceImpl extends ServiceImpl<OAuthClientDetail
     }
 
     @Override
+    @Transactional
     public void createOAuthClientDetails(OAuthClientDetails oauthClientDetails) throws FebsException {
         OAuthClientDetails byId = this.findById(oauthClientDetails.getClientId());
         if (byId != null) {
@@ -74,6 +75,7 @@ public class OAuthClientDetailsServiceImpl extends ServiceImpl<OAuthClientDetail
     }
 
     @Override
+    @Transactional
     public void updateOAuthClientDetails(OAuthClientDetails oauthClientDetails) {
         String clientId = oauthClientDetails.getClientId();
 
@@ -91,6 +93,7 @@ public class OAuthClientDetailsServiceImpl extends ServiceImpl<OAuthClientDetail
     }
 
     @Override
+    @Transactional
     public void deleteOAuthClientDetails(String clientIds) {
         Object[] clientIdArray = StringUtils.splitByWholeSeparatorPreserveAllTokens(clientIds, ",");
         LambdaQueryWrapper<OAuthClientDetails> queryWrapper = new LambdaQueryWrapper<>();

+ 211 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/SocialLoginService.java

@@ -0,0 +1,211 @@
+package cc.mrbird.febs.auth.service.impl;
+
+import cc.mrbird.febs.auth.entity.BindUser;
+import cc.mrbird.febs.auth.entity.UserConnection;
+import cc.mrbird.febs.auth.manager.UserManager;
+import cc.mrbird.febs.auth.properties.FebsAuthProperties;
+import cc.mrbird.febs.auth.service.UserConnectionService;
+import cc.mrbird.febs.common.entity.FebsAuthUser;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.entity.constant.GrantTypeConstant;
+import cc.mrbird.febs.common.entity.constant.ParamsConstant;
+import cc.mrbird.febs.common.entity.constant.SocialConstant;
+import cc.mrbird.febs.common.entity.system.SystemUser;
+import cc.mrbird.febs.common.exception.FebsException;
+import cc.mrbird.febs.common.utils.HttpContextUtil;
+import cn.hutool.core.util.StrUtil;
+import com.xkcoding.justauth.AuthRequestFactory;
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.provider.TokenRequest;
+import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author MrBird
+ */
+@Service
+public class SocialLoginService {
+
+    private static final String USERNAME = "username";
+    private static final String PASSWORD = "password";
+
+    private static final String NOT_BIND = "not_bind";
+    private static final String SOCIAL_LOGIN_SUCCESS = "social_login_success";
+
+    @Autowired
+    private UserManager userManager;
+    @Autowired
+    private AuthRequestFactory factory;
+    @Autowired
+    private FebsAuthProperties properties;
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+    @Autowired
+    private UserConnectionService userConnectionService;
+    @Autowired
+    private ResourceOwnerPasswordTokenGranter granter;
+    @Autowired
+    private RedisClientDetailsService redisClientDetailsService;
+
+    public AuthRequest renderAuth(String oauthType) throws FebsException {
+        return factory.get(getAuthSource(oauthType));
+    }
+
+    public FebsResponse resolveBind(String oauthType, AuthCallback callback) throws FebsException {
+        FebsResponse febsResponse = new FebsResponse();
+        AuthRequest authRequest = factory.get(getAuthSource(oauthType));
+        AuthResponse response = authRequest.login(resolveAuthCallback(callback));
+        if (response.ok()) {
+            febsResponse.data(response.getData());
+        } else {
+            throw new FebsException(String.format("第三方登录失败,%s", response.getMsg()));
+        }
+        return febsResponse;
+    }
+
+    public FebsResponse resolveLogin(String oauthType, AuthCallback callback) throws FebsException {
+        FebsResponse febsResponse = new FebsResponse();
+        AuthRequest authRequest = factory.get(getAuthSource(oauthType));
+        AuthResponse response = authRequest.login(resolveAuthCallback(callback));
+        if (response.ok()) {
+            AuthUser authUser = (AuthUser) response.getData();
+            UserConnection userConnection = userConnectionService.selectByCondition(authUser.getSource().toString(), authUser.getUuid());
+            if (userConnection == null) {
+                febsResponse.message(NOT_BIND).data(authUser);
+            } else {
+                SystemUser user = userManager.findByName(userConnection.getUserName());
+                if (user == null) {
+                    throw new FebsException("系统中未找到与第三方账号对应的账户");
+                }
+                OAuth2AccessToken oAuth2AccessToken = getOAuth2AccessToken(user);
+                febsResponse.message(SOCIAL_LOGIN_SUCCESS).data(oAuth2AccessToken);
+                febsResponse.put(USERNAME, user.getUsername());
+            }
+        } else {
+            throw new FebsException(String.format("第三方登录失败,%s", response.getMsg()));
+        }
+        return febsResponse;
+    }
+
+    public OAuth2AccessToken bindLogin(BindUser bindUser, AuthUser authUser) throws FebsException {
+        SystemUser systemUser = userManager.findByName(bindUser.getBindUsername());
+        if (systemUser == null || !passwordEncoder.matches(bindUser.getBindPassword(), systemUser.getPassword())) {
+            throw new FebsException("绑定系统账号失败,用户名或密码错误!");
+        }
+        this.createConnection(systemUser, authUser);
+        return this.getOAuth2AccessToken(systemUser);
+    }
+
+    public OAuth2AccessToken signLogin(BindUser registUser, AuthUser authUser) throws FebsException {
+        SystemUser user = this.userManager.findByName(registUser.getBindUsername());
+        if (user != null) {
+            throw new FebsException("该用户名已存在!");
+        }
+        String encryptPassword = passwordEncoder.encode(registUser.getBindPassword());
+        SystemUser systemUser = this.userManager.registUser(registUser.getBindUsername(), encryptPassword);
+        this.createConnection(systemUser, authUser);
+        return this.getOAuth2AccessToken(systemUser);
+    }
+
+    public void bind(BindUser bindUser, AuthUser authUser) throws FebsException {
+        String username = bindUser.getBindUsername();
+        if (isCurrentUser(username)) {
+            UserConnection userConnection = userConnectionService.selectByCondition(authUser.getSource().toString(), authUser.getUuid());
+            if (userConnection != null) {
+                throw new FebsException("绑定失败,该第三方账号已绑定" + userConnection.getUserName() + "系统账户");
+            }
+            SystemUser systemUser = new SystemUser();
+            systemUser.setUsername(username);
+            this.createConnection(systemUser, authUser);
+        } else {
+            throw new FebsException("绑定失败,您无权绑定别人的账号");
+        }
+    }
+
+    public void unbind(BindUser bindUser, String oauthType) throws FebsException {
+        String username = bindUser.getBindUsername();
+        if (isCurrentUser(username)) {
+            this.userConnectionService.deleteByCondition(username, oauthType);
+        } else {
+            throw new FebsException("绑定失败,您无权解绑别人的账号");
+        }
+    }
+
+    public List<UserConnection> findUserConnections(String username) {
+        return this.userConnectionService.selectByCondition(username);
+    }
+
+    private void createConnection(SystemUser systemUser, AuthUser authUser) {
+        UserConnection userConnection = new UserConnection();
+        userConnection.setUserName(systemUser.getUsername());
+        userConnection.setProviderName(authUser.getSource().toString());
+        userConnection.setProviderUserId(authUser.getUuid());
+        userConnection.setProviderUserName(authUser.getUsername());
+        userConnection.setImageUrl(authUser.getAvatar());
+        userConnection.setNickName(authUser.getNickname());
+        userConnection.setLocation(authUser.getLocation());
+        this.userConnectionService.createUserConnection(userConnection);
+    }
+
+    private AuthCallback resolveAuthCallback(AuthCallback callback) {
+        String state = callback.getState();
+        String[] strings = StringUtils.splitByWholeSeparatorPreserveAllTokens(state, "::");
+        if (strings.length == 3) {
+            callback.setState(strings[0] + "::" + strings[1]);
+        }
+        return callback;
+    }
+
+    private AuthSource getAuthSource(String type) throws FebsException {
+        if (StrUtil.isNotBlank(type)) {
+            return AuthSource.valueOf(type.toUpperCase());
+        } else {
+            throw new FebsException(String.format("暂不支持%s第三方登录", type));
+        }
+    }
+
+    private boolean isCurrentUser(String username) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        FebsAuthUser authUser = (FebsAuthUser) authentication.getPrincipal();
+        return StringUtils.equalsIgnoreCase(username, authUser.getUsername());
+    }
+
+    private OAuth2AccessToken getOAuth2AccessToken(SystemUser user) throws FebsException {
+        final HttpServletRequest httpServletRequest = HttpContextUtil.getHttpServletRequest();
+        httpServletRequest.setAttribute(ParamsConstant.LOGIN_TYPE, SocialConstant.SOCIAL_LOGIN);
+        String socialLoginClientId = properties.getSocialLoginClientId();
+        ClientDetails clientDetails = null;
+        try {
+            clientDetails = redisClientDetailsService.loadClientByClientId(socialLoginClientId);
+        } catch (Exception e) {
+            throw new FebsException("获取第三方登录可用的Client失败");
+        }
+        if (clientDetails == null) {
+            throw new FebsException("未找到第三方登录可用的Client");
+        }
+        Map<String, String> requestParameters = new HashMap<>(5);
+        requestParameters.put(ParamsConstant.GRANT_TYPE, GrantTypeConstant.PASSWORD);
+        requestParameters.put(USERNAME, user.getUsername());
+        requestParameters.put(PASSWORD, SocialConstant.SOCIAL_LOGIN_PASSWORD);
+
+        String grantTypes = String.join(",", clientDetails.getAuthorizedGrantTypes());
+        TokenRequest tokenRequest = new TokenRequest(requestParameters, clientDetails.getClientId(), clientDetails.getScope(), grantTypes);
+        return granter.grant(GrantTypeConstant.PASSWORD, tokenRequest);
+    }
+}

+ 51 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/UserConnectionServiceImpl.java

@@ -0,0 +1,51 @@
+package cc.mrbird.febs.auth.service.impl;
+
+import cc.mrbird.febs.auth.entity.UserConnection;
+import cc.mrbird.febs.auth.mapper.UserConnectionMapper;
+import cc.mrbird.febs.auth.service.UserConnectionService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+@Service
+@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
+public class UserConnectionServiceImpl extends ServiceImpl<UserConnectionMapper, UserConnection> implements UserConnectionService {
+
+    @Override
+    public UserConnection selectByCondition(String providerName, String providerUserId) {
+        LambdaQueryWrapper<UserConnection> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserConnection::getProviderName, providerName)
+                .eq(UserConnection::getProviderUserId, providerUserId);
+        return this.baseMapper.selectOne(queryWrapper);
+    }
+
+    @Override
+    public List<UserConnection> selectByCondition(String username) {
+        LambdaQueryWrapper<UserConnection> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserConnection::getUserName, username);
+        return this.baseMapper.selectList(queryWrapper);
+    }
+
+    @Override
+    @Transactional
+    public void createUserConnection(UserConnection userConnection) {
+        this.baseMapper.insert(userConnection);
+    }
+
+    @Override
+    @Transactional
+    public void deleteByCondition(String username, String providerName) {
+        LambdaQueryWrapper<UserConnection> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserConnection::getUserName, username);
+        queryWrapper.eq(UserConnection::getProviderName, providerName);
+        this.remove(queryWrapper);
+    }
+
+}

+ 3 - 1
febs-auth/src/main/resources/febs-auth.properties

@@ -1,7 +1,9 @@
-febs.auth.anonUrl=/actuator/**,/captcha
+febs.auth.anonUrl=/actuator/**,/captcha,/social/**
 febs.auth.enableJwt=true
 febs.auth.jwtAccessKey=febs
 
+febs.auth.socialLoginClientId=app
+
 febs.auth.code.time=120
 febs.auth.code.type=png
 febs.auth.code.width=115

+ 20 - 0
febs-auth/src/main/resources/templates/error.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" lang="ch">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>第三方登录失败</title>
+</head>
+<style>
+    span {
+        font-size: .9rem;
+        font-weight: bold;
+        color: #42b983;
+    }
+</style>
+<body>
+    <span>[[${error}]]</span>
+</body>
+</html>

+ 22 - 0
febs-auth/src/main/resources/templates/result.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" lang="ch">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>登录跳转中</title>
+</head>
+<body>
+    登录中..
+<script th:inline="javascript">
+    var response = [[${response}]];
+    var frontUrl = [[${frontUrl}]];
+    console.log(frontUrl);
+    window.onload = function () {
+        window.opener.postMessage(response, frontUrl);
+        window.close();
+    }
+</script>
+</body>
+</html>

+ 4 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/FebsConstant.java

@@ -52,5 +52,9 @@ public class FebsConstant {
      * utf-8
      */
     public static final String UTF8 = "utf-8";
+    /**
+     * 注册用户角色ID
+     */
+    public static final Long REGISTER_ROLE_ID = 2L;
 
 }

+ 4 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/ParamsConstant.java

@@ -19,5 +19,9 @@ public class ParamsConstant {
      * 认证类型参数 key
      */
     public static final String GRANT_TYPE = "grant_type";
+    /**
+     * 登录类型
+     */
+    public static final String LOGIN_TYPE = "login_type";
 
 }

+ 10 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/constant/SocialConstant.java

@@ -0,0 +1,10 @@
+package cc.mrbird.febs.common.entity.constant;
+
+/**
+ * @author MrBird
+ */
+public class SocialConstant {
+
+    public static final String SOCIAL_LOGIN = "social_login";
+    public static final String SOCIAL_LOGIN_PASSWORD = "febs_social_login_password";
+}

+ 0 - 8
febs-server/febs-server-system/src/main/java/cc/mrbird/febs/server/system/service/IUserService.java

@@ -86,14 +86,6 @@ public interface IUserService extends IService<SystemUser> {
      */
     void updatePassword(String username, String password);
 
-    /**
-     * 注册用户
-     *
-     * @param username 用户名
-     * @param password 密码
-     */
-    void regist(String username, String password);
-
     /**
      * 重置密码
      *

+ 0 - 20
febs-server/febs-server-system/src/main/java/cc/mrbird/febs/server/system/service/impl/UserServiceImpl.java

@@ -129,26 +129,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, SystemUser> impleme
         this.baseMapper.update(user, new LambdaQueryWrapper<SystemUser>().eq(SystemUser::getUsername, username));
     }
 
-    @Override
-    @Transactional
-    public void regist(String username, String password) {
-        SystemUser user = new SystemUser();
-        user.setPassword(passwordEncoder.encode(password));
-        user.setUsername(username);
-        user.setCreateTime(new Date());
-        user.setStatus(SystemUser.STATUS_VALID);
-        user.setSex(SystemUser.SEX_UNKNOW);
-        user.setAvatar(SystemUser.DEFAULT_AVATAR);
-        user.setDescription("注册用户");
-        this.save(user);
-
-        UserRole ur = new UserRole();
-        ur.setUserId(user.getUserId());
-        ur.setRoleId(2L); // 注册用户角色 ID
-        this.userRoleService.save(ur);
-
-    }
-
     @Override
     @Transactional
     public void resetPassword(String[] usernames) {