单系统分密码登录与免密码登录(手机验证码,邮箱登录,微信oauth2接入,其他系统单点过来),用多realm实现
单系统无需登录接入前端系统(移动端,小程序,大屏等),用oauth2认证获取jwt无状态token,进行调用接口
多系统互相跳转实现单点,用oltu框架oauth2协议实现(同一)
单点流程梳理:
方案一(参考xboot):
username:站点1的clientId
,返回站点1的access_tokenusername:站点2的clientId
,返回站点2的access_tokenusername
开头的键值对即可/xboot/oauth2/logout
accessToken
,通常为信任的内部站点中使用,将删除失效当前用户登录的accessToken
以及当前用户授权第三方应用的access_token
方案二(自定义系统)oauth2 授权码模式:
package com.ruoyi.web.controller.system;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.CookieUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.shiro.auth.LoginType;
import com.ruoyi.framework.shiro.auth.UserToken;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 单点登录验证
*
* @author ruoyi
*/
@Controller
@RequestMapping("/sso-client")
public class SysSSOLoginController {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${sso.client.clientId}")
private String clientId;
@Value("${sso.client.clientSecret}")
private String clientSecret;
@Value("${sso.client.accessTokenUrl}")
private String accessTokenUrl;
@Value("${sso.client.verifyTokenUrl}")
private String verifyTokenUrl;
@Value("${sso.client.authorizeUrl}")
private String authorizeUrl;
@Value("${sso.client.redirectUrl}")
private String redirectUrl;
@Value("${sso.client.responseType}")
private String responseType;
/**
* 单点登录入口
*
* @param request
* @return
*/
@GetMapping("/auth")
public String ssoAuth(HttpServletRequest request) {
String ssoAccessToken = CookieUtils.getCookie(request, "sso_access_token");
if (StringUtils.isNotEmpty(ssoAccessToken)) {
//已存在,去验证token获取用户信息直接登录
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("accessToken", ssoAccessToken);
String result = HttpUtil.post(verifyTokenUrl, paramMap);
logger.info("==> 验证ssoAccessToken返回值: " + result);
if (StringUtils.isNotEmpty(result)) {
JSONObject jsonObject = JSON.parseObject(result);
if ("200".equals(jsonObject.getString("code"))) {
String loginName = jsonObject.getJSONObject("data").getString("username");
try {
login(loginName);
return "redirect:/index";
} catch (AuthenticationException e) {
return "error/unauth";
}
}
}
}
//不存在则获取单点授权码
//配置请求参数,构建oauth2的请求。设置请求服务地址(authorizeUrl)、clientId、response_type、redirectUrl
String requestUrl = null;
try {
OAuthClientRequest accessTokenRequest = OAuthClientRequest.authorizationLocation(authorizeUrl)
.setClientId(clientId)
.setResponseType(responseType)
.setRedirectURI(redirectUrl)
.buildQueryMessage();
requestUrl = accessTokenRequest.getLocationUri();
} catch (OAuthSystemException e) {
e.printStackTrace();
}
logger.info("==> 客户端重定向到服务端获取auth_code: " + requestUrl);
return "redirect:" + requestUrl;
}
/**
* 单点登录回调
*
* @param request
* @param response
* @return
*/
@RequestMapping("/callback")
public String callback(HttpServletRequest request, HttpServletResponse response) throws OAuthProblemException, OAuthSystemException {
String code = request.getParameter("code");
logger.info("==> 服务端回调,获取的code:" + code);
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthClientRequest accessTokenRequest = null;
try {
accessTokenRequest = OAuthClientRequest
.tokenLocation(accessTokenUrl)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId(clientId)
.setClientSecret(clientSecret)
.setCode(code)
.setRedirectURI(redirectUrl)
.buildQueryMessage();
} catch (OAuthSystemException e) {
e.printStackTrace();
}
//去服务端请求access token,并返回响应
OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
logger.info("==> 客户端根据 code值 " + code + " 到服务端获取的access_token为:" + JSON.toJSONString(oAuthResponse.getBody()));
if (StringUtils.isNotEmpty(oAuthResponse.getAccessToken())) {
//保存access_token到cookies
CookieUtils.setCookie(response, "sso_access_token", oAuthResponse.getAccessToken());
String username = JSON.parseObject(oAuthResponse.getParam("userInfo")).getString("loginName");
UserToken token = new UserToken(username, LoginType.SSO);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return "redirect:/index";
} catch (AuthenticationException e) {
return "error/unauth";
}
}
return "error/unauth";
}
/**
* shiro登录
*
* @param username
* @throws AuthenticationException
*/
private void login(String username) {
UserToken token = new UserToken(username, LoginType.SSO);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
}
}
1.用户在前端页面发起微信登录
2.后端根据clientId和clientSecret以及自定义第三方来源标识组装授权url,让前端重定向该url
3.用户确定授权,第三方回调到后端接口
4.后端获取用户标识与用户信息存表或与系统用户表关联
5.完成认证颁发token
1.用户在订阅号发送验证码关键字
2.后端接收到用户标识和用户信息,并存表,生成验证码(会过期)与用户绑定
3.用户在前端输入验证码登录
4.完成认证颁发token
1.用户在前端输入手机号获取验证码
2.用户输入验证码登录
3.完成认证颁发token
1.在单点认证中心通过认证
2.进入系统直接颁发token
1.用户在移动端输入账号密码
2.完成认证颁发jwt token