编写你自己的单点登录(SSO)服务

编写你自己的单点登录(SSO)服务

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

王昱
yuwang881@gmail.com  
博客地址
http://yuwang881.blog.sohu.com

摘要
:单点登录(
SSO
)的技术被越来越广泛地运用到各个领域的软件系统其中。本文从业务的角度分析了单点登录的需求和应用领域;从技术本身的角度分析了单点登录技术的内部机制和实现手段,而且给出
Web-SSO
和桌面
SSO
的实现、源码和具体解说;还从安全和性能的角度对现有的实现技术进行进一步分析,指出对应的风险和须要改进的方面。本文除了从多个方面和角度给出了对单点登录(
SSO
)的全面分析,还而且讨论了怎样将现有的应用和
SSO
服务结合起来,可以帮助应用架构师和系统分析人员从本质上认识单点登录,从而更好地设计出符合须要的安全架构。
keyword

SSO, Java, J2EE, JAAS
1
什么是单点登陆
单点登录(
Single Sign On
),简称为
SSO
,是眼下比較流行的企业业务整合的解决方式之中的一个。
SSO
的定义是在多个应用系统中,用户仅仅须要登录一次就能够訪问全部相互信任的应用系统。
较大的企业内部,一般都有非常多的业务支持系统为其提供对应的管理和
IT
服务。比如財务系统为財务人员提供財务的管理、计算和报表服务;人事系统为人事部门提供全公司人员的维护服务;各种业务系统为公司内部不同的业务提供不同的服务等等。这些系统的目的都是让计算机来进行复杂繁琐的计算工作,来替代人力的手工劳动,提高工作效率和质量。这些不同的系统往往是在不同的时期建设起来的,执行在不同的平台上;或许是由不同厂商开发,使用了各种不同的技术和标准。假设举例说国内一著名的
IT
公司(名字隐去),内部共同拥有
60
多个业务系统,这些系统包含两个不同版本号的
SAP

ERP
系统,
12
个不同类型和版本号的数据库系统,
8
个不同类型和版本号的操作系统,以及使用了
3
种不同的防火墙技术,还有数十种互相不能兼容的协议和标准,你相信吗?不要怀疑,这样的情况事实上非常普遍。每个应用系统在执行了数年以后,都会成为不可替换的企业
IT
架构的一部分,例如以下图所看到的。
编写你自己的单点登录(SSO)服务
随着企业的发展,业务系统的数量在不断的添加�,老的系统却不能轻易的替换,这会带来非常多的开销。其一是管理上的开销,须要维护的系统越来越多。非常多系统的数据是相互冗余和反复的,数据的不一致性会给管理工作带来非常大的压力。业务和业务之间的相关性也越来越大,比如公司的计费系统和財务系统,財务系统和人事系统之间都不可避免的有着密切的关系。
为了减少管理的消耗,最大限度的重用已有投资的系统,非常多企业都在进行着企业应用集成(
EAI
)。企业应用集成能够在不同层面上进行:比如在数据存储层面上的“数据大集中”,在传输层面上的“通用数据交换平台”,在应用层面上的“业务流程整合”,和用户界面上的“通用企业门户”等等。其实,还用一个层面上的集成变得越来越重要,那就是“身份认证”的整合,也就是“单点登录”。
通常来说,每一个单独的系统都会有自己的安全体系和身份认证系统。整合曾经,进入每一个系统都须要进行登录,这种局面不仅给管理上带来了非常大的困难,在安全方面也埋下了重大的隐患。以下是一些著名的调查公司显示的统计数据:
  • 用户每天平均 16 分钟花在身份验证任务上 资料来源: IDS
  • 频繁的 IT 用户平均有 21 个password 资料来源: NTA Monitor Password Survey
  • 49% 的人写下了其password,而 67% 的人非常少改变它们
  • 79 秒出现一起身份被窃事件 资料来源:National Small Business Travel Assoc
  • 全球欺骗损失每年约 12B – 资料来源:Comm Fraud Control Assoc
  • 2007 年,身份管理市场将成倍增长至 $4.5B – 资料来源:IDS
 
使用“单点登录”整合后,只须要登录一次就能够进入多个系统,而不须要又一次登录,这不只带来了更好的用户体验,更重要的是减少了安全的风险和管理的消耗。请看以下的统计数据:
  • 提高 IT 效率:对于每 1000 个受管用户,每用户可节省$70K
  • 帮助台呼叫降低至少1/3,对于 10K 员工的公司,每年能够节省每用户 $75,或者合计 $648K
  • 生产力提高:每一个新员工可节省 $1K,每一个老员工可节省 $350 资料来源:Giga
  • ROI 回报:7.5 13 个月 资料来源:Gartner
 
另外,使用“单点登录”还是
SOA
时代的需求之中的一个。在面向服务的架构中,服务和服务之间,程序和程序之间的通讯大量存在,服务之间的安全认证是
SOA
应用的难点之中的一个,应此建立“单点登录”的系统体系可以大大简化
SOA
的安全问题,提高服务之间的合作效率。
2
单点登陆的技术实现机制
随着
SSO
技术的流行,
SSO
的产品也是满天飞扬。全部著名的软件厂商都提供了对应的解决方式。在这里我并不想介绍自己公司(
Sun Microsystems
)的产品,而是对
SSO
技术本身进行解析,而且提供自己开发这一类产品的方法和简单演示。有关我写这篇文章的目的,请參考我的博客(
http://yuwang881.blog.sohu.com/3184816.html
)。
单点登录的机制事实上是比較简单的,用一个现实中的样例做比較。颐和园是北京著名的旅游景点,也是我常去的地方。在颐和园内部有很多独立的景点,比如“苏州街”、“佛香阁”和“德和园”,都能够在各个景点门口单独买票。非常多游客须要游玩全部德景点,这样的买票方式非常不方便,须要在每一个景点门口排队买票,钱包拿进拿出的,easy丢失,非常不安全。于是绝大多数游客选择在大门口买一张通票(也叫套票),就能够玩遍全部的景点而不须要又一次再买票。他们仅仅须要在每一个景点门口出示一下刚才买的套票就能够被同意进入每一个独立的景点。
单点登录的机制也一样,例如以下图所看到的,当用户第一次訪问应用系统
1
的时候,由于还没有登录,会被引导到认证系统中进行登录(
1
);依据用户提供的登录信息,认证系统进行身份效验,假设通过效验,应该返回给用户一个认证的凭据--
ticket

