加密通讯聊天平台的设计与实现 - 以 WEB 平台为例_第1页
加密通讯聊天平台的设计与实现 - 以 WEB 平台为例_第2页
加密通讯聊天平台的设计与实现 - 以 WEB 平台为例_第3页
加密通讯聊天平台的设计与实现 - 以 WEB 平台为例_第4页
加密通讯聊天平台的设计与实现 - 以 WEB 平台为例_第5页
已阅读5页,还剩56页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

广东东软学院本科生毕业设计(论文)摘要21世纪是信息时代,随着计算机技术、多媒体技术和互联网技术的飞速发展,我们足不出户就能够联系到处于世界另一个角落的朋友,获取世界各地的信息。IM(InstantMessaging,即时通讯)应用已经成为人们交流沟通必备的工具。为国人熟知的就有QQ、weChat等。QQ和weChat等IM软件发展到今天,已经具备完善的通讯功能,简单易用且随时随地。但一些特定行业单位,比如国安部门、军队单位、军品研究生产单位或者有商业机密和研发机密的需求的一些公司,出于其安全性和保密性的要求,其办公网络是与Internet完全物理隔离的。那么为这些特定企业设计和开发即时通讯系统就显得非常重要和必要。本次毕业设计的加密通讯聊天平台,以AES加密算法和RSA加密算法相结合实现信息加密,论文具体介绍整个系统的设计与实现过程,使用Layer、BootstrapJQuery开发前端界面,SpringBoot搭建后端框架。关键词:SpringBoot;Shiro;WebSocket;AES加密;RSA加密 AbstractThe21stcenturyistheinformationage.Withtherapiddevelopmentofcomputertechnology,multimediatechnologyandInternettechnology,wecanreachfriendsinanothercorneroftheworldandgetinformationfromallovertheworldwithoutleavinghome.IM(InstantMessaging,instantmessaging)applicationhasbecomeanessentialtoolforpeopletocommunicate.QQandweChatarewell-knowntothepeople.WiththedevelopmentofIMsoftwaresuchasQQandweChattothisday,italreadyhasacompletecommunicationfunction,whichissimpleandeasytouse,anytime,anywhere.However,somespecificindustryunits,suchasnationalsecuritydepartments,militaryunits,militaryproductresearchandproductionunits,orsomecompanieswiththeneedsofcommercialsecretsandresearchanddevelopmentsecrets,becauseoftheirsecurityandconfidentialityrequirements,theirofficenetworkiscompletelyphysicallyisolatedfromtheInternetof.Soitisveryimportantandnecessarytodesignanddevelopinstantmessagingsystemsforthesespecificenterprises.ThegraduatedencryptedcommunicationchatplatformusesAESencryptionalgorithmandRSAencryptionalgorithmtoachieveinformationencryption.Thethesisspecificallyintroducesthedesignandimplementationprocessoftheentiresystem,usesLayerandBootstrapJQuerytodevelopthefront-endinterface,andSpringBootbuildstheback-endframework.Keywords:SpringBoot;Shiro;WebSocket;AES;RSA

