java实现单点登录

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

单点登录零、用户模块内容以及设计一、问题的提出二、单点登录SSO1.1 什么是单点登录1.2 单点登录的技术实现机制二、远程调用方式RPC三、JWT的使用3.1 session的使用原理3.2 JWT介绍3.3 JWT原理3.4 JWT的使用四、CAS实现单点登录的原理四、CAS的安装和代码的实现4.1 CAS Server的安装4.2 编写客户端4.3 使用springboot集成CAS客户端

零、用户模块内容以及设计

用户模块一般包含注册、认证、鉴权等功能。

注册一般是指添加用户信息。

认证登录和判断是否登录。

鉴权是验证用户的权限是否符合要求。

一般设计用户-----角色-----权限多对多一共5张表。

特殊设计相同角色可能由于用户的特殊性给与不同的权限。用户-----权限组角色----权限。用户最终跟权限挂钩权限组只是作为一个模板身份。

一、问题的提出

场景一企业里有一系列的系统由于开发的时间有先后导致出现了OA、ERP、CRM等系统每个都有独立的登录模块用户在使用系统的时候需要登录多个系统并且每个系统都不能直接跳转到其他系统中。

提出问题能否在登录了其中一个系统后其他的系统都不需要登录直接认证那么系统之间也就可以很容易跳转和相互调用了。

场景二某个大型的企业收购了另外一个企业本来每一个企业都有其用户数据库。

提出问题能否直接将被收购的企业的网站嵌入到原来的网站并且无论是哪个企业的用户登录其中一个系统后就不需要登录另一个系统可以直接使用。

场景三现在很多企业都是使用微服务架构来实现自己的系统。

提出问题多个服务之间如何保证登录了其中一个服务后就能够直接使用其他的服务而不需要再次登录。

二、单点登录SSO

1.1 什么是单点登录

单点登录Single Sign On简称为 SSO是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中用户只需要登录一次就可以访问所有相互信任的应用系统。

较大的企业内部一般都有很多的业务支持系统为其提供相应的管理和IT服务。例如财务系统为财务人员提供财务的管理、计算和报表服务人事系统为人事部门提供全公司人员的维护服务各种业务系统为公司内部不同的业务提供不同的服务等等。这些系统的目的都是让计算机来进行复杂繁琐的计算工作来替代人力的手工劳动提高工作效率和质量。这些不同的系统往往是在不同的时期建设起来的运行在不同的平台上也许是由不同厂商开发使用了各种不同的技术和标准。

如果举例说国内一著名的IT公司名字隐去内部共有60多个业务系统这些系统包括两个不同版本的SAP的ERP系统12个不同类型和版本的数据库系统8个不同类型和版本的操作系统以及使用了3种不同的防火墙技术还有数十种互相不能兼容的协议和标准你相信吗不要怀疑这种情况其实非常普遍。每一个应用系统在运行了数年以后都会成为不可替换的企业IT架构的一部分如下图所示

 

随着企业的发展业务系统的数量在不断的增加老的系统却不能轻易的替换这会带来很多的开销。其一是管理上的开销需要维护的系统越来越多。很多系统的数据是相互冗余和重复的数据的不一致性会给管理工作带来很大的压力。业务和业务之间的相关性也越来越大例如公司的计费系统和财务系统财务系统和人事系统之间都不可避免的有着密切的关系。

为了降低管理的消耗最大限度的重用已有投资的系统很多企业都在进行着企业应用集成EAI。企业应用集成可以在不同层面上进行例如在数据存储层面上的“数据大集中”在传输层面上的“通用数据交换平台”在应用层面上的“业务流程整合”和用户界面上的“通用企业门户”等等。事实上还用一个层面上的集成变得越来越重要那就是“身份认证”的整合也就是“单点登录”。

通常来说每个单独的系统都会有自己的安全体系和身份认证系统。整合以前进入每个系统都需要进行登录这样的局面不仅给管理上带来了很大的困难在安全方面也埋下了重大的隐患。下面是一些著名的调查公司显示的统计数据

 

