inputtype

背景

最近开发了一个新产品,然后老板说我们现在系统太多了,每次都要切换系统登录,太麻烦了。可以优化一下,换同一个账号吗?作为一个资深的建筑狮子,老板的要求一定要满足和安排!

当一个公司的产品矩阵丰富时,用户在不同系统之间来回切换,当然产品用户体验差,增加了用户密码管理的成本。

内部流量没有很好的用来打通用户,每个产品的独立系统会导致产品安全性的下降。

因此,集团产品单点登录的实现对用户体验和效率提升有很大的帮助。那么如何实现统一认证呢?我们先来看看传统的认证方式。

传统会话机制和身份认证方案

| Cookie 与服务器的交互

inputtype

众所周知,http是一种无状态协议,所以每次客户通过浏览器访问web。

当页面被请求到服务器时,服务器会创建一个新的线程并打开一个新的会话,服务器不会自动维护客户的上下文信息。

例如,如果我们想在电子商务中实现购物车功能,我们如何知道哪些购物车请求对应于来自同一个客户的请求?

因此,出现了会话的概念。会话是一种保存上下文信息的机制。它是面向用户的,每个SessionID对应一个用户,保存在服务器中。

会话主要是在cookie或URL重写的基础上实现的。默认使用cookie,系统会创建一个名为JSESSIONID的变量,并输出到cookie中。

JsSessionid存储在浏览器的内存中,而不是写在硬盘上。如果我们禁止浏览器的cookie,web服务器会通过URL重写的方式传递sessionid,我们可以在地址栏看到sessionid=KWJHUG6JJM65HS2K6的字符串。

通常,JSESSIONID不能跨窗口使用。当你打开一个新的浏览器窗口,进入同一个页面,系统会给你一个新的sessionid,这样我们信息共享的目的就达不到了。

| 服务器端的 session 的机制

服务器收到客户端的请求时,首先判断请求中是否包含JSESSIONID的sessionId。如果有已经创建的描述,则直接从内存中取出使用。如果无法查询,则说明无效。

如果客户端请求不包含sessionid,则为该客户端创建一个会话,并生成一个与该会话相关联的sessionid,该会话id将返回给客户端,以便保存在该响应中。

对于每个http请求,它都经过以下步骤:

服务端首先查找对应的 cookie 的值(sessionid)。根据 sessionid,从服务器端 session 存储中获取对应 id 的 session 数据,进行返回。如果找不到 sessionid,服务器端就创建 session,生成 sessionid 对应的 cookie,写入到响应头中。

会话由服务器生成,并以哈希表的形式存储在内存中。

| 基于 session 的身份认证流程

基于视觉的认证的主要过程如下:

因为http请求是无状态的,所以在Web领域大部分都是这样解决的。但是这样做有什么错呢?我们继续看。

集群环境下的会话困境及解决方案

随着技术的发展和用户流量的增加,单一的服务器已经不能满足系统的需求,分布式架构已经成为流行。

通常情况下,系统会部署在多台服务器上,通过负载均衡将请求分布到其中一台服务器上,因此很可能同一用户的请求会分布到不同的服务器上。

因为会话是保存在服务器上的,很有可能是第一次请求访问的A服务器创建了会话,但是第二次访问了B服务器,然后就无法获取会话了。

我们知道,Session一般用于存储会话的全局用户信息(不仅仅是登录问题)以及简化/加速后续的业务请求。

传统会话由服务器生成并存储。当应用部署在分布式集群中时,如何保证不同服务器上的会话信息可以共享?

| Session 共享方案

会话共享一般有两种思路:

session 复制session 集中存储①session 复制

会话复制意味着复制不同服务器上的会话数据。当用户登录、修改和注销时,会话信息也被复制到其他机器。

这种实现的问题是实现成本高,维护困难,登录延迟。

②session 集中存储

集中存储是将获取的会话存储在单个服务中,所有获取的会话都统一在这个服务中。

这避免了同步和维护多个会话的问题。通常,我们使用redis进行集中存储会话。

多服务下的登陆困境和单点登录方案

| SSO 的产生背景

企业做大了,一般会有很多业务支撑系统来提供相应的管理和IT服务。按照传统的认证方式,每个单独的系统都会有自己的安全系统和身份认证系统。

要进入每个系统,需要登录,获取一个会话,然后通过会话访问相应的系统资源。

这种情况不仅给管理层带来很大的困难,而且对客户也极其不友好。那么如何让客户只登录多个系统一次,而无需再次登录呢?

“单点登录”就是为了解决这类问题而设计的。大致思路流程如下:系统之间的用户信息通过一张票串联起来。

| SSO 的底层原理 CAS①CAS 实现单点登录流程