目录第一章绪论 7第二章相关理论知识 92.1开发环境和技术选型 92.2SpringBoot 92.2.1SpringBoot简介 92.3ApacheShiro 102.3.1可靠的安全框架 102.3.2总结shiro的三板斧 102.4.3UsernamePasswordToken 112.4.4ShiroConfig配置类 112.4.5Realm 12第三章关键技术研究 123.1WebSocket 123.1.1WebSocket简介 123.1.2系统WebSocket事务图 133.1.3WebSocket工作原理 133.2加密算法 143.2.1AES加密 143.2.2RSA加密 15第四章系统功能需求 154.1注册/登录模块 154.1.1用户注册 154.1.2用户登录 164.1.3微信第三方登录 174.2通信模块 184.3加密模块 18第五章系统具体实现 195.1系统架构设计 195.2系统功能设计 205.3数据库设计 205.3.1数据库E-R图 205.3.2数据库表设计 215.4.2项目目录 255.5关键代码实现 265.5.1Shiro实现免密登录 265.5.2微信扫码登录 365.5.4AES加密实现 455.5.5项目演示截图 48第六章项目总结 52参考文献 52致谢 53第一章绪论研究背景和意义1946年2月14日,世界上第一台通用计算机“ENIAC”在宾夕法尼亚大学被发明出世。1969年6月,世界上第一个采用分组交换技术组件的网络诞生,即“阿帕网”,后又发展为今天的互联网。随着计算机技术、多媒体技术和互联网技术的快速发展,改变了人与人之间从古时的车马邮件,飞鸽传书的通讯方式,人们之间的沟通方式更加多元。IM(InstantMessaging,即时通讯)因应而生并飞速发展。IM系统允许用户可以随时使用网络实地通信,即时性是其最大的优点,只要双方同时在线,即可实现交流通讯。众所周知,18至19世纪是蒸汽时,一切发展的动力源于蒸汽;19至20世纪是电气时代,电的发现以及电机等以电驱动的各类设备的发明与使用,推动着整个世界的生产迈向自动化,机械化。20至21世纪无疑就是信息时代,随着计算机技术、多媒体技术和互联网技术的飞速发展,我们足不出户就能够联系到处于世界另一个角落的朋友,获取世界各地的信息。IM系统已经成为人们交流沟通必备的工具。第一个即时通讯工具是几个以色列青年在1996年11月发明的。ICQ是英文ISEEKYOU我找你的谐音。因为中国接入互联网年份较晚,所以ICQ并不为中国普通大众所熟知。与ICQ有着异曲同工的QQ,则是家喻户晓的的IM产品,其优良的软件性能和免费特性,深受用户喜爱。随后诞生的微信,亦长江后浪推前浪。QQ和微信已是中国人民不可或缺的通讯工具。QQ、微信、阿里旺旺等通讯软件都是基于用户安装客户端软件,即C/S架构而存在。虽然之后推出了网页网版,但阉割了其大部分功能,要想有良好的用户体验,还是需要下载并安装其软件到电脑里或者手机里。如果设备里没有安装软件,或者要联系的人没有安装软件,那么就就会有一个等待安装的限制,便利性受到约束。而且没一次软件新功能的推出,都需要用户去升级软件,很大程度上并不能照顾到所有用户。随着网页WEB技术的发展,更多的软件脱离了C/S架构模式,转为B/S架构。而WebSocket的出现,也为IM系统设计成B/S架构提供了可能。采用B/S架构的IM工具,用户就可以摆脱下载软件安装软件才能使用软件的桎梏,只要电脑里、手机里有可以访问网页的浏览器,即可以使用其进行通讯交流,不必考虑设备差异。当下互联网WEB应用在市场的比重占有率逐步升高,且随着移动互联的发展,WEB应用的多终端兼容实现的优势也充分体现。相较于传统C/S架构,B/S架构有以下优点:轻量化开发、易维护更新、受众面广、简化用户使用流程、界面美观、良好的兼容性等。设计思路虽说是加密聊天通讯工具,但其功能实现也不外乎是传统IM工具的所应具备的能力。如基本聊天功能,用户登录与注册功能,文件传输功能等。基于功能实现属性,我将分为以下几个模块:用户注册/登录模块、私聊/群聊模块、日志模块、系统模块、公共文件模块、加密功能模块、好友模块、个人信息模块。其中聊天模块主要实现P2P私人聊天和建群群聊功能,文件和图片传输功能。该系统后端将基于SpringBoot框架开发,技术选型为:Java,MyBatis,WebSocket,WebRTC等;前端页面设计基于Bootstrap4,Jquery,Layer等;数据库选用Mysql5.7;版本控制即打包使用Maven;加密功能模块暂定为AES加密和RSA加密双结合实现信息加密处理,具体过程为:聊天信息为AES对称加密,但要对AES密钥用对方的RSA公钥加密。双方在得到加密信息后,需要用自己的密钥解密被RSA加密过的AES密钥,并用这个AES密钥解密被AES加密的信息。这样一来,传输的数据都是密文,且只有秘钥才能解密。以此来达到更为安全的加密效果。论文整体结构结构安排如下:第一章:绪论。介绍本系统的研究背景和意义,简述系统内容和结构安排第二章:相关理论知识。对系统开发中相关技术进行介绍,主要包括、SpringBoot、Maven、Shiro、Redis等第三章:关键技术研究,对系统关键技术WebSocket、AES加密技术、RSA加密技术进行叫详细说明第四章:对系统功能模块进行图例演示,阐述系统概要设计第五章:系统实现第六章:项目演示和总结第七章:文献综述第二章相关理论知识2.1开发环境和技术选型操作系统:Windows10操作系统;数据库:mysql-5.7;Java环境:JDK1.8;浏览器:MicrosoftEdgeBeta开发工具:IntelliJIDEA2019.2.3x64、Navicat12forMySQL系统环境:JavaEE8、Servlet3.0、ApacheMaven3主框架:SpringBoot2.0、SpringFramework5.0、ApacheShiro1.4持久层:ApacheMyBatis3.4、AlibabaDruid1.1视图层Bootstrap4、Thymeleaf3.02.2SpringBoot2.2.1SpringBoot简介SpringBoot是一个java开发框架,它简化用Spring开发应用的时候,繁琐的搭建过程。SpringBoot其实不是一个全新的产品,我们可以从它身上看到前辈SSH和SSM的影子。它默认配置了很多框架的使用方式,例如Mybatis、Shiro等等,就像mave可以整合所有的jar包一样2.2.2为什么使用SpringBootSpringBoot之前SSM框架或其他Spring框架,在整合其他功能框时都需要配置文件,这导致项目中的配置文件越来越多,内容越来越繁杂。项目中常常因为配置文件出错的原因,导致项目运行出错。SpringBoot的出现,解决开发框架的繁杂配置。SpringBoot约定大于配置的理念,使它能快速整合第三方框架,减少甚至不需要配置文件。对于一个开发团队来说,极大减少工作成本、更加利于开发,且后期维护也更加简单。2.3ApacheShiro2.3.1可靠的安全框架ApacheShiro是一个Java安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。Authentication的身份认证/登录,验证某个已认证的用户是否拥有某个权限;SessionManager:会话管理,用户登录后在没有退出之前,它的所有信息都在会话中。对Web开发十分友善;Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;我的认知是:Shiro是一套安全认证和权限管理框架,而不是一个取之能用的工具。既然是框架,Shiro就不会去主动维护用户、维护权限;简而言之,架子人家给搭好了,而登录验证逻辑,权限赋予逻辑等等之类“里子”需要我们自己去填2.3.2总结shiro的三板斧一个完整的Shiro业务,必须包括Subject、SecurityManager、Realm,我称之为shiro的三板斧,打完即收工:Subject:主体,代表了当前“用户”;所有Subject都会绑定到SecurityManager,且与Subject的所有交互都会委托给SecurityManager;Subject更多只是一个门面作用;SecurityManager才是实际的执行者;SecurityManager:安全管理器;所有与安全有关的操作都会与SecurityManager交互;可以看出它是Shiro的核心;Realm:域,谁来负责和数据库进行交互呢?Realm!谁来给用户做身份认证和赋予权限呢?Realm!也就是说SecurityManager要做的操作,其实都是交于Realm的2.4.3UsernamePasswordToken实现过程中我们可能会碰到这个家伙,它用于将用户输入的用户名和密码封装成token;普通的登录无非就是输入账号密码。可以通过调用getPrincipal()方法来获取token中的用户名并以此来进行登录验证;实质就是通过调用本地getUserName()方法直接获取存储在token中的username。credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等;可以通过getCredentials()方法来获取token中的密码,该方法与获取用户名的方式一致,调用本地getPassword()方法获取存储在token中的密码由此可见UsernamePasswordToken的重要性;这个类同时继承了HostAuthenticationToken及RememberMeAuthenticationToken,因此UsernamePasswordToken能够存储客户端的主机地址以及确定token是否能够被后续的方法使用(即登录时的‘“记住我”’)。出信息安全考虑,验证完成后通常会调用clear方法清除token数据,其中对用户密码(password)的处理较为特殊,UsernamePasswordToken不是单纯地:“password=null”,而是先将password引用对应的char数组的值全部置0,然后断开调用。2.4.4ShiroConfig配置类shiro做什么?怎么做?很大一部分(甚至可以说完全)由shiroConfig约定而为。简单点,shiroConfig就是shiro一个配置类,类似于以前的xml。林林总总有个多配置,个人觉得以下几个配置在配置类中比较重要:安全管理器SecurityManager、shiro拦截器、自定义Realm(后期要做第三方)、session管理器。SecurityManager在前边讲过,作为shiro的核心,主要协调Shiro内部的各种安全组件。我在使用过程中,接触到的主要是用来配置realmshiro拦截器(shiroFilter)**可以用设置各个页面的访问权限,一般在为进过shiro认证之前,这里只会放行登录/注册操作页面,错误页面,登出页面和一些静态文件。2.4.5RealmShiro的认证和授权操作,是由realm类来实现的;一般这个类是自己自定义的。自定义的Realm类要继承AuthorizingRealm类,并且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。第三章关键技术研究3.1WebSocket3.1.1WebSocket简介WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层,和HTTP一样基于TCP传输。但WebSocket是全双工传输形式,也就是说可以实现客户端和服务器之间双向交流,客户端实时了解服务器数据变化,这种形式特别适合用来做聊天程序。WebSocket最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,这种情况下可以实现:较少的控制开销、更强的实时性、保持连接状态、数据格式比较轻量,性能开销小,通信高效。建立在TCP协议之上,服务器端的实现比较容易。3.1.2系统WebSocket事务图3.1.3WebSocket工作原理WEB服务端开启WebSocket服务并等待客户端用户连接。浏览器创建WebSocket类对象并向服务端请求连接。WEB服务器触发WebSocketService的createWebSocketInbound方法并接受数据,创建对应onOpen方法。WEB服务器判断连接是否创建成功,如失败执行步骤5,成功执行步骤6连接创建失败,触发客户端error回调函数,结束WebSocket连接连接创建成功,触发客户端onopen回调函数,完成WebSocket连接客户端通过send方法发送数据到WEB服务器WEB服务器触发onMessage方法接受客户端数据WEB服务器通过sendMessage方法像客户端发送数据客户端触发onMessage回调函数接收数据并进行相关操作客户端通过调用close方法关闭WebSocket连接服务器触发onClose方法进行关闭连接操作,结束WebSocket连接3.2加密算法3.2.1AES加密AES是最流行的对称加密算法,对信息的加密和解密用的是同一套密钥。加密之初会把文件切分成一个个128位,的小块进行加密。如果文件大小不是16字节的整数倍,那就要在末尾添加一些数据凑够。AES有两个输入一个输出,输出是密文,输入自然是密钥和明文。输出是明文,输入自然就是密钥和密文。当然具体的加解密过程十分复杂。AES是非常安全的,最短的密钥也会有128位,大部分密钥长度会比128位大。密钥越长也就越安全,但随之加解密的速度就越慢。。AES加密非常常用,很多CPU在硬件层面上都会支持AES的加解密,https就是使用AES来进行数据的加密的,因为AES是TLS和SSL标准的一部分。3.2.2RSA加密对称加密和非对称加密是密不可分的。AES是对称加密,而RSA则是非对称加密算法。如果没有RSA算法,现在的网络世界可以说毫无安全可言。ssh协议也是基于RSA加密算法才能确保通讯是加密的,可靠的。非对称加密算法的加密解密过程是这样的:1)用户A使用RSA算法生成两把密钥,就是公钥和私钥。顾名思义,公钥就是公开的,私钥就是自己保存的。2)用户B想对用户A发送加密信息,用户B可以轻松获取到用户A的公钥,然后用它对信息加密。3)用户A得到加密后的信息,用自己的私钥解密。这样每个人都可以利用公钥发送加密消息,而只有拥有秘钥的接收方才能解读密文看到原消息。第四章系统功能需求我将系统总体功能分为以下几个模块:用户登录/注册、用户信息更改、头像文件上传、在线多人群聊、在线私聊、系统日志记录、聊天内容加密传输。4.1注册/登录模块4.1.1用户注册用户注册抛弃传统的用户自由定义帐号密码进行注册,改为现流行的手机号注册新用户,即用户通过手机号接收短信验证码实现注册,因为手机号注册均为个人实名注册,这就保证了帐号的唯一性和真实性。方便用户维护和管理;具体流程为下图:4.1.2用户登录短信登录:因为用户注册的时候用的是手机号,用户在注册成功后,可以通过手机号接收登录验证码进行登录。帐号密码登录:用户在注册时设置的密码,即用户的登录密码,用户可以通过手机号加登录密码进行登录。第三方登录:用户可以给帐号绑定第三方帐号,实现第三方帐号登录4.1.3微信第三方登录第三方登录我选用应用较为广泛的微信扫码登录。即帐号可以和用户自己的微信帐号进行绑定,实现微信扫码登录本IM系统。在这个项目中,微信第三方登录的关键点,即用户的系统账号和微信号的绑定。总体可以分为一下三个步骤:对于系统账户与微信的绑定,我的思路是:若是没有绑定过微信的用户扫码登录,即提示其先账号密码登录,系统自动更新Auth表信息;若未未注册用户,则提示其注册,注册时自动更新第三方登录表信息。但扫码登录只是登录的一种,更多的时候用户只是执行简单的登录或者注册;所以我引入了redis服务。​ 当一个用户是先扫码再登录/注册的,我会在redis保存其openId和unionId信息,执行登录/注册操作时,先去redis取值,如果存在openId和unionId,说明用户此时是在执行绑定微信操作,绑定微信操作完成后清除redis中的openId和unionId信息。这样就可以很简单的区分用户是在进行普通的登录注册或者绑定微信的登录注册了。​ 怎么确保每一个扫码绑定微信的用户,能从redis拿到属于自己的openId和unionId呢?可以用第一步请求到的code,开放平台给的微信授权临时票据。结合code作为对应的openId和unionI在redis的key,可以确保每一个用户拿到对应的唯一的openId和unionId。4.2通信模块在线多人群聊、在线私聊的实现相对简单,在WebSocket连接成功的情况下,WebSocket基本可以完成以上功能。用户的聊天记录信息都会在加密后实时保存在Redis服务器中,当用户退出系统后,系统将会获取Redis中的聊天信息,保存到数据库中。用户下次登录,服务器将从数据库获取对应的加密聊天记录,解密后回显指用户界面。具体流程图如下4.3加密模块加密功能模块暂定为AES加密和RSA加密双结合实现信息加密处理,即其登录信息和聊天信息为AES对称加密,但要对其解密密钥实现RSA加密传输,即客户端和服务器通过RSA算法各自生成一套公钥和私钥,并交换公钥。信息被AES算法加密,AES的key被RSA算法加密。双方在得到加密信息后,需要用对方的公钥解密被RSA加密过的key,并用这个key解密被AES加密的信息。这样一来,传输的数据都是密文,且只有秘钥才能解密。以此来达到更为安全的加密效果。第五章系统具体实现5.1系统架构设计系统采用经典的B/S架构体系,即三层架构体系。分别为表现层(UI)、业务逻辑层(BBL)和数据访问层(DAL)。而项目的设计模式也是较为传统实用的MVC模式,即Model(模型),View(视图),Controller(控制)。1)表现层:主要负责展示系统业务和数据,实例化用户的业务操作;较为通俗的说法即前端页面展示。我将使用HTML5、Jquery、Bootstrap和Thymeleaf实现。2)业务逻辑层:作为连接前端和数据库数据的中间媒介,业务逻辑层极为重要,它将表现层,即前端用户的操作行为和数据请求进行处理,与数据库做数据交互,将处理结果返回表现层。起着中间枢纽的重要作用。MVC设计模式也在该层有极大的体现3)数据访问层:原始数据的操作层,对原始数据进行操作,具体的功能是为表现层以及业务逻辑层提供需要得数据服务。5.2系统功能设计5.3数据库设计5.3.1数据库E-R图用户与系统日志用户与用户日志用户和在线用户表5.3.2数据库表设计表1用户表用户表(Employee)字段名意义数据类型宽度是否可为空主键id用户idvarchar64否√login_name登录账户varchar100否nick_name用户昵称varchar100是salt盐varchar100是password用户密码varchar100是phonenumber电话号码varchar100是sex性别char10是age年龄int100是head_profile头像文件char11是profile个人简介varchar255是last_time最后登录时间varchar64是status帐号状态char2是del_flag删除标志char2是create_time注册时间varchar64是表2在线用户表在线用户表(user_online)字段名意义数据类型宽度是否可为空主键sessionId用户会话idvarchar50否√login_name登录账号varchar50否ipaddr登录IP地址varchar50是browser浏览器类型varchar50是os操作系统varchar50是status在线状态varchar10是last_time最后访问时间varchar50是create_tiem创建时间varchar50是表3系统日志表在线用户表(logininfor)字段名意义数据类型宽度是否可为空主键info_id访问IDvarchar50否√login_name登录账号varchar50否ipaddr登录IP地址varchar50是browser浏览器类型varchar50是os操作系统varchar50是status登录状态varchar10是msg提示消息varchar50是login_time创建时间varchar50是表4第三方登录表第三方登录表(auth_login)字段名意义数据类型宽度是否可为空主键id主键varchar64否√open_id第三方标识varchar200否union_id用户统一标识varchar200是app_type第三方平台varchar100是user_id用户idvarchar64是status账号状态char2是create_time创建时间datetime0是5.4系统代码实现5.4.1引入jar包===========================代码开始==========================<java.version>1.8</java.version><thymeleaf.version>3.0.11.RELEASE</thymeleaf.version><thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version><druid.version>1.1.14</druid.version><commons.lang3.version>3.8</commons.lang3.version><mysql.version>5.1.47</mysql.version><mybatis.version>2.1.1</mybatis.version><shiro.version>1.4.0</shiro.version><snakeyaml.version>1.25</snakeyaml.version><userAgentUtils.version>1.21</userAgentUtils.version><spring.session>1.3.5.RELEASE</spring.session><fastjson.version>1.2.68</fastjson.version><gson.version>2.8.6</gson.version><hettpclient.vesion>4.5.10</hettpclient.vesion><commons-pool2.version>2.6.1</commons-pool2.version><hutool-log.version>5.3.0</hutool-log.version><shiro.version>2.0.0</shiro.version><hutool-core.version>5.2.0</hutool-core.version><shiro-ehcache.version>1.5.0</shiro-ehcache.version><aspectjweaver.version>1.9.5</aspectjweaver.version>===========================代码开始==========================5.4.2项目目录5.5关键代码实现5.5.1Shiro实现免密登录由于项目要集成第三方登录,我在shiro原有的账号密码登录认证的基础上,多写了一个免密登录的Realm,当用户授权第三方拿到身份认证后,可以跳过原有的账号密码认证来登录系统,实现第三方登录。可以从写一个登录模式枚举开始===========================代码开始==========================/**

*登录模式

*@authorpong

*/