2
);用户再訪问别的应用的时候(
3

5
)就会将这个
ticket
带上,作为自己认证的凭据,应用系统接受到请求之后会把
ticket
送到认证系统进行效验,检查
ticket
的合法性(
4

6
)。假设通过效验,用户就能够在不用再次登录的情况下訪问应用系统
2
和应用系统
3
了。
编写你自己的单点登录(SSO)服务
从上面的视图能够看出,要实现
SSO
,须要下面基本的功能:
  • 全部应用系统共享一个身份认证系统。
    统一的认证系统是SSO的前提之中的一个。认证系统的主要功能是将用户的登录信息和用户信息库相比較,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行效验,推断其有效性。
  • 全部应用系统可以识别和提取ticket信息
    要实现SSO的功能,让用户仅仅登录一次,就必须让应用系统可以识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自己主动推断当前用户是否登录过,从而完毕单点登录的功能。
 
上面的功能仅仅是一个很easy的
SSO
架构,在现实情况下的
SSO
有着更加复杂的结构。有两点须要指出的是:
  • 单一的用户信息数据库并非必须的,有很多系统不能将全部的用户信息都集中存储,应该同意用户信息放置在不同的存储中,例如以下图所看到的。其实,仅仅要统一认证系统,统一ticket的产生和效验,不管用户信息存储在什么地方,都能实现单点登录。
 
编写你自己的单点登录(SSO)服务
  • 统一的认证系统并非说仅仅有单个的认证server,例如以下图所看到的,整个系统可以存在两个以上的认证server,这些server甚至可以是不同的产品。认证server之间要通过标准的通讯协议,互相交换认证信息,就能完毕更高级别的单点登录。例如以下图,当用户在訪问应用系统1时,由第一个认证server进行认证后,得到由此server产生的ticket。当他訪问应用系统4的时候,认证server2可以识别此ticket是由第一个server产生的,通过认证server之间标准的通讯协议(比如SAML)来交换认证信息,仍然可以完毕SSO的功能。
 
编写你自己的单点登录(SSO)服务
3 WEB-SSO
的实现
随着互联网的快速发展,
WEB
应用差点儿统治了绝大部分的软件应用系统,因此
WEB-SSO

SSO
应用其中最为流行。
WEB-SSO
有其自身的特点和优势,实现起来比較简单易用。非常多商业软件和开源软件都有对
WEB-SSO
的实现。其中值得一提的是
OpenSSO