另外使用“单点登录”还是SOA时代的需求之一。在面向服务的架构中服务和服务之间程序和程序之间的通讯大量存在服务之间的安全认证是SOA应用的难点之一应此建立“单点登录”的系统体系能够大大简化SOA的安全问题提高服务之间的合作效率。

1.2 单点登录的技术实现机制

单点登录的机制其实是比较简单的用一个现实中的例子做比较。颐和园是北京著名的旅游景点在颐和园内部有许多独立的景点例如“苏州街”、“佛香阁”和“德和园”都可以在各个景点门口单独买票。很多游客需要游玩所有的景点这种买票方式很不方便需要在每个景点门口排队买票钱包拿进拿出的容易丢失很不安全。于是绝大多数游客选择在大门口买一张通票也叫套票就可以玩遍所有的景点而不需要重新再买票。他们只需要在每个景点门口出示一下刚才买的套票就能够被允许进入每个独立的景点

单点登录的机制也一样如下图所示当用户第一次访问应用系统1的时候因为还没有登录会被引导到认证系统中进行登录1根据用户提供的登录信息认证系统进行身份效验如果通过效验应该返回给用户一个认证的凭据ticket2用户再访问别的应用的时候35就会将这个ticket带上作为自己认证的凭据应用系统接受到请求之后会把ticket送到认证系统进行效验检查ticket的合法性46。如果通过效验用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。

 

从上面的视图可以看出要实现SSO需要以下主要的功能

所有应用系统共享一个身份认证系统。统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较对用户进行登录认证认证成功后认证系统应该生成统一的认证标志ticket返还给用户。另外认证系统还应该对ticket进行效验判断其有效性。

所有应用系统能够识别和提取ticket信息要实现SSO的功能让用户只登录一次就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取通过与认证系统的通讯能自动判断当前用户是否登录过从而完成单点登录的功能。

常见的方式有

  1. 使用redis之类的实现session共享

  2. 使用jwt实现

  3. 使用CAS实现

  4. ...

二、远程调用方式RPC

指在一个项目中调用另一个项目中的服务。

【RMI】RMI是个典型的为java定制的远程通信协议 我们都知道在single vm中我们能够通过直接调用java object instance来实现通信那么在远程通信时假设也能依照这样的方式当然是最好了。这样的远程通信的机制成为RPCRemoteProcedure CallRMI正是朝着这个目标而诞生的。传输的标准格式是Java Object Stream基于Java串行化机制将请求的Java Object信息转化为流。传输协议是Socket。

特点为Java定制所以在Java中运行速度最快使用Java语言开发不能跨语言。通用性较弱。例如微服务dubbo就是采用的此方案。

【REST】采用http协议进行通信。例如微服务的springcloud就是采用此方案。

特点由于采用的通用协议也就意味着通用性比较强。基本上现在大多都采用此方案。

【WS】webservice。采用SOAPSimpleObject Access Protocol协议采用标准的xml格式传输数据。要求语言格式为WSDLwebservice describe language。

特点 使用xml格式通用性也很强。但是xml传输数据相对json来说体积比较大格式解析比较麻烦。早期用得很多。

【JMS】Java消息服务传输内容作为一个Message。

三、JWT的使用

3.1 session的使用原理

http协议是无状态的。无状态是指服务器不记录客户端的信息。借助session来确认是否同一个客户端发送的请求。

当客户端第一次访问服务器时请求中没有包含sessionid服务器会自动创建一个session并将该sessionid保存到客户端的cookie中age采用默认设置即关闭浏览器失效下一次客户端访问服务器时会将sessionid传输到服务器服务器将会验证该id是否合法并确认该id所关联的数据map。

【缺点】如果需要跨服务认证那么可能无法获取之前存在的session信息。不适合分布式环境除非进行session共享。

3.2 JWT介绍

在分布式环境中需要模拟session的使用过程此处采用第三方的框架JWT。

JWT,即JSON WEB TOKEN。是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的特别适用于分布式站点的单点登录SSO场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息该token也可直接被用于认证也可被加密。

3.3 JWT原理

参考JWT官方文档:JSON Web Tokens - jwt.io

3.4 JWT的使用

1、导入依赖

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.1</version>
</dependency>
​
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2、编写帮助类