publicenumLoginType{

//密码登录

PASSWORD("Normal"),

//免密登录

NOPASSWD("Free");

privateStringcode;

//状态值

LoginType(Stringcode){

this.code=code;

}

publicStringgetCode(){

returncode;

}

}===========================代码结束==========================原有UsernamePasswordToken已经不满足我们区分登录模式的需求,我们可以继承UsernamePasswordToken重写一个token封装类CustomToken===========================代码开始==========================/***自定义令牌**@authorpong*/publicclassCustomTokenextendsUsernamePasswordToken{privateStringloginType;publicCustomToken(){super();}/***@paramloginName用户名*@parampassword密码*@paramloginType登录模式*@paramrememberMe*/publicCustomToken(StringloginName,Stringpassword,StringloginType,booleanrememberMe){super(loginName,password,rememberMe);this.loginType=loginType;}publicStringgetType(){returnloginType;}publicvoidsetType(StringloginType){this.loginType=loginType;}}===========================代码结束==========================一个账号密码登录的常规Realm===========================代码开始==========================/***帐号密码登录*@authorpong*/publicclassNormalRealmextendsAuthorizingRealm{privatestaticfinalLoggerlog=LoggerFactory.getLogger(NormalRealm.class);/***授权*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionarg0){//授权业务}/***登录认证*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//认证业务}}===========================代码结束==========================一个免密登录的Realm===========================代码开始==========================/***免密登录realm*@authorpong*/publicclassFreeRealmextendsAuthorizingRealm{PrivatestaticfinalLoggerlog=LoggerFactory.getLogger(FreeRealm.class);/***授权*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionarg0){//授权业务}/***免密登录认证*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{StringloginName=(String)token.getPrincipal();SysUseruser=null;if(StringUtils.isNotNull(loginName)){user=userService.selectUserByLoginName(loginName);}SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(user,user.getPassword(),getName());returninfo;}}===========================代码结束==========================一般Shiro只需要一个Realm就够,多Realm存在时,token为了能够被认证通过,会自动遍历所有Realm,当有Realm无法认证这个token的时候系统就会异常报错。因此,要写一个选择器,来让token找到属于自己的Realm。Shiro自带了一个多Realm选择器ModularRealmAuthenticator,为满足项目需求,同样要进行继承重写。===========================代码开始==========================/***用于指定登录使用对应Realm的控制器**@authorpong*/publicclassUserModularRealmAuthenticatorextendsModularRealmAuthenticator{@OverrideprotectedAuthenticationInfodoAuthenticate(AuthenticationTokenauthenticationToken)throwsAuthenticationException{System.out.println("UserModularRealmAuthenticator:methoddoAuthenticate()execute");//判断getRealms()是否返回为空assertRealmsConfigured();//强制转换回自定义的CustomizedTokenCustomTokenuserToken=(CustomToken)authenticationToken;//登录类型StringloginType=userToken.getType();//所有RealmCollection<Realm>realms=getRealms();//登录类型对应的所有RealmList<Realm>typeRealms=newArrayList<>();for(Realmrealm:realms){if(realm.getName().contains(loginType)){typeRealms.add(realm);}}//判断是单Realm还是多Realmif(typeRealms.size()==1){System.out.println("doSingleRealmAuthentication()execute");returndoSingleRealmAuthentication(typeRealms.get(0),userToken);}else{System.out.println("doMultiRealmAuthentication()execute");returndoMultiRealmAuthentication(typeRealms,userToken);}}}===========================代码结束==========================其中,这段遍历代码就是根据登录模式分配对应Realm的操作===========================代码开始==========================List<Realm>typeRealms=newArrayList<>();for(Realmrealm:realms){if(realm.getName().contains(loginType)){typeRealms.add(realm);}}===========================代码结束==========================最后,就是在shrioConfig配置上这两个自定义Realm和Realm选择器===========================代码开始==========================/***系统自带的Realm管理,主要针对多realm**/@BeanpublicModularRealmAuthenticatormodularRealmAuthenticator(){//重写ModularRealmAuthenticatorUserModularRealmAuthenticatormodularRealmAuthenticator=newUserModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(newAtLeastOneSuccessfulStrategy());returnmodularRealmAuthenticator;}/***密码登录Realm*/@BeanpublicNormalRealmnormalRealm(){NormalRealmnormalRealm=newNormalRealm();returnnormalRealm;}/***免密登录Realm*/@BeanpublicFreeRealmfreeRealm(){FreeRealmfreeRealm=newFreeRealm();returnfreeRealm;}/***安全管理器*/@BeanpublicSecurityManagersecurityManager(NormalRealmuserRealm){DefaultWebSecurityManagersecurityManager=newDefaultWebSecurityManager();...//设置realm.securityManager.setAuthenticator(modularRealmAuthenticator());List<Realm>realms=newArrayList<>();//密码登录realmrealms.add(normalRealm(getEhCacheManager()));//免密登录realmrealms.add(freeRealm(getEhCacheManager()));securityManager.setRealms(realms);...}===========================代码结束==========================5.5.2微信扫码登录原系统使用的登录认证框架是ApaceShiro,而网上现大部分第三方接微信登录都是用OAuth2,微信自己也推荐用OAuth2;两者存在一定差异。但不能为了接一个登录,去改变系统的架构;所以只能转变思路:用户授权微信登录→从微信平台拿到用户标识→如果这个标识和系统用户表表示相匹配→给予用户免密登录权限,以此来实现第三方登录。参数是否必须说明appid是应用唯一标识,在开发平台注册审核通过后会得到redirect_uri是前面多次提到的回调地址,一般是网站域名response_type是填codescope是网页应用目前仅填写snsapi_loginstate否用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验返回说明用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数:redirect_uri?code=CODE&state=STATE若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数:redirect_uri?state=STATE这里又有一个小坑,很多时候我们redirect_uri填注册时的回调地址是拿不到code的。具体应该是:微信开放平台上注册时redirect_uri填网站实际访问域名,如获取code的代码中redirect_uri填的是域名/方法,如/login具体带什么方法因项目而异,像我开发的项目,未登录访问肯定会被定向到登录页,所以我带上了/login。接下来就是请求accessToken我们需要用上一步获取到的code来获取accessToken/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code 参数是否必须说明appid是应用唯一标识,在微信开放平台提交应用审核通过后获得secret是应用密钥AppSecret,在微信开放平台提交应用审核通过后获得code是填写第一步获取的code参数grant_type是填authorization_code代码实现===========================代码开始==========================/***获取accessToken*@paramcode*@return*/@OverridepublicMap<String,Object>getAccessToken(Stringcode){Map<String,Object>map=newHashMap<>();try{Stringurl="/sns/oauth2/access_token";Stringparam="appid="+appId+"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code";Stringresult=HttpUtils.sendGet(url,param);JSONObjectjsStr=JSONObject.fromObject(result);StringopenId=jsStr.getString("openid");StringunionId=jsStr.getString("unionid");map.put("openId",openId);map.put("unionId",unionId);}catch(Exceptione){Stringmsg="获取微信accessToken失败";Map<String,Object>error=newHashMap<>();error.put("error",msg);returnerror;}returnmap;}===========================代码结束==========================通过openId和unionId确定auth表中绑定的用户id,根据用户id从系统用户表查找对应用户,如果存在该用户,则授予免密登录权限,至此,接微信第三方登录基本实现。===========================代码开始========================== /***微信扫码登录*@paramcode*@return*/@ResponseBody@ApiImplicitParam(paramType="String",value="code",name="code")@PostMapping("/loginByWeChat")publicAjaxResultajaxLoginByWeChat(Stringcode){if(!code.isEmpty()){//获取accessTokenMap<String,Object>accessTokenMap=weChatLoginService.getAccessToken(code);if(!accessTokenMap.isEmpty()){StringuserOpenId=(String)accessTokenMap.get("openId");StringunionId=(String)accessTokenMap.get("unionId");//根据openId+unionId确定用户AuthLoginInfouser=weChatLoginService.selectAuthUser(userOpenId,unionId);if(user!=null){SysUsersysUser=userService.selectUserById(Long.valueOf(user.getUserId()));//免密登录returnajaxLoginNoPwd(sysUser.getLoginName(),false);}else{redisService.set("openId",userOpenId);redisService.set("unionId",unionId);returnAjaxResult.error("该用户尚未绑定微信,请输入帐号密码登录!登录成功自动完成绑定!!");}}returnAjaxResult.error("登录失败!");}returnAjaxResult.error("登录失败!");}===========================代码结束==========================对于系统账户与微信的绑定,我的思路是:若是没有绑定过微信的用户扫码登录,即提示其先账号密码登录,系统自动更新Auth表信息;若未未注册用户,则提示其注册,注册时自动更新Auth表信息。但扫码登录只是登录的一种,更多的时候用户只是执行简单的登录或者注册;所以我引入了redis服务。​ 当一个用户是先扫码再登录/注册的,我会在redis保存其openId和unionId信息,执行登录/注册操作时,先去redis取值,如果存在openId和unionId,说明用户此时是在执行绑定微信操作,绑定微信操作完成后清除redis中的openId和unionId信息。这样就可以很简单的区分用户是在进行普通的登录注册或者绑定微信的登录注册了。​ 怎么确保每一个扫码绑定微信的用户,能从redis拿到属于自己的openId和unionId呢?可以用第一步请求到的code,开放平台给的微信授权临时票据。结合code作为对应的openId和unionI在redis的key,可以确保每一个用户拿到对应的唯一的openId和unionId;至此,系统对接微信第三方登录功能基本完成5.5.3WebSocket的引入SpringBoot对Websocket的集成程度很高,这里直接上代码。首先是一个配置文件。===========================代码开始==========================@Configuration

publicclasswebSocketConfig{

@Bean

publicServerEndpointExporterserverEndpointExporter(){

returnnewServerEndpointExporter();

}

}===========================代码结束==========================WebSocket服务===========================代码开始==========================/***websocket服务*@author:pong*/@ServerEndpoint(value="/chatServer",configurator=HttpSessionConfigurator.class)publicclassChatServer{//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。privatestaticintonlineCount=0;//与某个客户端的连接会话,需要通过它来给客户端发送数据privatestaticCopyOnWriteArraySet<ChatServer>webSocketSet=newCopyOnWriteArraySet<ChatServer>();privateSessionsession;privateStringuserid;privateStringkey;privateHttpSessionhttpSession;privatestaticListlist=newArrayList<>();privatestaticMaproutetab=newHashMap<>();/***连接建立成功调用的方法*@paramsession可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据*/@OnOpenpublicvoidonOpen(Sessionsession,EndpointConfigconfig){//代码块}/***连接关闭调用的方法*/@OnClosepublicvoidonClose(){webSocketSet.remove(this);//从set中删除subOnlineCount();//在线数减1list.remove(userid);//从在线列表移除这个用户routetab.remove(userid);Stringmessage=getMessage("["+userid+"]离开了聊天室,当前在线人数为"+getOnlineCount()+"位","notice",list);broadcast(message);//广播}/***接收客户端的message,判断是否有接收人而选择进行广播还是指定发送*@param_message客户端发送过来的消息*/@OnMessagepublicvoidonMessage(String_message){Stringdecrypt="";try{//代码块}catch(Exceptione){e.printStackTrace();}JSONObjectchat=JSON.parseObject(decrypt);JSONObjectmessage=JSON.parseObject(chat.get("message").toString());if(message.get("to")==null||message.get("to").equals("")){//如果to为空,则广播;如果不为空,则对指定的用户发送消息broadcast(decrypt);}else{String[]userlist=message.get("to").toString().split(",");singleSend(decrypt,(Session)routetab.get(message.get("from")));//发送给自己,这个别忘了for(Stringuser:userlist){if(!user.equals(message.get("from"))){singleSend(decrypt,(Session)routetab.get(user));//分别发送给每个指定用户}}}}/***发生错误时调用*@paramerror*/@OnErrorpublicvoidonError(Throwableerror){error.printStackTrace();}/***广播消息*@parammessage*/publicvoidbroadcast(Stringmessage){for(ChatServerchat:webSocketSet){try{chat.session.getBasicRemote().sendText(message);}catch(IOExceptione){e.printStackTrace();continue;}}}/***对特定用户发送消息*@parammessage*@paramsession*/publicvoidsingleSend(Stringmessage,Sessionsession){try{session.getBasicRemote().sendText(message);}catch(IOExceptione){e.printStackTrace();}}/***组装返回给前台的消息*@parammessage交互信息*@paramtype信息类型*@paramlist在线列表*@return*/publicStringgetMessage(Stringmessage,Stringtype,Listlist){代码块}publicintgetOnlineCount(){returnonlineCount;}publicvoidaddOnlineCount(){ChatServer.onlineCount++;}publicvoidsubOnlineCount(){ChatServer.onlineCount--;}}===========================代码结束==========================5.5.4AES加密实现AES算法在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个独立的明文块,每一个明文块长度128bit进行独立加密再拼接。如果一个明文块长度不够128bit将会被填充至128bit。AES的工作模式,体现在把明文块加密成密文块的处理过程中。AES加密算法提供了五种不同的工作模式:ECB、CBC、CTR、CFB、OFB。加密解密的模式必须一致!简单封装一下代码:===========================代码开始==========================/***前端*/varaesUtil={//获取key,genKey:function(length=16){letrandom="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";letstr="";for(leti=0;i<length;i++){str=str+random.charAt(Math.random()*random.length)}returnstr;},//加密encrypt:function(plaintext,key){if(plaintextinstanceofObject){//JSON.stringifyplaintext=JSON.stringify(plaintext)}letencrypted=CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(plaintext),CryptoJS.enc.Utf8.parse(key),{mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7});returnencrypted.toString();},//解密decrypt:function(ciphertext,key)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论