https://opensso.dev.java.net
),为用
Java
实现
WEB-SSO
提供架构指南和服务指南,为用户自己来实现
WEB-SSO
提供了理论的根据和实现的方法。
为什么说
WEB-SSO
比較easy实现呢?这是有
WEB
应用自身的特点决定的。
众所周知,
Web
协议(也就是
HTTP
)是一个无状态的协议。一个
Web
应用由非常多个
Web
页面组成,每一个页面都有唯一的
URL
来定义。用户在浏览器的地址栏输入页面的
URL
,浏览器就会向
Web Server
去发送请求。例如以下图,浏览器向
Web
server发送了两个请求,申请了两个页面。这两个页面的请求是分别使用了两个单独的
HTTP
连接。所谓无状态的协议也就是表如今这里,浏览器和
Web
server会在第一个请求完毕以后关闭连接通道,在第二个请求的时候又一次建立连接。
Web
server并不区分哪个请求来自哪个client,对全部的请求都一视同仁,都是单独的连接。这种方式大大差别于传统的(
Client/Server

C/S
结构
,
在那样的应用中,client和server端会建立一个长时间的专用的连接通道。正是由于有了无状态的特性,每一个连接资源可以非常快被其它client所重用,一台
Web
server才可以同一时候服务于成千上万的client。
编写你自己的单点登录(SSO)服务
可是我们通常的应用是有状态的。先不用提不同应用之间的
SSO
,在同一个应用中也须要保存用户的登录身份信息。比如用户在訪问页面
1
的时候进行了登录,可是刚才也提到,client的每一个请求都是单独的连接,当客户再次訪问页面
2
的时候,怎样才干告诉
Web
server,客户刚才已经登录过了呢?浏览器和server之间有约定:通过使用
cookie
技术来维护应用的状态。
Cookie
是能够被
Web
server设置的字符串,而且能够保存在浏览器中。例如以下图所看到的,当浏览器訪问了页面
1
时,
web
server设置了一个
cookie
,并将这个
cookie
和页面
1
一起返回给浏览器,浏览器接到
cookie
之后,就会保存起来,在它訪问页面
2
的时候会把这个
cookie
也带上,
Web
server接到请求时也能读出
cookie
的值,依据
cookie
值的内容就能够推断和恢复一些用户的信息状态。
编写你自己的单点登录(SSO)服务
Web-SSO
全然能够利用
Cookie
结束来完毕用户登录信息的保存,将浏览器中的
Cookie
和上文中的
Ticket
结合起来,完毕
SSO
的功能。
 
为了完毕一个简单的
SSO
的功能,须要两个部分的合作:
  1. 统一的身份认证服务。
  2. 改动Web应用,使得每一个应用都通过这个统一的认证服务来进行身份效验。
3.1 Web SSO
的例子
依据上面的原理,我用
J2EE
的技术(
JSP

Servlet
)完毕了一个具有
Web-SSO
的简单例子。例子包括一个身份认证的server和两个简单的
Web
应用,使得这两个
Web
应用通过统一的身份认证服务来完毕
Web-SSO
的功能。此例子全部的源码和二进制代码都能够从站点地址
http://gceclub.sun.com.cn/wangyu/
下载。
 
例子下载、安装部署和执行指南:
  • Web-SSO的例子是由三个标准Web应用组成,压缩成三个zip文件,从http://gceclub.sun.com.cn/wangyu/web-sso/中下载。当中SSOAuthhttp://gceclub.sun.com.cn/wangyu/web-sso/SSOAuth.zip)是身份认证服务;SSOWebDemo1http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo1.zip)和SSOWebDemo2http://gceclub.sun.com.cn/wangyu/web-sso/SSOWebDemo2.zip)是两个用来演示单点登录的Web应用。这三个Web应用之所以没有打成war包,是由于它们不能直接部署,依据读者的部署环境须要作出小小的改动。例子部署和执行的环境有一定的要求,须要符合Servlet2.3以上标准的J2EE容器才干执行(比如Tomcat5,Sun Application Server 8, Jboss 4等)。另外,身份认证服务须要JDK1.5的执行环境。之所以要用JDK1.5是由于笔者使用了一个线程安全的高性能的Java集合类“ConcurrentMap”,仅仅有在JDK1.5中才有。
  • 这三个Web应用全然能够单独部署,它们能够分别部署在不同的机器,不同的操作系统和不同的J2EE的产品上,它们全然是标准的和平台无关的应用。可是有一个限制,那两台部署应用(demo1demo2)的机器的域名须要同样,这在后面的章节中会解释到cookiedomain的关系以及怎样制作跨域的WEB-SSO
  • 解压缩SSOAuth.zip文件,在/WEB-INF/下的web.xml中请改动“domainname”的属性以反映实际的应用部署情况,domainname须要设置为两个单点登录的应用(demo1demo2)所属的域名。这个domainname和当前SSOAuth服务部署的机器的域名没有关系。我缺省设置的是“.sun.com”。假设你部署demo1demo2的机器没有域名,请输入IP地址或主机名(如localhost),可是假设使用IP地址或主机名也就意味着demo1demo2须要部署到一台机器上了。设置完后,依据你所选择的J2EE容器,可能须要将SSOAuth这个文件夹压缩打包成war文件。用“jar -cvf SSOAuth.war SSOAuth/”就能够完毕这个功能。
  • 解压缩SSOWebDemo1SSOWebDemo2文件,分别在它们/WEB-INF/下找到web.xml文件,请改动当中的几个初始化參数
    <init-param>
    <param-name>SSOServiceURL</param-name>
    <param-value>http://wangyu.prc.sun.com:8080/SSOAuth/SSOAuth</param-value>
    </init-param>
    <init-param>
    <param-name>SSOLoginPage</param-name>
    <param-value>http://wangyu.prc.sun.com:8080/SSOAuth/login.jsp</param-value>
    </init-param>
    将当中的SSOServiceURLSSOLoginPage改动成部署SSOAuth应用的机器名、port号以及根路径(缺省是SSOAuth)以反映实际的部署情况。设置完后,依据你所选择的J2EE容器,可能须要将SSOWebDemo1SSOWebDemo2这两个文件夹压缩打包成两个war文件。用“jar -cvf SSOWebDemo1.war SSOWebDemo1/”就能够完毕这个功能。
  • 请输入第一个web应用的測试URLtest.jsp,比如http://wangyu.prc.sun.com:8080/ SSOWebDemo1/test.jsp,假设是第一次訪问,便会自己主动跳转到登录界面,例如以下图

    编写你自己的单点登录(SSO)服务

  • 使用系统自带的三个帐号之中的一个登录(比如,username:wangyu,password:wangyu),便能成功的看到test.jsp的内容:显示当前username和欢迎信息。
    编写你自己的单点登录(SSO)服务
  • 请接着在同一个浏览器中输入第二个web应用的測试URLtest.jsp,比如http://wangyu.prc.sun.com:8080/ SSOWebDemo2/test.jsp。你会发现,不须要再次登录就能看到test.jsp的内容,相同是显示当前username和欢迎信息,并且欢迎信息中明白的显示当前的应用名称(demo2)。
             
编写你自己的单点登录(SSO)服务
3.2 WEB-SSO
代码解说
3.2.1
身份认证服务代码解析
Web-SSO
的源码能够从站点地址
http://gceclub.sun.com.cn/wangyu/web-sso/websso_src.zip
下载。身份认证服务是一个标准的
web
应用,包含一个名为
SSOAuth

Servlet
,一个
login.jsp
文件和一个
failed.html
。身份认证的全部服务差点儿都由
SSOAuth

Servlet
来实现了;
login.jsp
用来显示登录的页面(假设发现用户还没有登录过);
failed.html
是用来显示登录失败的信息(假设用户的username和password与信息数据库中的不一样)。
SSOAuth
的代码如以下的列表显示,结构很easy,先看看这个
Servlet
的主体部分
package DesktopSSO;
 
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
 
import javax.servlet.*;
import javax.servlet.http.*;
 
 
public class SSOAuth extends HttpServlet {
   
   
static private ConcurrentMap accounts;
   
static private ConcurrentMap SSOIDs;
   
String cookiename=”WangYuDesktopSSOID”;
   
String domainname;
   
   
public void init(ServletConfig config) throws ServletException {
       
super.init(config);
       
domainname= config.getInitParameter(“domainname”);
       
cookiename = config.getInitParameter(“cookiename”);
       
SSOIDs = new ConcurrentHashMap();
       
accounts=new ConcurrentHashMap();
       
accounts.put(“wangyu”, “wangyu”);
       
accounts.put(“paul”, “paul”);
       
accounts.put(“carol”, “carol”);
   
}
 
   
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
PrintWriter out = response.getWriter();
       
String action = request.getParameter(“action”);
       
String result=”failed”;
       
if (action==null) {
           
handlerFromLogin(request,response);
       
} else if (action.equals(“authcookie”)){
           
String myCookie = request.getParameter(“cookiename”);
           
if (myCookie != null) result = authCookie(myCookie);
           
out.print(result);
           
out.close();
       
} else if (action.equals(“authuser”)) {
           
result=authNameAndPasswd(request,response);
           
out.print(result);
           
out.close();
       
} else if (action.equals(“logout”)) {
           
String myCookie = request.getParameter(“cookiename”);
           
logout(myCookie);
            
out.close();
       
}
   
}
 
…..
 
}
 
从代码非常easy看出,
SSOAuth
就是一个简单的
Servlet
。当中有两个静态成员变量:
accounts

SSOIDs
,这两个成员变量都使用了
JDK1.5
中线程安全的
MAP
类: ConcurrentMap
,所以这个例子一定要
JDK1.5
才干执行。
Accounts
用来存放用户的username和password,在
init()
的方法中能够看到我给系统加入�了三个合法的用户。在实际应用中,
accounts
应该是去数据库中或
LDAP
中获得,为了简单起见,在本例子中我使用了
ConcurrentMap
在内存中用程序创建了三个用户。而
SSOIDs
保存了在用户成功的登录后所产生的
cookie
和username的相应关系。它的功能显而易见:当用户成功登录以后,再次訪问别的系统,为了鉴别这个用户请求所带的
cookie
的有效性,须要到
SSOIDs
中检查这种映射关系是否存在。
 