public class TokenUtil {
    private static Long tokenExpireTime = 1000 * 60 * 30L;//  单位毫秒
    public static final String PRIVATEKEY = "privateKey";
    public static final String ACCESSTOKEN = "AccessToken";// 公私钥
    private static final String secretKey = "ueor82739sjsd234759jdfijosd289347sdjklfvjsxdr389wrksjdhfjksdyr9234yu89htsdkhfjksdhf83wy4hrsdjkhfsdjkh8i34wyuirfhsdjkfsxmnfbcvm,xcnskdhfriw3eyrikni12y391y238923y4y89dfhisfhsdjknfk23w4y598hfdjkfkjxcfbnisyer93we5rhkdjsfnjks"; // secret
​
    // 生成token
    public static String createToken(String userName){
        // 得到当前时间
        Date now = new Date();
        // 通过hs256算法以及secretKey得到Algorithm对象
        Algorithm algo = Algorithm.HMAC256(secretKey);
        String token = JWT.create()
                .withIssuer("qianfeng")
                .withIssuedAt(now)
                .withExpiresAt(new Date(now.getTime() + tokenExpireTime))
                .withClaim("userName", userName)//保存身份标识
                .sign(algo);
        return token;
    }
​
​
​
    /**
     * JWT验证
     * @param token
     * @return userName
     */
    public static String verifyJWT(String token){
        String userName = null;
        try {
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("qianfeng")
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            userName = jwt.getClaim("userName").asString();
        } catch (JWTVerificationException e){
            e.printStackTrace();
        }
        return userName;
    }
​
    public static void main(String[] args) {
        String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJxaWFuZmVuZyIsImV4cCI6MTYwNTYwMzc1NSwidXNlck5hbWUiOiJhZG1pbiIsImlhdCI6MTYwNTYwMTk1NX0.7mgYbJfCSqkauj0Vi7KJba96qHjk7i3CYYj4bCJF5FE";
        System.out.println(verifyJWT(jwt));
​
//        System.out.println(createToken("admin"));
    }
}

3、在登录时创建token并存入到cookie中在访问时加载cookie中的数据并发送到服务器验证。

四、CAS实现单点登录的原理

CAS 是 Yale 大学发起的一个开源项目旨在为 Web 应用系统提供一种可靠的单点登录方法CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。CAS 具有以下特点

【1】开源的企业级单点登录解决方案。

【2】CAS Server 为需要独立部署的 Web 应用。

【3】CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用)包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

从结构上看CAS 包含两个部分 CAS Server 和 CAS Client。CAS Server 需要独立部署主要负责对用户的认证工作CAS Client 负责处理对客户端受保护资源的访问请求需要登录时重定向到 CAS Server。下图是 CAS 最基本的协议过程

CAS原理

从结构上看CAS 包含两个部分 CAS Server 和 CAS Client。CAS Server 需要独立部署主要负责对用户的认证工作CAS Client 负责处理对客户端受保护资源的访问请求需要登录时重定向到 CAS Server

SSO单点登录访问流程主要有以下步骤

  • 访问服务SSO客户端发送请求访问应用系统提供的服务资源。

  • 定向认证SSO客户端会重定向用户请求到SSO服务器。

  • 用户认证用户身份认证。

  • 发放票据SSO服务器会产生一个随机的Service Ticket。

  • 验证票据SSO服务器验证票据Service Ticket的合法性验证通过后允许客户端访问服务。

  • 传输用户信息SSO服务器验证票据通过后传输用户认证结果信息给客户端。

四、CAS的安装和代码的实现

4.1 CAS Server的安装

CAS服务器安装

可以在下载GitHub - apereo/cas-overlay-template at 5.3

点击Download ZIP下载并解压

 

下载后将工程用开发工具打开使用maven进行打包为war

使用开发工具打开并使用maven的package命令打包成war文件

 

将cas.war部署到tomcat的webapps目录下后启动tomcat

在浏览器打开http://localhost:8080/cas 会出现登录页面默认用户名casuser密码Mellon

4.2 编写客户端

下载一个客户端示例代码GitHub - cas-projects/cas-sample-java-webapp: Sample Java web app protected by Java CAS client