我们知道,对于域名完全不同的系统,cookie是不能跨域名共享的,所以会话id是不能在页面端共享的。所以要实现单店登录,需要启用一个专门用于登录的域名,比如(ouath.com),为所有系统提供会话id。

业务系统开通后,借助授权系统登录。整个过程如下:

当 b.com 打开时,发现自己未登陆,于是跳转到 ouath.com 去登陆ouath.com 登陆页面被打开,用户输入帐户/密码登陆成功ouath.com 登陆成功,种 cookie 到 ouath.com 域名下把 sessionid 放入后台 redis,存放<ticket,sesssionid>数据结构,然后页面重定向到 A 系统当 b.com 重新被打开,发现仍然是未登陆,但是有了一个 ticket 值当 b.com 用 ticket 值,到 redis 里查到 sessionid,并做 session 同步,然后种 cookie 给自己,页面原地重定向当 b.com 打开自己页面,此时有了 cookie,后台校验登陆状态,成功

整个交互流程图如下:

②单点登录流程演示CAS 登录服务 demo 核心代码如下:

用户实体类:

public类UserForm实现Serializable {
private static final long serialVersionUID = 1L;

私有字符串用户名;
私有字符串密码;
私有字符串backurl

公共字符串get username(){
返回用户名;
}

public void set username(String username){
this . username = username;
}

public String get password(){
返回密码;
}

public void set password(String password){
this . password = password;
}

public String getBackurl(){
return backurl;
}

public void setBackurl(String backurl){
this . backurl = backurl;
}

}

登录控制器:

@ Controller
public class index Controller {
@ Autowired
private redis template redis template;

@ get mapping(& # 34;/toLogin & # 34;)
public String to log in(Model Model,http servlet request request){
Object userInfo = request . getsession()。getAttribute(LoginFilter。USER _ INFO);
//如果不是空,则登录
if (null!= userInfo){
String ticket = uuid . random uuid()。toString();
redis template . ops for value()。set(ticket,userInfo,2,TimeUnit。秒);
return & # 34;重定向:& # 34;+request . getparameter(& # 34;网址& # 34;)+"?ticket = & # 34+票;
}
UserForm user = new UserForm();
user . set username(& # 34;老王& # 34;);
user . set password(& # 34;老王& # 34;);
user . setbackurl(request . getparameter(& # 34;网址& # 34;));
model . add attribute(& # 34;用户& # 34;,用户);

return & # 34;登录& # 34;;
}

@ post mapping(& # 34;/log in & # 34;)
public void登录(@ModelAttribute UserForm用户,HttpServletRequest请求,HttpServletResponse响应)抛出IOException,servlet exception {
system . out . println(& # 34;backurl:& # 34;+user . getbackurl());
request.getSession()。setAttribute(LoginFilter。USER_INFO,用户);

/登录成功,创建用户信息票
string ticket = uuid . random uuid()。tostring();
redis template . ops for value()。set(ticket,user,20,TimeUnit。秒);
//重定向,返回原URL-a.com
if(null = = user . getbackurl()| | | user . getbackurl()。length()= = 0){
response . redirect(& # 34/index & # 34;);
} else {
response . send redirect(user . getbackurl()+& # 34;?ticket = & # 34+票);
}
}

@ get mapping(& # 34;/index & # 34;)
public modeland view index(http servlet request request){
modeland view modeland view = new modeland view();
Object user = request . getsession()。getAttribute(LoginFilter。USER _ INFO);
UserForm userInfo =(UserForm)user;
modeland view . set viewname(& # 34;索引& # 34;);
modeland view . add object(& # 34;用户& # 34;,userInfo);
request.getSession()。set attribute(& # 34;测试& # 34;,"123");
返回modelAndView
}
}

登录过滤器:

public class LoginFilter实现Filter {
public static final String USER _ INFO = & # 34;用户& # 34;;
@ Override
public void init(filter config filter config)throws servlet exception {

}

public void do filter(servlet request servlet request,
servlet response servlet response,filter chain)
throws io exception {

http servlet request request =(http servvl
http servlet response response =(http servlet response)servlet response;

Object userInfo = request . getsession()。get attribute(USER _ INFO);;

/如果没有登录,拒绝请求,转到登录页面
String request URL = request . getservletpath();
如果(!"/toLogin & # 34;。equals(requestUrl)//不是登录页
&&!request URL . starts with(& # 34;/log in & # 34;)//未登录
& null = = userinfo){//未登录

请求。getRequestDispatcher(& # 34;/toLogin & # 34;).转发(请求、响应);
退货;
}

filter chain . do filter(request,servlet response);
}

@ Override
public void destroy(){

}
}

配置过滤器:

@ configuration
public class log information {

/Configure filter生效
@ bean
public filter registration bean session filter registration(){

FilterRegistrationBean registration = new FilterRegistrationBean();
registration . set filter(new log in filter());
registration . addurl patterns(& # 34;/*");
registration . addinitparameter(& # 34;paramName & # 34, "paramValue & # 34);
registration . setname(& # 34;sessionFilter & # 34);
registration . set order(1);
返回注册;
}
}

登录页面:

& lt!DOCTYPE HTML & gt
& lt;html xmlns:th = & # 34;http://www . thyme leaf . org & # 34;& gt
& lt;head & gt
& lt;title & gt享受登录& lt/title & gt;
& lt;meta http-equiv = & # 34;内容类型& # 34;内容= & # 34;文本/html;charset = UTF-8 & # 34;/& gt;
& lt;/head & gt;
& lt;body & gt
& lt;div text-align = & # 34;中心& # 34;& gt
& lt;h1 & gt请登录

web 系统 demo 核心代码如下:

过滤器:

public类SSOFilter实现Filter {
private redis template redis template;

public static final String USER _ INFO = & # 34;用户& # 34;;

public SSO filter(redis template redis template){
this . redis template = redis template;
}
@ Override
public void init(filter config filter config)抛出servlet exception {

}

public void do filter(servlet request servlet request,
servlet response servlet response,filter chain)
抛出IOException,servlet exception {

http servlet request请求
http servlet response response =(http servlet response)servlet response;

Object userInfo = request . getsession()。get attribute(USER _ INFO);;

/如果没有登录,拒绝请求,转到登录页面
String request URL = request . getservletpath();
如果(!"/toLogin & # 34;。equals(requestUrl)//不是登录页
&&!request URL . starts with(& # 34;/log in & # 34;)//未登录
& null = = userinfo){//未登录

String ticket = request . getparameter(& # 34;门票& # 34;);
//如果有票,使用票尝试获取用户信息
if (null!= ticket){
userInfo = redis template . ops forvalue()。get(票);
}
/如果无法获取用户信息,请前往登录页面
If(null = = userinfo){
response . redirect(& # 34;http://127.0.0.1:8080/toLogin?url = & # 34+request.getRequestURL()。toString());
退货;
}

*将用户信息加载到会话中
*/
userformuser =(userform)userinfo;
request.getSession()。setAttribute(SSOFilter。USER_INFO,用户);
redis template . delete(ticket);
}

filter chain . do filter(request,servlet response);
}

@ Override
public void destroy(){

}
}

控制器:

@ Controller
public class index Controller {
@ Autowired
private redis template redis template;

@ get mapping(& # 34;/index & # 34;)
public modeland view index(http servlet request request){
modeland view modeland view = new modeland view();
Object userInfo = request . getsession()。getAttribute(SSOFilter。USER _ INFO);
UserForm user =(UserForm)userInfo;
modeland view . set viewname(& # 34;索引& # 34;);
modeland view . add object(& # 34;用户& # 34;,用户);

request.getSession()。set attribute(& # 34;测试& # 34;,"123");
返回modelAndView
}
}

主页:

& lt!DOCTYPE HTML & gt
& lt;html xmlns:th = & # 34;http://www . thyme leaf . org & # 34;& gt
& lt;head & gt
& lt;title & gt享受指数& lt/title & gt;
& lt;meta http-equiv = & # 34;内容类型& # 34;内容= & # 34;文本/html;charset = UTF-8 & # 34;/& gt;
& lt;/head & gt;
& lt;body & gt
& lt;div th:object = & # 34;$ { user } & # 34& gt
& lt;h1 & gt中科院网站:欢迎& # 34;& gt& lt/h1 & gt;
& lt;/div & gt;
& lt;/body & gt;
& lt;/html & gt;

③CAS 的单点登录和 OAuth2 的区别

OAuth2:三方授权协议,允许用户通过受信任的应用程序进行授权,而无需提供帐户密码,这样他们的客户端就可以访问他们权限内的资源。

CAS:Central authentic ation Service,基于Kerberos ticket的SSO框架,为Web应用系统(属于Web SSO)提供可靠的单点登录解决方案。

保证CAS单点登录时客户端用户资源的安全;OAuth2是为了保证服务器上用户资源的安全。

CAS客户端希望获得的最终信息是该用户是否有权访问我的(CAS客户端的)资源;OAuth2获得的最终信息是我的用户(oauth2服务提供者)的资源是否可以被你(oauth2客户端)访问。

所以身份认证需要统一的账号密码,CAS被使用;我们需要授权第三方服务使用我们的资源,使用OAuth2。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论