在基本的请求处理方法
processRequest()
中,能够非常清楚的看到
SSOAuth
的全部功能
  1. 假设用户还没有登录过,是第一次登录本系统,会被跳转到login.jsp页面(在后面会解释怎样跳转)。用户在提供了username和password以后,就会用handlerFromLogin()这种方法来验证。
  2. 假设用户已经登录过本系统,再訪问别的应用的时候,是不须要再次登录的。由于浏览器会将第一次登录时产生的cookie和请求一起发送。效验cookie的有效性是SSOAuth的主要功能之中的一个。
  3. SSOAuth还能直接效验非login.jsp页面过来的username和password的效验请求。这个功能是用于非web应用的SSO,这在后面的桌面SSO中会用到。
  4. SSOAuth还提供logout服务。
 
以下看看几个基本的功能函数:
 
private void handlerFromLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
String username = request.getParameter(“username”);
       
String password = request.getParameter(“password”);
       
String pass = (String)accounts.get(username);
       
if ((pass==null)||(!pass.equals(password)))
           
getServletContext().getRequestDispatcher(“/failed.html”).forward(request, response);
       
else {
           
String gotoURL = request.getParameter(“goto”);
           
String newID = createUID();
           
SSOIDs.put(newID, username);
           
Cookie wangyu = new Cookie(cookiename, newID);
           
wangyu.setDomain(domainname);
           
wangyu.setMaxAge(60000);
           
wangyu.setValue(newID);
           
wangyu.setPath(“/”);
           
response.addCookie(wangyu);
           
System.out.println(“login success, goto back url:” + gotoURL);
           
if (gotoURL != null) {
               
PrintWriter out = response.getWriter();
                
response.sendRedirect(gotoURL);
               
out.close();
           
}
       
}  
   
}
handlerFromLogin()
这种方法是用来处理来自
login.jsp
的登录请求。它的逻辑非常easy:将用户输入的username和password与预先设定好的用户集合(存放在
accounts
中)相比較,假设username或password不匹配的话,则返回登录失败的页面(
failed.html
),假设登录成功的话,须要为用户当前的
session
创建一个新的
ID
,并将这个
ID
和username的映射关系存放到
SSOIDs
中,最后还要将这个
ID
设置为浏览器可以保存的
cookie
值。
登录成功后,浏览器会到哪个页面呢?那我们回想一下我们是怎样使用身份认证服务的。一般来说我们不会直接訪问身份服务的不论什么
URL
,包含
login.jsp
。身份服务是用来保护其它应用服务的,用户一般在訪问一个受
SSOAuth
保护的
Web
应用的某个
URL
时,当前这个应用会发现当前的用户还没有登录,便强制将也页面转向
SSOAuth

login.jsp
,让用户登录。假设登录成功后,应该自己主动的将用户的浏览器指向用户最初想訪问的那个
URL
。在
handlerFromLogin()
这种方法中,我们通过接收

goto”
这个參数来保存用户最初訪问的
URL
,成功后便又一次定向到这个页面中。
另外一个要说明的是,在设置
cookie
的时候,我使用了一个setMaxAge(6000)
的方法。这种方法是用来设置
cookie
的有效期,单位是秒。假设不使用这种方法或者參数为负数的话,当浏览器关闭的时候,这个
cookie
就失效了。在这里我给了非常大的值(
1000
分钟),导致的行为是:当你关闭浏览器(或者关机),下次再打开浏览器訪问刚才的应用,仅仅要在
1000
分钟之内,就不须要再登录了。我这样做是以下要介绍的桌面
SSO
中所须要的功能。
其它的方法更加简单,这里就不多解释了。
 
3.2.2
具有
SSO
功能的
web
应用源码解析
要实现
WEB-SSO
的功能,仅仅有身份认证服务是不够的。这点非常显然,要想使多个应用具有单点登录的功能,还须要每一个应用本身的配合:将自己的身份认证的服务交给一个统一的身份认证服务-
SSOAuth

SSOAuth
服务中提供的各个方法就是供每一个添�
SSO

Web
应用来调用的。
一般来说,
Web
应用须要
SSO
的功能,应该通过下面的交互过程来调用身份认证服务的提供的认证服务:
  • Web应用中每个须要安全保护的URL在訪问曾经,都须要进行安全检查,假设发现没有登录(没有发现认证之后所带的cookie),就又一次定向到SSOAuth中的login.jsp进行登录。
  • 登录成功后,系统会自己主动给你的浏览器设置cookie,证明你已经登录过了。
  • 当你再訪问这个应用的须要保护的URL的时候,系统还是要进行安全检查的,可是这次系统可以发现对应的cookie
  • 有了这个cookie,还不能证明你就一定有权限訪问。由于有可能你已经logout,或者cookie已经过期了,或者身份认证服务重起过,这些情况下,你的cookie都可能无效。应用系统拿到这个cookie,还须要调用身份认证的服务,来推断cookie时候真的有效,以及当前的cookie相应的用户是谁。
  • 假设cookie效验成功,就同意用户訪问当前请求的资源。
