JustAuth-第三方登录组件
1、新增依赖
<dependency> <groupId>me.zhyd.oauth</groupId> <artifactId>JustAuth</artifactId> <version>1.16.5</version> </dependency>
2、前端示例
<template> <div class="social-signup-container"> <div class="sign-btn" @click="thirdLoginClick('wechat')"> <span class="wx-svg-container"><svg-icon icon-class="wechat" class="icon"/></span> 微信 </div> <div class="sign-btn" @click="thirdLoginClick('qq')"> <span class="qq-svg-container"><svg-icon icon-class="qq" class="icon"/></span> QQ </div> <div class="sign-btn" @click="thirdLoginClick('gitee')"> <span class="gitee-svg-container"><svg-icon icon-class="gitee" class="icon"/></span> Gitee </div> </div> </template>
<script>
export default {
name: 'SocialSignin',
methods: {
// 请求后台获取跳转路径
thirdLoginClick(source) {
this.$store.dispatch('user/thirdLogin', {source: source}).then(() => {
}).catch(() => {
})
}
}
}
</script>
// 第三方登录 thirdLogin({commit}, userInfo) { const {source} = userInfo return new Promise((resolve, reject) => { thirdLogin({source: source.trim()}).then(response => { console.log("第三方登录返回", response) if (response.data.code === "200") { window.location.href = response.data.url } else { Message.warning(response.data.msg); } resolve() }).catch(error => { reject(error) }) }) }
export function thirdLogin(data) { return request({ url: '/login/render', method: 'post', params: {source: data.source} }) }
3、后端示例
获取跳转路径
import com.mxy.common.core.utils.ServiceResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.*; /** * 第三方登录认证 */ @RestController @RequestMapping("/api/login") @Api(value = "第三方登录相关接口", tags = {"第三方登录相关接口"}) @Slf4j public class AuthRestApi { @Resource private AuthUtil authUtil; @ApiOperation(value = "系统认证", notes = "系统认证") @RequestMapping("/render") public String renderAuth(String source) { log.info("进入第三方认证:" + source); Map<String, String> map = new HashMap<>(); AuthRequest authRequest = authUtil.getAuthRequest(source); if (authRequest == null) { map.put("code", "201"); map.put("msg", "系统未开启该登录方式"); return ServiceResult.success(map); } String token = AuthStateUtils.createState(); String authorizeUrl = authRequest.authorize(token); log.info("获取返回url:" + authorizeUrl); map.put("code", "200"); map.put("url", authorizeUrl); return ServiceResult.success(map); } }
拦截登录回调接口(ThirdPartyAuthenticationFilter)
import me.zhyd.oauth.model.AuthCallback; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 第三方登录-拦截器 * 拼接入参 */ public class ThirdPartyAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static String EXTEND_LOGIN_URL = "/api/login/callback/**"; private boolean getOnly = true; /** * 表示这个 Filter 拦截 /api/login/callback/** 接口 */ private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(EXTEND_LOGIN_URL, "GET"); public ThirdPartyAuthenticationFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.getOnly && !"GET".equals(request.getMethod())) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { ThirdPartyAuthenticationToken authRequest = new ThirdPartyAuthenticationToken(getSourceType(request), getCallback(request)); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } } protected void setDetails(HttpServletRequest request, ThirdPartyAuthenticationToken authRequest) { authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); } /** * 组装请求 */ private AuthCallback getCallback(HttpServletRequest request) { return AuthCallback.builder() .code(request.getParameter("code")) .auth_code(request.getParameter("auth_code")) .authorization_code(request.getParameter("authorization_code")) .oauth_token(request.getParameter("oauth_token")) .state(request.getParameter("state")) .oauth_verifier(request.getParameter("oauth_verifier")) .build(); } /** * 判断-登录系统类型 */ private String getSourceType(HttpServletRequest request) { String uri = request.getRequestURI(); int common = EXTEND_LOGIN_URL.length() - 2; int start = uri.indexOf(EXTEND_LOGIN_URL.substring(0, common)); return uri.substring(start + common); } }
封装用户信息(ThirdPartyAuthenticationToken)
import me.zhyd.oauth.model.AuthCallback; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; /** * 模仿 UsernamePasswordAuthenticationToken * 封装用户信息 */ public class ThirdPartyAuthenticationToken extends AbstractAuthenticationToken { /** * 认证返回 */ private AuthCallback callback; /** * 登录类型 */ private String source; /** * 用户实体 */ private Object principal; /** * 用户id */ private Object credentials; public ThirdPartyAuthenticationToken(String source, AuthCallback callback) { super(null); this.source = source; this.callback = callback; setAuthenticated(false); } public ThirdPartyAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getPrincipal() { return principal; } @Override public Object getCredentials() { return credentials; } public Object getCallback() { return callback; } public Object getSource() { return source; } }
第三方统一登录认证逻辑(ThirdPartyAuthenticationProvider)
package com.mxy.security.justauth; import com.alibaba.fastjson.JSONObject; import com.mxy.common.core.constant.Constants; import com.mxy.common.core.entity.SelfUserEntity; import com.mxy.common.core.entity.SysRole; import com.mxy.common.core.entity.SysUser; import com.mxy.common.core.entity.SysUserRole; import com.mxy.common.core.utils.IPUtils; import com.mxy.common.core.utils.RedisUtil; import com.mxy.common.core.utils.ServletUtils; import com.mxy.common.log.enums.OperType; import com.mxy.security.security.service.SelfUserDetailsService; import com.mxy.system.service.SysUserService; import com.mxy.system.utils.LogUtil; import lombok.extern.slf4j.Slf4j; 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.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.*; /** * 第三方统一登录认证逻辑 */ @Slf4j @Component public class ThirdPartyAuthenticationProvider implements AuthenticationProvider { @Autowired private SelfUserDetailsService selfUserDetailsService; @Autowired private SysUserService sysUserService; @Autowired private RedisUtil redisUtil; @Resource private AuthUtil authUtil; @Resource private BCryptPasswordEncoder bCryptPasswordEncoder; private Random random = new Random(); /** * 第三方统一登录认证逻辑 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { ThirdPartyAuthenticationToken token = (ThirdPartyAuthenticationToken) authentication; String source = (String) token.getSource(); AuthCallback callback = (AuthCallback) token.getCallback(); log.info("------------进入" + source + "认证逻辑, callback params:" + JSONObject.toJSONString(callback)); AuthRequest authRequest = authUtil.getAuthRequest(source); AuthResponse response = authRequest.login(callback); if (response.getCode() == 5000) { // 认证失败 throw new BadCredentialsException(source + "认证失败"); } AuthUser authUser = (AuthUser) response.getData(); log.info("------------认证用户:{}", authUser); // 根据 uuid 查询用户信息 SelfUserEntity userInfo = selfUserDetailsService.getUserInfoByUuid(authUser.getUuid()); if (userInfo == null) { // 自动注册 userInfo = doRegister(authUser); } if (Constants.USER_STATE_TWO.equals(userInfo.getStatus())) { LogUtil.saveLog("该账号已冻结[" + userInfo.getRelName() + "]", 99); throw new LockedException("该账号已冻结"); } // 角色集合 Set<GrantedAuthority> authorities = new HashSet<>(); // 查询用户角色 List<SysRole> sysRoleList = sysUserService.selectSysRoleByUserId(userInfo.getUserId()); for (SysRole sysRole : sysRoleList) { authorities.add(new SimpleGrantedAuthority(sysRole.getRoleKey())); } userInfo.setAuthorities(authorities); ThirdPartyAuthenticationToken authenticationResult = new ThirdPartyAuthenticationToken(userInfo, userInfo.getUserId(), userInfo.getAuthorities()); authenticationResult.setDetails(token.getDetails()); return authenticationResult; } /** * 账号注册 **/ public SelfUserEntity doRegister(AuthUser authUser) { SelfUserEntity selfUser = new SelfUserEntity(); SysUser sysUser = new SysUser(); sysUser.setNickName(authUser.getNickname()); sysUser.setUsername(authUser.getSource() + (random.nextInt(89999999) + 10000000)); String password = String.valueOf(random.nextInt(899999) + 100000); sysUser.setPassword(bCryptPasswordEncoder.encode(password)); sysUser.setAvatar(authUser.getAvatar()); sysUser.setRegistrationType(authUser.getSource()); sysUser.setUuid(authUser.getUuid()); sysUser.setLoginCount(0); sysUser.setIpSource(IPUtils.getClientIp(Objects.requireNonNull(ServletUtils.getRequest()))); // 2-男 sysUser.setSex("2".equals(authUser.getRawUserInfo().getString("gender_type")) ? "0" : "1"); sysUser.setCreateUser("system"); sysUser.setRemark(authUser.getSource() + "首次注册默认密码为:" + password); sysUser.setLoginDate(new Date()); sysUser.setUserType("2"); sysUser.insert(); // 新增用户角色关系 SysUserRole sysUserRole = new SysUserRole(); sysUserRole.setUserId(sysUser.getUserId()); // 游客 sysUserRole.setRoleId("2"); sysUserRole.insert(); BeanUtils.copyProperties(sysUser, selfUser); selfUser.setRelName(sysUser.getNickName()); LogUtil.saveNoLoginLog("账号注册(" + authUser.getSource() + ")", JSONObject.toJSONString(sysUser), OperType.REGISTRATION.ordinal()); return selfUser; } /** * 判断是上面 authenticate 方法的 authentication 参数,是哪种类型 * Authentication 是个接口,实现类有很多,目前我们最熟悉的就是 ThirdPartyAuthenticationToken、UsernamePasswordAuthenticationToken * 很明显,我们只支持 ThirdPartyAuthenticationToken,因为它封装的是TOKEN OPENID * * @param authentication * @return */ @Override public boolean supports(Class<?> authentication) { return (ThirdPartyAuthenticationToken.class.isAssignableFrom(authentication)); } }
工具类
import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.request.AuthGiteeRequest; import me.zhyd.oauth.request.AuthQqRequest; import me.zhyd.oauth.request.AuthRequest; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Slf4j @Component public class AuthUtil { @Value("${justauth.qq.clientId}") private String qqClientId; @Value("${justauth.qq.clientSecret}") private String qqClientSecret; @Value("${justauth.qq.redirectUri}") private String qqRedirectUri; @Value("${justauth.gitee.clientId}") private String giteeClientId; @Value("${justauth.gitee.clientSecret}") private String giteeClientSecret; @Value("${justauth.gitee.redirectUri}") private String giteeRedirectUri; /** * 鉴权 */ public AuthRequest getAuthRequest(String source) { AuthRequest authRequest = null; switch (source) { case "qq": authRequest = new AuthQqRequest(AuthConfig.builder() .clientId(qqClientId) .clientSecret(qqClientSecret) .redirectUri(qqRedirectUri) .build()); break; case "gitee": authRequest = new AuthGiteeRequest(AuthConfig.builder() .clientId(giteeClientId) .clientSecret(giteeClientSecret) .redirectUri(giteeRedirectUri) .build()); break; default: break; } return authRequest; } }
# 第三方登录秘钥
justauth:
qq:
clientId: xxxxxxxxxx
clientSecret: xxxxxxxxxxxx
redirectUri: http://xxxxxxxxxx/api/login/callback/qq
gitee:
clientId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
clientSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
redirectUri: http://xxxxxxxxxx/api/login/callback/gitee