新建一个Java web的maven项目将刚下载的示例项目中的pom.xml中的依赖复制到自己项目中导入对应的依赖将示例项目的web.xml中的内容复制到自己的项目的web.xml中并将示例项目中的index.jsp和logout.jsp复制到自己的项目中并修改web.xml中配置将cas服务端修改成自己配置的tomcat地址将客户端修改成自己的客户端的地址例如本项目web.xml中的配置

<filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://localhost:8080/cas</param-value>
        </init-param>
    </filter>
​
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
​
    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <!--<filter-class>org.jasig.cas.client.authentication.Saml11AuthenticationFilter</filter-class>-->
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>http://localhost:8080/cas/login</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://localhost:8080/demo</param-value>
        </init-param>
    </filter>
​
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <!--<filter-class>org.jasig.cas.client.validation.Saml11TicketValidationFilter</filter-class>-->
        <filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://localhost:8080/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://localhost:8080/demo</param-value>
        </init-param>
        <init-param>
            <param-name>redirectAfterValidation</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>useSession</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--
        <init-param>
            <param-name>acceptAnyProxy</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>proxyReceptorUrl</param-name>
            <param-value>/sample/proxyUrl</param-value>
        </init-param>
        <init-param>
            <param-name>proxyCallbackUrl</param-name>
            <param-value>https://mmoayyed.unicon.net:9443/sample/proxyUrl</param-value>
        </init-param>
        -->
        <init-param>
            <param-name>authn_method</param-name>
            <param-value>mfa-duo</param-value>
        </init-param>
    </filter>
​
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
​
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
​
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
​
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
​
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

启动项目运行项目的首页会出现以下问题

未配置https出现的问题

 

出现此问题的原因主要是CAS默认支持的服务器和客户端必须是https协议的但是我们刚写的项目中默认是http协议此处有两种办法解决1. 将刚刚的项目配置成https。2修改服务端配置让http也可以支持。此处为了简单起见直接采用第二种方式。实际生产环境中可以采取第一种方式。

修改配置在刚刚配置服务端的tomcat的webapps中找到cas项目修改cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json文件修改第三行。

#未修改前
"serviceId" : "^(https|imaps)://.*",
#修改成
"serviceId" : "^(https|http|imaps)://.*",

修改cas\WEB-INF\classes目录下的application.properties文件在最后加上如下内容

cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true

重启服务器端后再访问客户端的首页会提示登录

登录页

 

说明客户端和服务器都配置成功接下来需要验证客户端是否能够实现SSO效果

在tomcat的webapps中将客户端代码demo复制一份起名为demo1并修改web.xml中的demo为demo1

重启tomcat

在浏览器中打开http://localhost:8080/demo并开启一个新的标签页打开另一个项目http://localhost:8080/demo1

发现都会出现上图的登录页面在其中一个登录页面使用用户名casuser和密码Mellon进行登录会跳转到项目的首页然后在另一个标签中刷新发现不用再次登录也会跳转到首页说明单点登录效果成功。

4.3 使用springboot集成CAS客户端

接下来搭建springboot项目的客户端1

在新建的springboot项目的pom.xml添加如下依赖匹配对应的版本

<dependency>
    <groupId>net.unicon.cas</groupId>
    <artifactId>cas-client-autoconfig-support</artifactId>
    <version>2.1.0-GA</version>
</dependency>

修改application.properties文件

server.port=8088
#cas服务端的地址
cas.server-url-prefix=http://localhost:8080/cas
#cas服务端的登录地址
cas.server-login-url=http://localhost:8080/cas/login
#当前服务器的地址(客户端)
cas.client-host-url=http://localhost:8088
#Ticket校验器使用Cas30ProxyReceivingTicketValidationFilter
cas.validation-type=cas3

修改Application类添加注解@EnableCasClient

@EnableCasClient
@SpringBootApplication
public class TestcasApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestcasApplication.class, args);
    }
}

创建一个测试类

@RestController
public class CasController {
    @RequestMapping("/")
    public String hello(){
        return "hello, cas";
    }
}

打开浏览器启动http://localhost:8088也会出现登录页面可以跟前面的客户端一起测试效果

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java