以上这些功能,能够用非常多方法来实现:
  • 在每一个被訪问的资源中(JSPServlet)中都添�身份认证的服务,来获得cookie,而且推断当前用户是否登录过。只是这个笨方法没有人会用:-)
  • 能够通过一个controller,将全部的功能都写到一个servlet中,然后在URL映射的时候,映射到全部须要保护的URL集合中(比如*.jsp/security/*等)。这种方法能够使用,只是,它的缺点是不能重用。在每一个应用中都要部署一个同样的servlet
  • Filter是比較好的方法。符合Servlet2.3以上的J2EE容器就具有部署filter的功能。(Filter的使用能够參考JavaWolrd的文章http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.htmlFilter是一个具有非常好的模块化,可重用的编程API,用在SSO正合适只是。本例子就是使用一个filter来完毕以上的功能。
 
package SSO;
 
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
 
public class SSOFilter implements Filter {
   
private FilterConfig filterConfig = null;
   
private String cookieName=”WangYuDesktopSSOID”;
   
private String SSOServiceURL= “http://wangyu.prc.sun.com:8080/SSOAuth/SSOAuth”;
   
private String SSOLoginPage= “http://wangyu.prc.sun.com:8080/SSOAuth/login.jsp”;
   
   
public void init(FilterConfig filterConfig) {
 
       
this.filterConfig = filterConfig;
       
if (filterConfig != null) {
           
if (debug) {
               
log(“SSOFilter:Initializing filter”);
           
}
       
}       
       
cookieName = filterConfig.getInitParameter(“cookieName”);
       
SSOServiceURL = filterConfig.getInitParameter(“SSOServiceURL”);
       
SSOLoginPage = filterConfig.getInitParameter(“SSOLoginPage”);
   
…..
…..
 
}
以上的初始化的源码有两点须要说明:一是有两个须要配置的參数
SSOServiceURL

SSOLoginPage
。由于当前的
Web
应用非常可能和身份认证服务(
SSOAuth
)不在同一台机器上,所以须要让这个
filter
知道身份认证服务部署的
URL
,这样才干去调用它的服务。另外一点就是由于身份认证的服务调用是要通过
http
协议来调用的(在本例子中是这样设计的,读者全然能够设计自己的身份服务,使用别的调用协议,如
RMI

SOAP
等等),全部笔者引用了
apache

commons
工具包(具体信息情訪问
apache
的站点
http://jakarta.apache.org/commons/index.html
),当中的

httpclient”
能够大大简化
http
调用的编程。
以下看看
filter
的主体方法
doFilter():
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
       
if (debug) log(“SSOFilter:doFilter()”);
       
HttpServletRequest request = (HttpServletRequest) req;
       
HttpServletResponse response = (HttpServletResponse) res;
       
String result=”failed”;
       
String url = request.getRequestURL().toString();
       
String qstring = request.getQueryString();
       
if (qstring == null) qstring =””;
 
       
//
检查
http
请求的
head
是否有须要的
cookie
       
String cookieValue =””;
       
javax.servlet.http.Cookie[] diskCookies = request.getCookies();
       
if (diskCookies != null) {
           
for (int i = 0; i < diskCookies.length; i++) {
               
if(diskCookies[i].getName().equals(cookieName)){
                   
cookieValue = diskCookies[i].getValue();
 
                   
//
假设找到了对应的
cookie
则效验其有效性
                   
result = SSOService(cookieValue);
                   
if (debug) log(“found cookies!”);
                
}
           
}
       
}
       
if (result.equals(“failed”)) { //
效验失败或没有找到
cookie
,则须要登录
           
response.sendRedirect(SSOLoginPage+”?goto=”+url);
       
} else if (qstring.indexOf(“logout”) > 1) {//logout
服务
           
if (debug) log(“logout action!”);
           
logoutService(cookieValue);
           
response.sendRedirect(SSOLoginPage+”?goto=”+url);
       
} else {//
效验成功
           
request.setAttribute(“SSOUser”,result);
           
Throwable problem = null;
           
try {
                
chain.doFilter(req, res);
           
} catch(Throwable t) {
               
problem = t;
               
t.printStackTrace();
           
}      
           
if (problem != null) {
               
if (problem instanceof ServletException) throw (ServletException)problem;
               
if (problem instanceof IOException) throw (IOException)problem;
               
sendProcessingError(problem, res);
           
}
       
}  
   
}
doFilter()
方法的逻辑也是很easy的,在接收到请求的时候,先去查找是否存在期望的
cookie
值,假设找到了,就会调用
SSOService(cookieValue)
去效验这个
cookie
的有效性。假设
cookie
效验不成功或者
cookie
根本不存在,就会直接转到登录界面让用户登录;假设
cookie
效验成功,就不会做不论什么阻拦,让此请求进行下去。在配置文件里,有以下的一个节点表示了此
filter

URL
映射关系:仅仅拦截全部的
jsp
请求。
<filter-mapping>
<filter-name>SSOFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
 
以下还有几个基本的函数须要说明:
   
private String SSOService(String cookievalue) throws IOException {
       
String authAction = “?action=authcookie&cookiename=”;
       
HttpClient httpclient = new HttpClient();
       
GetMethod httpget = new GetMethod(SSOServiceURL+authAction+cookievalue);
       
try { 
           
httpclient.executeMethod(httpget);
           
String result = httpget.getResponseBodyAsString();
           
return result;
       
} finally {
           
httpget.releaseConnection();
       
}
   
}
   
   
private void logoutService(String cookievalue) throws IOException {
       
String authAction = “?action=logout&cookiename=”;
       
HttpClient httpclient = new HttpClient();
       
GetMethod httpget = new GetMethod(SSOServiceURL+authAction+cookievalue);
       
try {
           
httpclient.executeMethod(httpget);
           
httpget.getResponseBodyAsString();
       
} finally {
           
httpget.releaseConnection();
       
}
   
}
这两个函数主要是利用
apache
中的
httpclient
訪问
SSOAuth
提供的认证服务来完毕效验
cookie

logout
的功能。
其它的函数都非常easy,有非常多都是我的
IDE

NetBeans
)替我自己主动生成的。
4
当前方案的安全局限性
当前这个
WEB-SSO
的方案是一个比較简单的雏形,主要是用来演示
SSO
的概念和说明
SSO
技术的实现方式。有非常多方面还须要完好,当中安全性是非常重要的一个方面。
我们说过,採用
SSO
技术的主要目的之中的一个就是加强安全性,减少安全风险。由于採用了
SSO
,在网络上传递password的次数减少,风险减少是显然的,可是当前的方案却有其它的安全风险。由于
cookie
是一个用户登录的唯一凭据,对
cookie
的保护措施是系统安全的重要环节:
  • cookie的长度和复杂度
    在本方案中,cookie是有一个固定的字符串(我的姓名)加上当前的时间戳。这种cookie非常easy被伪造和推測。怀有恶意的用户假设推測到合法的cookie就能够被当作已经登录的用户,随意訪问权限范围内的资源
  • cookie的效验和保护
    在本方案中,尽管password仅仅要传输一次就够了,可cookie在网络中是常常传来传去。一些网络探測工具(如sniff, snoop,tcpdump等)能够非常easy捕获到cookie的数值。在本方案中,并没有考虑cookie在传输时候的保护。另外对cookie的效验也过于简单,并不去检查发送cookie的来源到底是不是cookie最初的拥有者,也就是说无法区分正常的用户和仿造cookie的用户。
  • 当当中一个应用的安全性不好,其它全部的应用都会受到安全威胁
    由于有SSO,所以当某个处于 SSO的应用被黒客攻破,那么非常easy攻破其它处于同一个SSO保护的应用。
这些安全漏洞在商业的
SSO
解决方式中都会有所考虑,提供相关的安全措施和保护手段,比如
Sun
公司的
Access Manager

cookie
的复杂读和对
cookie
的保护都做得很好。另外在
OpneSSO

https://opensso.dev.java.net
)的架构指南中也给出了部分安全措施的解决方式。
5
当前方案的功能和性能局限性
除了安全性,当前方案在功能和性能上都须要非常多的改进:
  • 当前所提供的登录认证模式仅仅有一种:username和password,并且为了简单,将username和password放在内存其中。其实,用户身份信息的来源应该是多种多样的,能够是来自数据库中,LDAP中,甚至于来自操作系统自身的用户列表。还有非常多其它的认证模式都是商务应用必不可少的,因此SSO的解决方式应该包含各种认证的模式,包含数字证书,RadiusSafeWord MemberShipSecurID等多种方式。最为灵活的方式应该同意可插入的JAAS框架来扩展身份认证的接口
  • 我们编写的Filter仅仅能用于J2EE的应用,而对于大量非JavaWeb应用,却无法提供SSO服务。
  • 在将Filter应用到Web应用的时候,须要对容器上的每个应用都要做对应的改动,又一次部署。而更加流行的做法是Agent机制:为每个应用server安装一个agent,就能够将SSO功能应用到这个应用server中的全部应用。
  • 当前的方案不能支持分别位于不同domainWeb应用进行SSO。这是由于浏览器在訪问Webserver的时候,只会带上和当前webserver具有同样domain名称的那些cookie。要提供跨域的SSO的解决方式有非常多其它的方法,在这里就不多说了。SunAccess Manager就具有跨域的SSO的功能。
  • 另外,Filter的性能问题也是须要重视的方面。由于Filter会截获每个符合URL映射规则的请求,获得cookie,验证其有效性。这一系列任务是比較消耗资源的,特别是验证cookie有效性是一个远程的http的调用,来訪问SSOAuth的认证服务,有一定的延时。因此在性能上须要做进一步的提高。比如在本例子中,假设将URL映射从“.jsp改成“/*,也就是说filter对全部的请求都起作用,整个应用会变得非常慢。这是由于,页面其中包括了各种静态元素如gif图片,css样式文件,和其它html静态页面,这些页面的訪问都要通过filter去验证。而其实,这些静态元素没有什么安全上的需求,应该在filter中进行推断,不去效验这些请求,性能会好非常多。另外,假设在filter中加上一定的cache,而不须要每个cookie效验请求都去远端的身份认证服务中运行,性能也能大幅度提高。
  • 另外系统还须要非常多其它的服务,如在内存中定时删除没用的cookie映射等等,都是一个严肃的解决方式须要考虑的问题。
6
桌面
SSO
的实现

WEB-SSO
的概念延伸开,我们能够把
SSO
的技术拓展到整个桌面的应用,不只局限在浏览器。
SSO
的概念和原则都没有改变,只须要再做一点点的工作,就能够完毕桌面
SSO
的应用。
桌面
SSO

WEB-SSO
一样,关键的技术也在于怎样在用户登录过后保存登录的凭据。在
WEB-SSO
中,登录的凭据是靠浏览器的
cookie
机制来完毕的;在桌面应用中,能够将登录的凭证保存到不论什么地方,仅仅要全部
SSO
的桌面应用都共享这个凭证。
从站点能够下载一个简单的桌面
SSO
的例子
(http://gceclub.sun.com.cn/wangyu/desktop-sso/desktopsso.zip)
和所有源代码(
http://gceclub.sun.com.cn/wangyu/desktop-sso/desktopsso_src.zip
),尽管简单,可是它具有桌面
SSO
大多数的功能,略微加以扩充就能够成为自己的解决方式。
 
6.1
桌面例子的部署
  1. 执行此桌面SSO须要三个前提条件:
    a) WEB-SSO
    的身份认证应用应该正在执行,由于我们在桌面SSO其中须要用到统一的认证服务
    b)
    当前桌面须要执行MozillaNetscape浏览器,由于我们将ticket保存到mozillacookie文件里
    c)
    必须在JDK1.4以上执行。(WEB-SSO须要JDK1.5以上)
  2. 解开desktopsso.zip文件,里面有两个文件夹binlib
  3. bin文件夹下有一些脚本文件和配置文件,当中config.properties包括了三个须要配置的參数:
    a) SSOServiceURL
    要指向WebSSO部署的身份认证的URL
    b) SSOLoginPage
    要指向WebSSO部署的身份认证的登录页面URL
    c) cookiefilepath
    要指向当前用户的mozilla所存放cookie的文件
  4. bin文件夹下另一个login.conf是用来配置JAAS登录模块,本例子提供了两个,读者能够随意选择当中一个(也能够都选),再又一次执行程序,查看登录认证的变化
  5. bin下的执行脚本可能须要作对应的改动
    a)
    假设是在unix下,各个jar文件须要用“:来隔开,而不是“;
    b) java
    执行程序须要放置在当前执行的路径下,否则须要加上java的路径全名。
 
6.2
桌面例子的执行
例子程序包括三个简单的
Java
控制台程序,这三个程序单独执行都须要登录。假设执行第一个命叫“
GameSystem

的程序,提示须要输入username和password:
编写你自己的单点登录(SSO)服务
效验成功以后,便会显示当前登录的用户的基本信息等等。
编写你自己的单点登录(SSO)服务
 
这时候再执行第二个桌面
Java
应用(
mailSystem
)的时候,就不须要再登录了,直接就显示出来刚才登录的用户。
编写你自己的单点登录(SSO)服务
第三个应用是
logout
,执行它之后,用户便退出系统。再訪问的时候,又须要又一次登录了。请读者再制裁执行完
logout
之后,又一次验证一下前两个应用的
SSO
:先执行第二个应用,再执行第一个,会看到同样的效果。
我们的例子并没有在这里停步,其实,本例子不仅可以和在几个
Java
应用之间
SSO
,还能和浏览器进行
SSO
,也就是将浏览器也当成是桌面的一部分。这对一些行业有着不小的吸引力。
这时候再打开
Mozilla
浏览器,訪问曾经提到的那两个
WEB
应用,会发现仅仅要桌面应用假设登录过,
Web
应用就不用再登录了,并且能显示刚才登录的用户的信息。读者能够在几个桌面和
Web
应用之间进行登录和
logout
的试验,看看它们之间的
SSO
编写你自己的单点登录(SSO)服务
6.3
桌面例子的源代码分析
桌面
SSO
的例子使用了
JAAS
(要了解
JAAS
的具体的信息请參考
http://java.sun.com/products/jaas
)。
JAAS
是对
PAM

Pluggable Authentication Module
)的
Java
实现,来完毕
Java
应用可插拔的安全认证模块。使用
JAAS
作为
Java
应用的安全认证模块有非常多优点,最基本的是不须要改动源码就能够更换认证方式。比如原有的
Java
应用假设使用
JAAS
的认证,假设须要应用
SSO
,仅仅须要改动
JAAS
的配置文件即可了。如今在流行的
J2EE
和其它
Java
的产品中,用户的身份认证都是通过
JAAS
来完毕的。在例子中,我们就展示了这个功能。请看配置文件
login.conf
   
DesktopSSO {
  
desktopsso.share.PasswordLoginModule required;
  
desktopsso.share.DesktopSSOLoginModule required;
};
当我们注解掉第二个模块的时候,仅仅有第一个模块起作用。在这个模块的作用下,仅仅有
test
用户(password是
12345
)才干登录。当我们注解掉第一个模块的时候,仅仅有第二个模块起作用,桌面
SSO
才会起作用。
 
全部的
Java
桌面例子程序都是标准
JAAS
应用,熟悉
JAAS
的程序猿会非常快了解。
JAAS
中基本的是登录模块(
LoginModule
)。以下是
SSO
登录模块的源代码:
 
public class DesktopSSOLoginModule implements LoginModule {
  
……….
  
private String SSOServiceURL = “”;
  
private String SSOLoginPage = “”;
  
private static String cookiefilepath = “”;  
  
………
 

config.properties
的文件里,我们配置了它们的值:
SSOServiceURL=http://wangyu.prc.sun.com:8080/SSOAuth/SSOAuth
SSOLoginPage=http://wangyu.prc.sun.com:8080/SSOAuth/login.jsp
cookiefilepath=C://Documents and Settings//yw137672//Application Data//Mozilla//Profiles//default//hog6z1ji.slt//cookies.txt
SSOServiceURL

SSOLoginPage
成员变量指向了在
Web-SSO
中用过的身份认证模块:
SSOAuth
,这就说明在桌面系统中我们试图和
Web
应用共用一个认证服务。而
cookiefilepath
成员变量则泄露了一个“天机”:我们使用了
Mozilla
浏览器的
cookie
文件来保存登录的凭证。换句话说,和
Mozilla
共用了一个保存登录凭证的机制。之所以用
Mozilla
是应为它的
Cookie
文件格式简单,非常easy编程訪问和改动随意的
Cookie
值。(我试图解析
Internet Explorer

cookie
文件但没有成功。)
以下是登录模块DesktopSSOLoginModule的主体:
login()
方法。逻辑也是很easy:先用
Cookie
来登陆,假设成功,则直接就进入系统,否则须要用户输入username和password来登录系统。
   
public boolean login() throws LoginException{
       
try {
           
if (Cookielogin()) return true;
       
} catch (IOException ex) {
           
ex.printStackTrace();
       
}
     
if (passwordlogin()) return true;
     
throw new FailedLoginException();
 
}
 
以下是Cookielogin()
方法的实体,它的逻辑是:
先从
Cookie
文件里获得对应的
Cookie
值,通过身份效验服务效验
Cookie
的有效性。假设
cookie
有效
就算登录成功;假设不成功或
Cookie
不存在,用
cookie
登录就算失败。
   
public boolean Cookielogin() throws LoginException,IOException {
     
String cookieValue=””;
     
int cookieIndex =foundCookie();
     
if (cookieIndex<0)
           
return false;
     
else
           
cookieValue = getCookieValue(cookieIndex);
    
username = cookieAuth(cookieValue);
    
if (! username.equals(“failed”)) {
        
loginSuccess = true;
        
return true;
    
}
    
return false;
 
}
 
 
用username和password登录的方法要复杂一些,通过
Callback
的机制和屏幕输入输出进行信息交互,完毕用户登录信息的获取;获取信息以后通过
userAuth
方法来调用远端
SSOAuth
的服务来判定当前登录的有效性。
  
public boolean passwordlogin() throws LoginException {
   
//
   
// Since we need input from a user, we need a callback handler
   
if (callbackHandler == null) {
      
throw new LoginException(“No CallbackHandler defined”);
   
}
   
Callback[] callbacks = new Callback[2];
   
callbacks[0] = new NameCallback(“Username”);
   
callbacks[1] = new PasswordCallback(“Password”, false);
   
//
   
// Call the callback handler to get the username and password
   
try {
     
callbackHandler.handle(callbacks);
     
username = ((NameCallback)callbacks[0]).getName();
     
char[] temp = ((PasswordCallback)callbacks[1]).getPassword();
     
password = new char[temp.length];
     
System.arraycopy(temp, 0, password, 0, temp.length);
     
((PasswordCallback)callbacks[1]).clearPassword();
   
} catch (IOException ioe) {
     
throw new LoginException(ioe.toString());
   
} catch (UnsupportedCallbackException uce) {
     
throw new LoginException(uce.toString());
   
}
   
   
System.out.println();
   
String authresult =””;
   
try {
       
authresult = userAuth(username, password);
   
} catch (IOException ex) {
       
ex.printStackTrace();
   
}
   
if (! authresult.equals(“failed”)) {
       
loginSuccess= true;
       
clearPassword();
       
try {
           
updateCookie(authresult);
       
} catch (IOException ex) {
           
ex.printStackTrace();
       
}
       
return true;
   
}
  
 
   
loginSuccess = false;
   
username = null;
   
clearPassword();
   
System.out.println( “Login: PasswordLoginModule FAIL” );
   
throw new FailedLoginException();
 
}
 
 
CookieAuth

userAuth
方法都是利用
apahce

httpclient
工具包和远程的
SSOAuth
进行
http
连接,获取服务。
       
private String cookieAuth(String cookievalue) throws IOException{
       
String result = “failed”;
       
       
HttpClient httpclient = new HttpClient();      
       
GetMethod httpget = new GetMethod(SSOServiceURL+Action1+cookievalue);
   
       
try {
           
httpclient.executeMethod(httpget);
           
result = httpget.getResponseBodyAsString();
       
} finally {
           
httpget.releaseConnection();
       
}
       
return result;
   
}
 
private String userAuth(String username, char[] password) throws IOException{
       
String result = “failed”;
       
String passwd= new String(password);
       
HttpClient httpclient = new HttpClient();      
       
GetMethod httpget = new GetMethod(SSOServiceURL+Action2+username+”&password=”+passwd);
       
passwd = null;
   
       
try {
           
httpclient.executeMethod(httpget);
           
result = httpget.getResponseBodyAsString();
       
} finally {
           
httpget.releaseConnection();
       
}
       
return result;
       
   
}
 
另一个地方须要补充说明的是,在本例子中,username和password的输入都会在屏幕上显示明文。假设希望用掩码形式来显示password,以提高安全性,请參考:
http://java.sun.com/developer/technicalArticles/Security/pwordmask/
7
真正安全的全方位
SSO
解决方式:
Kerberos
我们的例子程序(桌面
SSO

WEB-SSO
)都有一个共性:要想将一个应用集成到我们的
SSO
解决方式中,或多或少的须要改动应用程序。
Web
应用须要配置一个我们预制的
filter
;桌面应用须要加上我们桌面
SSO

JAAS
模块(至少要改动
JAAS
的配置文件)。但是有非常多程序是没有源码和无法改动的,比如经常使用的远程通讯程序
telnet

ftp
等等一些操作系统自己带的经常使用的应用程序。这些程序是非常难改动添�到我们的
SSO
的解决方式中。
其实有一种全方位的
SSO
解决方式能够解决这些问题,这就是
Kerberos
协议(
RFC 1510
)。
Kerberos
是网络安全应用标准
(http://web.mit.edu/kerberos/)
,由
MIT
学校发明,被主流的操作系统所採用。在採用
kerberos
的平台中,登录和认证是由操作系统本身来维护,认证的凭证也由操作系统来保存,这样整个桌面都能够处于同一个
SSO
的系统保护中。操作系统中的各个应用(如
ftp,telnet
)仅仅须要通过配置就能添�到
SSO
中。另外使用
Kerberos
最大的优点在于它的安全性。通过密钥算法的保证和密钥中心的建立,能够做到用户的password根本不须要在网络中传输,而传输的信息也会十分的安全。
眼下支持
Kerberos
的操作系统包含
Solaris, windows,Linux
等等主流的平台。仅仅只是要搭建一个
Kerberos
的环境比較复杂,
KDC
(密钥分发中心)的建立也须要相当的步骤。
Kerberos
拥有很成熟的
API
,包含
Java

API
。使用
Java Generic Security Services(GSS) API
而且使用
JAAS
中对
Kerberos
的支持(具体信息请參见
Sun

Java&Kerberos
教程
http://java.sun.com/ j2se/1.5.0/docs/guide/security/jgss/tutorials/index.html
),要将我们这个例子改造成对
Kerberos
的支持也是不难的。 值得一提的是在
JDK6.0

http://www.java.net/download/jdk6
)其中直接就包含了对
GSS
的支持,不须要单独下载
GSS
的包。
 
8
总结
本文的主要目的是阐述
SSO
的基本原理,并提供了一种实现的方式。通过对源码的分析来掌握开发
SSO
服务的技术要点和充分理解
SSO
的应用范围。可是,本文只说明了身份认证的服务,而另外一个和身份认证密不可分的服务
—-
权限效验,却没有提到。要开发出真正的
SSO
的产品,在功能上、性能上和安全上都必须有更加完备的考虑。
作者简单介绍
王昱是
Sun
中国project研究院的
Java
project师,如今的主要负责全球合作伙伴的技术支持。作为一名
Java
资深project师和架构师,王昱在
Java
的非常多领域都有多年的造诣,特别是在
Java
虚拟机、
J2EE
技术
(
包含
EJB, JSP/Servlet, JMS

Web services
等技术
)
、集群技术和
Java
应用性能调优上有着较为丰富的经验。以前多次在重要的
Java
会议发表演讲,并在国际著名的
Java
技术站 点发表文章。
 
资源链接
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/118157.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • JDK安全模块JCE核心Cipher使用详解

    JDK安全模块JCE核心Cipher使用详解目录JDK安全模块JCE核心Cipher使用详解前提Cipher初始化transformation(转换模式)的一些知识补充算法工作模式填充模式transformation小结Cipher的属性和方法Cipher的七个主要公有属性getInstance方法init方法wrap方法和unwrap方法update方法doFinal方法upda…

  • 初中英语语法(015)-附加疑问句

    初中英语语法(015)-附加疑问句附加疑问句附加疑问句由陈述句加简短附加问句构成,用以要求对方证实所述之事。附加疑问句主要有两种:一类是反意的附加疑问句,另一类是非反意附加疑问句。例如:1、附加疑问句句尾上升语调,表示询问。(1)YouarefromJapan,aren’tyou?-你来自日本,对吗?AreyoufromJapan?-这句话等同于上句回答方法也相同Yes/No,Iam/I…

  • PyCharm使用教程 — 5、PyCharm的基本配置「建议收藏」

    PyCharm使用教程 — 5、PyCharm的基本配置「建议收藏」PyCharm基础配置PyCharm安装完毕之后,我们需要修改一些常用的配置让视觉上更加享受,比如修改PyCharm的主题以及字体显示呢基础配置在File->Settings中进行修改主题在Settings中选择Appearance&Behavior->Appearance,如下图所示我个人习惯使用Darcula的黑色主题,除了默认的主题,我们也可以自己安装其他主题风格。如何安装主题主题可以从网站http://www.themesmap.com/上进行下

  • java中lambda表达式[通俗易懂]

    java中lambda表达式[通俗易懂]Java8(JDK1.8)中加入的lambda表达式Lambda的使用前提使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才…

  • 激活成功教程WEP密钥过程全解(上)[通俗易懂]

    激活成功教程WEP密钥过程全解(上)[通俗易懂]激活成功教程WEP密钥过程全解(上)

    2022年10月26日
  • 我的世界java手机_我的世界java手机版

    我的世界java手机_我的世界java手机版我的世界java手机版是一款非常好玩的模拟经营游戏,游戏非常的自由,你几乎可以在游戏中干任何事,你可以自己建造一个世界,或是制作一个像素版的动漫人物,你还可以探索这个世界,寻找资源,你还可以和好友一起在这里进行对抗,你还在等什么,赶快来体验吧!我的世界java手机版游戏特色没有华丽的画面,没有什么游戏特效但是它最大的优势就是在于它的游戏性玩家在一个完全开放的世界,可以完全按照自己的想法建造我的世界…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号