win系统做网站,设计工作网站,北京注册公司流程,wordpress 会员登录分布式微服务系统主流常用的登录方案
前言: 单点登录其实是一个概念,主要是为了解决一次登录,多系统(本系统或外部系统)之间不需要重复登录的问题,就目前来说,主流的解决方案针对业务场景分为3个方向:
1: 同一公司,同父域下的单点登录解决方案.
如[http://map.baidu.com][[h…分布式微服务系统主流常用的登录方案
前言: 单点登录其实是一个概念,主要是为了解决一次登录,多系统(本系统或外部系统)之间不需要重复登录的问题,就目前来说,主流的解决方案针对业务场景分为3个方向:
1: 同一公司,同父域下的单点登录解决方案.
如[http://map.baidu.com][[http://www.baidu.com][[http://image.baidu.com] 基于cookie开源项目代表: JWT;会详细介绍和实现;
2: 同一公司,不同域下的单点登录解决方案.
如[[http://www.taobao.com] [[http://www.tmall.com]]
基于中央认证服务器开源项目代表:CAS(https://github.com/apereo/cas); https://www.apereo.org/projects/cas
3: 不同公司之间,不同域下的 第三方登录功能实现.
如第三方网站支持qq登录,微信登录,微博登录等;
基于OAuth2.0协议各大公司自己的支持;
一: 基于JWT的单点登录解决方案:
首先我们看一下图形,这个就是基于JWT解决方案的单点登录解决方案; )
应用和原理 : 什么是JWT什么业务场景使用JWT
JWT JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. JSONWeb令牌是一种开放的、行业标准的RFC7519方法用于在双方之间安全地表示声明。
JWT 应用场景
Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录后续每个请求都将包含JWT允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性因为它的开销很小并且可以轻松地跨域使用JWT 省去了服务器存储用户信息的过程。
Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名例如用公钥/私钥对你可以确定发送人就是它们所说的那个人。另外由于签名是使用头和有效负载计算的您还可以验证内容没有被篡改。
JWT的令牌结构 看官方文档https://jwt.io/introduction 并解释
JSON Web Token由三部分组成它们之间用圆点(.)连接。这三部分分别是HeaderPayloadSignature。
官网对于这个三部分的定义。一个典型的JWT Token是 tttttttt.yyyyyyyyyyy.eeeeeee 这样组成。 Header : 由两部分组成token的类型“JWT”和算法名称比如HMAC SHA256或者RSA等等。然后用Base64对这个JSON编码就得到JWT的第一部分。
Payload 部分也是一个 JSON 对象用来存放实际需要传递的数据。分为三种类型
1registered claims。标准声明。一般常用于校验的有 iatexpnbf 校验 token 是否过期。
iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间这个过期时间必须要大于签发时间nbf: 定义在什么时间之前该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识主要用来作为一次性token,从而回避重放攻击
2public claims。公开声明。 可以存放任意信息比如userid等。不是信息安全的。
3private claims。私密声明。 双方约定的信息。
Signature Base64(header).Base64(payload) head中定义的算法 密钥 生成一个字符串 。
使用方式
客户端收到服务器返回的 JWT可以储存在 Cookie 里面也可以储存在 localStorage。
此后客户端每次与服务器通信都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送但是这样不能跨域所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
*// Authorization: Bearer *
另一种做法是跨域的时候JWT 就放在 POST 请求的数据体里面。
通过代码实现JWT之单点登录(基于springbootdubbozookeeper)
代码实现JWT服务器(基于dubbo实现):
JWTService:
package com.qianfeng.user;import com.qianfeng.user.dto.UserRequest;
import com.qianfeng.user.dto.UserResponse;/*** \* 用户服务对外接口* p* \* author Martin*/
public interface IUserService {/*** \* 用户登录接口* \* param request user info* \* return 查询到的结果*/UserResponse login(UserRequest request);
}JWT ServiceImpl:
package com.qianfeng.user;import com.qianfeng.user.dao.entity.UserEntity;
import com.qianfeng.user.dao.mapper.UserMapper;
import com.qianfeng.user.dto.UserRequest;
import com.qianfeng.user.dto.UserResponse;
import com.qianfeng.user.util.JWTTokenUtil;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;/*** \* 用户业务实现类* \* author Martin*/
Service(userServiceImpl)
public class UserServiceImpl implements IUserService {undefinedAutowiredprivate UserMapper userMapper;/*** \* 用户登录方法实现* \* param request 请求参数* \* return 登录结果*/public UserResponse login(UserRequest request) {undefined//非空校验UserResponse ur verification(request);if (null ! ur) {undefinedreturn ur;}UserEntity ue new UserEntity();ue.setUserName(request.getUserName());ue.setUserPass(request.getUserPass());//执行登录UserEntity user userMapper.login(ue);ur new UserResponse();if (null user) { //登录失败ur.setCode(ErrorCode.CODE_LOGIN_FAIL);ur.setMsg(ErrorCode.MSG_LOGIN_FAIL);return ur;}//获取tokenMapString, Object map new HashMap();map.put(uid, user.getUserID());map.put(exp, DateTime.now().plusSeconds(30).toDate().getTime() / 1000); //设置过期时间 当前时间的基础上加上30sString token JWTTokenUtil.generatorToken(map); //通过封装的算法来获取tokenur.setToken(token);ur.setUid(user.getUserID());//将一些用户信息返回,可以封装一个通用类return ur;}//非空校验private UserResponse verification(UserRequest request) {undefinedUserResponse ur null;//请求为空if (null request) {undefinedur new UserResponse();ur.setCode(ErrorCode.CODE_ERROR_SYSTEM);ur.setMsg(ErrorCode.MSG_ERROR_SYSTEM);return ur;}//用户名为空if (StringUtils.isEmpty(request.getUserName())) {undefinedur new UserResponse();ur.setCode(ErrorCode.CODE_USER_EMPTY);ur.setMsg(ErrorCode.MSG_USER_EMPTY);return ur;}//密码为空if (StringUtils.isEmpty(request.getUserPass())) {undefinedur new UserResponse();ur.setCode(ErrorCode.CODE_PASS_EMPTY);ur.setMsg(ErrorCode.MSG_PASS_EMPTY);return ur;}return ur;}
}JWT Util:
package com.qianfeng.user.util;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Map;/*** \* JWT 生成token和提取token信息工具类* \* author Martin*/
public class JWTTokenUtil {undefinedstatic SignatureAlgorithm sa SignatureAlgorithm.HS256;//使用hs256算法//获取keyprivate static Key generatorKey() {undefinedbyte[] bin DatatypeConverter.parseBase64Binary(2521afcf18c749c1a8a7615c03d15e43);Key key new SecretKeySpec(bin, sa.getJcaName());return key;}/*** \* 将传过来的信息按照 HeaderPayloadSignature 的方式组装一个字符串* \* param payLoad* \* return*/public static String generatorToken(MapString, Object payLoad) {undefinedObjectMapper objectMapper new ObjectMapper();try {undefinedreturn Jwts.builder().setPayload(objectMapper.writeValueAsString(payLoad)).signWith(sa, generatorKey()).compact();} catch (JsonProcessingException e) {undefinede.printStackTrace();}return null;}/*** \* 将token解析得到Payload内容* \* param token* \* return*/public static Claims phaseToken(String token) {undefined//将token解析成claimsJwsClaims jws Jwts.parser().setSigningKey(generatorKey()).parseClaimsJws(token);// jws.getHeader(); Header
// jws.getBody(); Payload
// jws.getSignature(); Signaturereturn jws.getBody();}}代码实现JWT客户端请求:
tocken校验拦截器:
package com.qianfeng.conf.interceptor;import com.qianfeng.user.anno.Evade;
import com.qianfeng.user.util.CookieUtil;
import com.qianfeng.user.util.JWTTokenUtil;
import com.qianfeng.user.web.BaseController;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;/*** \* 拦截器如果没有token直接返回结果。* \* author Martin*/
public class TokenInterceptor implements HandlerInterceptor {undefinedOverridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {undefinedif (handler instanceof ResourceHttpRequestHandler || handler instanceof AbstractErrorController) {undefinedreturn true;}System.out.println(start);HandlerMethod handlerMethod (HandlerMethod) handler;//如果需要规避if (hasEvade(handlerMethod)) {undefinedSystem.out.println(evade handlerMethod.toString());return true;}//从cookie中获取tokenString access_token CookieUtil.getCookieValue(request, access_token);//如果没有token跳转到登录页面这里需要判断一下是否是ajax请求if (StringUtils.isEmpty(access_token)) {undefinedSystem.out.println(token is null);return vafail(request, response);}Claims claims null;try {undefinedclaims JWTTokenUtil.phaseToken(access_token); //解析token} catch (ExpiredJwtException e) { //token已经过期System.out.println(token out time);return vafail(request, response);} catch (SignatureException e1) { //签名校验失败System.out.println(token signature fail);return vafail(request, response);}if (null ! claims !StringUtils.isEmpty(claims.get(uid).toString())) {undefinedSystem.out.println(token success);//获取id并将id设置到Controllerif (handlerMethod.getBean() instanceof BaseController) {undefinedBaseController bean (BaseController) handlerMethod.getBean();bean.setUserID(claims.get(uid).toString());}return true;}System.out.println(end);return false;}private boolean vafail(HttpServletRequest request, HttpServletResponse response) throws Exception {undefinedif (CookieUtil.isAjax(request)) {undefinedresponse.getWriter().println({result:6666,msg:no token});return false;}response.sendRedirect(login);return false;}/*** \* 验证是否可以规避不需要拦截* \* param handlerMethod* \* return*/private boolean hasEvade(HandlerMethod handlerMethod) {undefinedMethod method handlerMethod.getMethod();return method.getAnnotation(Evade.class) ! null; //判断方法上面是否有自定义的规避注解。}Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {undefined
// TODO}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {undefined
// TODO}
}LoginController登录实现代码:
package com.qianfeng.user.web;import com.alibaba.dubbo.config.annotation.Reference;
import com.qianfeng.user.anno.Evade;
import com.qianfeng.user.dto.UserRequest;
import com.qianfeng.user.dto.UserResponse;
import com.qianfeng.user.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** \* 登录* \* author Martin*/
Controller
public class LoginController extends BaseController {undefinedReference //通过dubbo注入实现类IUserService userService;GetMapping(/login)Evade //自定义注解,配置该注解不拦截请求;public String jumpLogin() {undefinedreturn login;}RequestMapping(value /dologin)Evadepublic String doLogin(UserRequest ur, HttpServletRequest request, HttpServletResponse response) {undefinedUserRequest res new UserRequest(ur.getUserName(), ur.getUserPass());if (null userService) { //dubbo服务不可用return error;}UserResponse rp userService.login(res); //通过dubbo调用用户服务进行登录//登录失败跳转到登录页面if (null response || StringUtils.isEmpty(rp.getUid())) {undefinedreturn login;}//按照以前如果登录成功是需要将用户信息保存到session中。request.getSession().setAttribute(user, rp);request.setAttribute(uid, rp.getUid());//使用token的方式将信息保存到客户端 access_token : 注意这里在获取的时候也需要这个值
//response.addHeader(Set-Cookie,access_tokenrp.getToken();Path/;HttpOnly); //根目录的 cookie 设置tokenreturn index;}//用来测试是否可以直接访问资源RequestMapping(value /buy)public String buy(HttpServletRequest request) {//从Session中获取用户信息UserResponse ur (UserResponse) request.getSession().getAttribute(user);System.out.println(Session中 /buy : id ur.getUid());// System.out.println(Token中/buy : id getUserID());
// request.setAttribute(uid,getUserID());return info;}}Evade 自定义注解配置不需要验证的请求:
package com.qianfeng.user.anno;import java.lang.annotation.*;/*** \* 自定义注解该注解可以设置不拦截处理* \* author Martin*/
Documented
Inherited
Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
public interface Evade {}以上为主要实现代码
二: 基于CAS同公司不同域的单点登录解决方案
Yelu大学研发的CAS单点登录原理: !
主要步骤分为:
1:用户请求CAS Client的某一个网站,或者直接请求CAS Server登录页面;
2:用户到CAS Server的登录页面进行验证,验证成功,CAS Server返回ticket到客户端;
3:客户端再次请求CAS Client资源, CAS Client携带ticket到CAS Server认证,成功则继续访问;
实现步骤:
1: 搭建CAS Server端,可使用CAS的框架实现;可自定义登录页面;实现其内部接口即可;
2: 搭建CAS Client端,就是公司不同的服务器系统,携带秘钥即可;
三: 基于Auth2.0之第三方登录
OAuth 2.0是用于授权的行业标准协议。 OAuth 2.0 is the industry-standard protocol for authorization。 OAuth定义了四个角色: 资源所有者 能够授予对受保护资源的访问权限的实体。 当资源所有者是一个人时它被称为 最终用户。 资源服务器 托管受保护资源的服务器能够接受 并使用访问令牌响应受保护的资源请求。 客户 代表受保护的资源请求的应用程序 资源所有者及其授权。“客户”一词的确如此 并不意味着任何特定的实施特征例如 应用程序是在服务器桌面还是其他服务器上执行 设备。 授权服务器 服务器成功后向客户端发出访问令牌 验证资源所有者并获得授权。
授权模式
1 简化模式Implicit
2 授权码模式Authorization Code
3 密码模式Resource Owner Password Credentials Grant
4 客户端模式Client Credentials
场景: 公司的网站支持第三方登录(qq登录,微信登录,微博登录):
qq开放平台:https://connect.qq.com/index.html
微博开放平台: https://open.weibo.com/authentication/
接入方式按照对应的平台操作即可,第三方网站接入文档提示非常全面.
关于登录,还请题主需要关注一下 分布式下登录成功以后,分布式Session的问题以及解决方案;
方案是用来解决问题的这些方案没有好不好的说法只有合不合适的说法合适的才是最好的。