Java开发
MyBatis之where关键字与<where>标签的区别

1.在使用mybatis的动态sql时,有时候遇到根据条件判断添加where后面的筛选条件的情况,会出现多余的AND或者OR:2.使用where关键字:2.1当第一个参数为空时,拼接后的sql为:select*fromtdwhereandphone=.......;2.2当所有的参数都为空时,拼接后的sql为:select*fromtdwhere.....,显然这样的sql不是完整的sql,执行时会报错.3.使用where标签时:3.1当第一个参数为空时,拼接后的sql为:select*fromtdwherephone=......(若语句的开头为AND或者OR时,where元素会将他们去除).3.2当所有的参数都为空时,拼接后的sql为:select*fromtd.(where元素只会在至少有一个子元素的条件返回SQL子句的情况下才去插入“WHERE”子句)。

Tags: JAVA
大数据开发
CentOS7安装CDH5

CentOS7的ClouderaManager5.8安装系统要求http://www.cloudera.com/documentation/manager/5-1-x/Cloudera-Manager-Installation-Guide/cm5ig_cm_requirements.html内存(最低要求):主机4G节点2G相关包下载:ClouderaManager下载地址:http://archive.cloudera.com/cm5/cm/5/选择cloudera-manager-centos7-cm5.8.0_x86_64.tar.gzCDH5下载地址:http://archive.cloudera.com/cdh5/parcels/5/选择以下三个文件:CDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcelCDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcel.sha1(下载后重命名为CDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcel.sha)manifest.jsonJDK下载地址:http://www.oracle.com/technetwork/java/javase/downloads选择最新的JDKrpm包cmagent与cmdaemons下载地址:http://archive-primary.cloudera.com/cm5/redhat/7/x86_64/cm/5.8/RPMS/x86_64/选择以下两个文件:cloudera-manager-agent-5.8.1-1.cm581.p0.7.el7.x86_64.rpmcloudera-manager-daemons-5.8.1-1.cm581.p0.7.el7.x86_64.rpmMysqlJDBC驱动下载地址:http://dev.mysql.com/downloads/connector/j/选择mysql-connector-java-5.1.45.tar.gz准备工作:系统环境搭建++以下操作均用root用户操作++1.网络配置(所有节点)vi/etc/sysconfig/network修改hostname:NETWORKING=yesHOSTNAME=master通过servicenetworkrestart重启网络服务生效。vi/etc/hosts修改ip与主机名的对应关系192.168.1.101master192.168.1.102slave1192.168.1.103slave2192.168.1.104slave3注意:这里需要将每台机器的ip及主机名对应关系都写进去,本机的也要写进去,否则启动Agent的时候会提示hostname解析错误。2.打通SSH,设置ssh无密码登陆(所有节点)在主节点上执行ssh-keygen-trsa一路回车,生成无密码的密钥对。将公钥添加到认证文件中:Cat~/.ssh/id_rsa.pub&gt;&gt;~/.ssh/authorized_keys并设置authorized_keys的访问权限:chmod600~/.ssh/authorized_keysscp文件到所有datenode节点:scp~/.ssh/authorized_keysroot@slave1:~/.ssh/scp~/.ssh/authorized_keysroot@slave2:~/.ssh/scp~/.ssh/authorized_keysroot@slave3:~/.ssh/在主节点上sshslave1,正常情况下,不需要密码就能直接登陆进去了。3.安装Oracle的Java(所有节点)CentOS,自带OpenJdk,不过运行CDH5需要使用Oracle的Jdk,需要Java7的支持。卸载自带的OpenJdk,使用rpm-qa|grepjava查询java相关的包,使用rpm-e--nodeps包名卸载之。去Oracle的官网下载jdk的rpm安装包,并使用rpm-ivh包名安装之。由于是rpm包并不需要我们来配置环境变量,我们只需要配置一个全局的JAVA_HOME变量即可,执行命令:echo&quot;JAVA_HOME=/usr/java/latest/&quot;&gt;&gt;/etc/environment4.安装配置MySql(主节点)通过yuminstallmysql-server安装mysql服务器。chkconfigmysqldon设置开机启动,并servicemysqldstart启动mysql服务,并根据提示设置root的初试密码:mysqladmin-urootpassword&#39;123456&#39;mysql-uroot-p123456进入mysql命令行,创建以下数据库:#hivecreatedatabasehiveDEFAULTCHARSETutf8COLLATEutf8_general_ci;#activitymonitorcreatedatabaseamonDEFAULTCHARSETutf8COLLATEutf8_general_ci;设置root授权访问以上所有的数据库:#授权root用户在主节点拥有所有数据库的访问权限grantallprivilegeson*.*to&#39;root&#39;@&#39;master&#39;identifiedby&#39;123456&#39;withgrantoption;flushprivileges;5.关闭防火墙和SELinux注意:需要在所有的节点上执行,因为涉及到的端口太多了,临时关闭防火墙是为了安装起来更方便,安装完毕后可以根据需要设置防火墙策略,保证集群安全。关闭防火墙:serviceiptablesstop(临时关闭)chkconfigiptablesoff(重启后生效)关闭SELINUX(实际安装过程中发现没有关闭也是可以的,不知道会不会有问题,还需进一步进行验证):setenforce0(临时生效)修改/etc/selinux/config下的SELINUX=disabled(重启后永久生效)CentOS7.0systemctlstopfirewalld.service#停止firewallsystemctldisablefirewalld.service#禁止firewall开机启动6.所有节点配置NTP服务集群中所有主机必须保持时间同步,如果时间相差较大会引起各种问题。具体思路如下:master节点作为ntp服务器与外界对时中心同步时间,随后对所有datanode节点提供时间同步服务。所有datanode节点以master节点为基础同步时间。所有节点安装相关组件:yuminstallntp。完成后,配置开机启动:chkconfigntpdon,检查是否设置成功:chkconfig--listntpd其中2-5为on状态就代表成功。centos7:systemctlenablentpdsystemctlis-enabledntpd1.主节点配置位置/etc/ntp.conf在配置之前,先使用ntpdate手动同步一下时间,免得本机与对时中心时间差距太大,使得ntpd不能正常同步。这里选用65.55.56.206作为对时中心,ntpdate-u65.55.56.206。ntp服务只有一个配置文件,配置好了就OK。这里只给出有用的配置,不需要的配置都用#注掉,这里就不在给出:driftfile/var/lib/ntp/driftrestrict127.0.0.1restrict-6::1restrictdefaultnomodifynotrapservercn.ntp.org.cnpreferincludefile/etc/ntp/crypto/pwkeys/etc/ntp/keys配置文件完成,保存退出,启动服务,执行如下命令:1servicentpdstart2ntpstat检查是否成功,用ntpstat命令查看同步状态,出现以下状态代表启动成功:synchronisedtoNTPserver()atstratum2timecorrecttowithin74mspollingserverevery128s如果出现异常请等待几分钟,一般等待5-10分钟才能同步。配置ntp客户端(所有datanode节点)driftfile/var/lib/ntp/driftrestrict127.0.0.1restrict-6::1restrictdefaultkodnomodifynotrapnopeernoqueryrestrict-6defaultkodnomodifynotrapnopeernoquery#这里是主节点的主机名或者ipservermasterincludefile/etc/ntp/crypto/pwkeys/etc/ntp/keysok保存退出,请求服务器前,请先使用ntpdate手动同步一下时间:ntpdate-umaster(主节点ntp服务器)这里可能出现同步失败的情况,请不要着急,一般是本地的ntp服务器还没有正常启动,一般需要等待5-10分钟才可以正常同步。启动服务:servicentpdstart因为是连接内网,这次启动等待的时间会比master节点快一些,但是也需要耐心等待一会儿正式开工安装CMagent和CMDaemons安装先用scp将cmagent与cmdaemons复制到所有节点用yum进行安装:yum--nogpgchecklocalinstallcloudera-manager-agent-*.x86_64.rpmyum--nogpgchecklocalinstallcloudera-manager-daemons-*.x86_64.rpm安装ClouderaManagerServer和Agent主节点解压安装clouderamanager的目录默认位置在/opt下,解压:tarxzvfcloudera-manager*.tar.gz将解压后的cm-5.8.0和cloudera目录放到/opt目录下。为ClouderaManager5建立数据库:首先需要去MySql的官网下载JDBC驱动,http://dev.mysql.com/downloads/connector/j/,解压后,找到mysql-connector-java-5.1.45-bin.jar,放到/opt/cm-5.8.0/share/cmf/lib/中。在主节点初始化CM5的数据库:/opt/cm-5.8.0/share/cmf/schema/scm_prepare_database.shmysqlcm-hlocalhost-uroot-pxxxx--scm-hostlocalhostscmscmscmAgent配置修改/opt/cm-5.8.0/etc/cloudera-scm-agent/config.ini中的server_host为主节点的主机名。同步Agent到其他节点scp-r/opt/cm-5.8.0root@slave1:/opt/在所有节点创建cloudera-scm用户useradd--system--home=/opt/cm-5.8.0/run/cloudera-scm-server/--no-create-home--shell=/bin/false--comment&quot;ClouderaSCMUser&quot;cloudera-scm准备Parcels,用以安装CDH5将CHD5相关的Parcel包放到主节点的/opt/cloudera/parcel-repo/目录中(parcel-repo如果没有需要手动创建,)。相关的文件如下:CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcelCDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.shamanifest.json最后将CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.sha1,重命名为CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.sha,这点必须注意,否则,系统会重新下载CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel文件。相关启动脚本通过/opt/cm-5.8.0/etc/init.d/cloudera-scm-serverstart启动服务端。通过/opt/cm-5.8.0/etc/init.d/cloudera-scm-agentstart启动Agent服务。注意:如果AGent启动失败,则手动创建一个cloudera-scm-agent文件夹:mkdir-p/opt/cm-5.8.0/run/cloudera-scm-agent/然后再使用命令启动agent我们启动的其实是个service脚本,需要停止服务将以上的start参数改为stop就可以了,重启是restart。CDH5的安装配置ClouderaManagerServer和Agent都启动以后,就可以进行CDH5的安装配置了。这时可以通过浏览器访问主节点的7180端口测试一下了(由于CMServer的启动需要花点时间,这里可能要等待一会才能访问),默认的用户名和密码均为admin:选择免费版本:指定集群主机:在搜索框中填写所有的集群的Hostname或者IP,搜索如果所有的主机的agent都启动成功则会有下图显示,没有则重新启动Agent,或者检查日志报错:如果parcel文件事先放在指定文件夹中,则会找到相应的版本:注:如果CDH的安装目录不在/opt下,则需要点击更多选项,修改parcel目录及本地parcel存储路径为正确的目录,然后重启agent和server即可点击,继续,如果配置本地Parcel包无误,那么下图中的已下载,应该是瞬间就完成了,然后就是耐心等待分配过程就行了,大约10多分钟吧,取决于内网网速。注意:在安装过程中如果一直卡在“正在激活”过程中,检查相应节点的/opt/cm-5.8.0/log/cloudera-scm-agent下的日志,如果出现connectionrefuse的错误信息。则可以通过将已完成激活的主机上的parcels文件夹用scp命令复制到出问题的agent相应位置上,再在parcels文件夹下创建一个快捷方式:ln-sCDHCDH-5.8.0-1.cdh5.8.0.p0.42/然后分配就完成了。Cloudera建议将/proc/sys/vm/swappiness设置为10。当前设置为60。使用sysctl命令在运行时更改该设置并编辑/etc/sysctl.conf以在重启后保存该设置。您可以继续进行安装,但可能会遇到问题,ClouderaManager报告您的主机由于交换运行状况不佳。以下主机受到影响通过echo10&gt;/proc/sys/vm/swappiness即可解决。已启用透明大页面压缩,可能会导致重大性能问题。请运行“echonever&gt;/sys/kernel/mm/transparent_hugepage/defrag”以禁用此设置,然后将同一命令添加到/etc/rc.local等初始脚本中,以便在系统重启时予以设置。通过echonever&gt;/sys/kernel/mm/transparent_hugepage/defrag解决检查全部通过,点击继续,进入集群设置,选择自定义服务,可以根据自己需要配置安装相应的服务:根据自己的需要进行修改配置:数据库配置:开始安装启动相应组件:等待集群配置安装完成!注意:倘若过程中图形化安装过程中失误导致安装失败,需要重新图形化安装:

大数据开发
全国建筑市场监管公共服务平台接口数据爬取

全国建筑市场监管公共服务平台(四库一平台)接口数据解密需要爬取建筑市场公司的不良行为记录进入服务平台页面http://jzsc.mohurd.gov.cn,点击顶部的搜索,发现返回的数据是经过加密的:1.寻找返回的数据既然数据是通过这个url返回的,全局搜索url:http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;pgsz=15尝试全局模糊搜索/query/comp/list,开发者工具切换到sources页签,CTRL+SHIFT+F进行全局搜索,然后点击进入JS函数,再格式化后的JS文件里面再次CTRL+F搜索:返回的结果是请求url/dataservice/query/comp/list得到的,打上断点,重新点击页面上的搜索,一步一步调试js代码:调试过程就不一步一步分析了,最终定位到,感觉像我们想要的数据,进入Console打印一下t和et中data是最初我们请求http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;pgsz=15所返回的数据e这其中的数据正是我们想要的数据2.分析加密方式既然我们已经知道了数据的加密方式,那我们就重点分析一下这个地方其中t.data我们在第一步已经分出来了使我们第一步请求http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;pgsz=15得到的结果那我们重点分析p函数的处理过程,点击进入p函数,结果如下:对数据经过层层加密处理后,调用toString方法,既然加密函数已经找到,我们就可以编写JS代码了3.代码实现我们将函数p的代码复制出来,data是加密后返回的数据,我们先复制出来用一下运行一下项目报错的原因,其中u和d没有进行初始化我们寻找一下u和d,就在函数p的上方我们把u和d添加到代码中运行项目进行测试其中返回的数据,正是我们想要的结果4.Python实现到这里已经能成功使用JS解密返回的数据了,但是如果实现爬虫自动解析需要使用Python实现:Python中AES解密可以使用Crypto库实现,具体实现代码如下:defdecrypt(text):key=&#39;jo8j9wGw%6HbxfFn&#39;vi=&#39;0123456789ABCDEF&#39;#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode(&#39;utf8&#39;),AES.MODE_CBC,vi.encode(&#39;utf8&#39;))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode(&#39;utf8&#39;)returntext_decrypted5.最后基于Python的requests实现了简单的请求+解密代码#-*-coding:utf-8-*-importjsonimportrequestsimportbinasciifromCrypto.CipherimportAESdefdecrypt(text):key=&#39;jo8j9wGw%6HbxfFn&#39;vi=&#39;0123456789ABCDEF&#39;#将请求返回的16进制数据转换为二进制数据text=binascii.a2b_hex(text)#构建解密对象cipher=AES.new(key.encode(&#39;utf8&#39;),AES.MODE_CBC,vi.encode(&#39;utf8&#39;))text_decrypted=cipher.decrypt(text)unpad=lambdas:s[0:-s[-1]]text_decrypted=unpad(text_decrypted)#去补位text_decrypted=text_decrypted.decode(&#39;utf8&#39;)returntext_decryptedif__name__==&#39;__main__&#39;:headers={&#39;User-Agent&#39;:&#39;Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/74.0.3729.108Safari/537.36&#39;}#获取到当前搜索结果,并解密data=requests.get(&#39;http://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list?pg=0&amp;pgsz=15&amp;total=0&amp;complexname=&#39;,headers=headers).textres=json.loads(decrypt(data))print(res)6.注意本文参考地址:https://www.cnblogs.com/mingyangliang/p/11875925.html平台具有防爬取策略,频繁爬取会导致封IP,可以使用IP代理,或者设置爬取间隔在1.5s所爬取数据请勿用于非法用途

工作日志
【基于ELK搭建日志分析系统】- 简单介绍

ELK日志分析系统介绍1.系统概述本系统为业务日志分析监控系统,使用ELK+Beats实现对系统业务日志的收集、存储、分析,业务系统运行期间将相关日志输出到一个指定的文件夹/文件内,使用FileBeat组件实现对日志文件夹/文件的监听,可以直接将新增的数据发往设定好的Logstash中过滤,或直接发往ElasticSearch分类存储,当系统运行出现问题时,运维人员可以使用Kibana对存储在ES中的日志数据根据相关字段搜索查找,Kibana也支持对数据进行相应的可视化图表展现。2.系统实现描述2.1采集-filebeat对于日志数据收集使用Filebeat部署在业务服务器后台监听日志文件的方式。Filebeat运行环境没有任何依赖,后台运行占用内存资源极低,相比于Logstash可以忽略不计,不影响服务器正常的业务。Filebeat可以运行在MacOS、Windows、Linux等系统下。Filebeat监听指定的文件(可以使用通配符),一旦文件中有新的一行内容追加则会将这条数据发往配置好的output路径。Filebeat的output支持logstash、ElastciSearch、file、console等,一般的不需要复杂过滤的可以直接发往ES存储,多节点日志采集也可以经过Logstash汇总过滤后,再存储进ES。在Filebaet运行过程中,每个Prospector的状态信息都会保存在内存里。当Filebeat进行了重启后,会从注册表文件里恢复重启之前的状态信息,让FIlebeat继续从之前已知的位置开始进行数据读取。2.2解析-Logstash对于数据解析主要包括:(1)对汇总多节点后的日志进行区分(2)将不规则格式数据转换为规则数据(3)将不符合格式要求的数据过滤去除因为Filebeat只支持简单的数据解析,对于日志的解析过滤整体可以使用Logstash。Logstash内置许多解析格式:grok、date、ip、json...,支持对不规则的数据字符串进行规则化输出,也能够在数据传输过程中添加或删除某些指定字段。将采集到的日志数据经过logstash过滤转换后发往ES建立索引存储。因为logstash占用内存资源较大(默认1G),为不影响业务尽量不部署在业务服务器上。2.3存储-ElasticSearch日志数据存储使用ElasticSearch,由logstash将过滤完成后的规则化数据存入ES指定索引中。ES有自动发现功能,初期使用ElasticSearch的单节点集群模式,后续想要添加节点只需指定elasticsearch集群名称保持一致,就能自动加入集群,ES就会按照配置将索引分片到新加入的节点上。2.4展现-Kibana(1)ELK中Kibana专门为ES中的数据提供可视化展现的,支持搜索、汇总计算,图表展现等。(2)ElasticSearch也提供有RESTAPI,支持调用接口的方式访问操作索引数据。(3)使用插件进行数据异常监控报警功能实现,如系统日志出现异常报错则可配置发送邮件通知相关人员。(4)Kibana也支持对系统日志进行可视化监控展现,包括CPU、内存、硬盘等。3.系统可用性测试系统运行过程中,logstash宕机:Filebeat会记录发送不成功的数据,并尝试连接logstash,成功连接后会再次将数据发往logstash,下图为再次发送成功后的日志。系统运行过程中,Filebeat宕机:在Filebaet运行过程中,每个Prospector的状态信息都会保存在内存里。当Filebeat进行了宕机重启后,会从注册表文件里恢复重启之前的状态信息,让FIlebeat继续从之前已读取的位置开始往后进行数据读取。系统运行过程中,ES集群宕机::::hljs-centerLogstash日志::::::hljs-centerFilebeat日志:::logstash没有数据存储功能,ES集群宕机,logstash数据无法发送,Filebeat会记录未成功发送的数据,同时logstash定时尝试连接ES,直到连接成功,数据会再次发送。

工作日志
【基于ELK搭建日志分析系统】- ElasticSearch安装

存储集群应与业务服务器不在同一台机器,此处搭建ES存储集群使用单节点的方式。ES集群机器需要实现安装配置好JAVA_HOME环境变量。安装配置下载,版本应与filebeat版本一致:官网地址解压修改elasticsearch.yml配置文件:其中cluster.name避免使用默认名称,各节点中配置为相同的名称,ES就会自动搜索加入启动elasticsearch:==ES集群启动不允许使用root账户==,所以应该先创建一个用户,使用新创建的用户启动elasticsearchgroupaddelkuseradd-gelkelkpasswdelk#创建elk用户密码chown-Relk:elk$ELASTICSEARCH_HOMEsuelk$ELASTICSEARCH_HOME/bin/elasticsearch-d#后台启动ES启动完成后测试,浏览器输入http://host:9200测试,有如下结果则为正常:问题解决启动elasticsearch时出现:elasticsearch:which:nojavain(/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin)[解决]在$ES_HOME/config/elasticsearch文件中加入以下配置:JAVA_HOME=/usr/local/jdk1.8maxnumberofthreads[1024]foruser[elk]likelytoolow,increasetoatleast[2048].[解决]切换到root用户,进入limits.d目录下修改配置文件:vi/etc/security/limits.d/90-nproc.conf找到并修改为softnproc2048hardnproc4096maxfiledescriptors[4096]forelasticsearchprocesslikelytoolow,increasetoatleast[65536][解决]切换到root用户,进入limits.d目录下修改配置文件。vi/etc/security/limits.d/90-nproc.conf找到并修改为softnofile65536hardnofile131072maxvirtualmemoryareasvm.max_map_count[65530]likelytoolow,increasetoatleast[262144][解决]修改:vim/etc/sysctl.conf添加下面配置:vm.max_map_count=655360并执行命令:sysctl-p

开发项目专栏
SpringBoot+Thymleaf项目初入(二) - 配置基础页面访问

1.建基础包/文件夹2.application.propertis编写application.propertis基础配置和数据库连接3.index.ftl注:在index.ftl页面可以输入感叹号!,然后按tab键一键生成HTML代码4.IndexController.java5.启动服务启动服务,后再浏览器输入http://localhost:8080,是否能成功访问:

Java开发
Spring注解之@Component

1.@Component,@Service,@Controller,@Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理2.@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能3.如果想使用自定义的组件注解,那么只要在你定义的新注解中加上@Component即可:4.@Repository注解在持久层中,具有将数据库操作抛出的原生异常翻译转化为spring的持久层异常的功能。5.@Controller层是spring-mvc的注解,具有将请求进行转发,重定向的功能。6.@Service层是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层。7.用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。

Tags: JAVA
个人随笔
WebStorm破解激活

下载破解文件JetbrainsIdesCrack-3.4.jar将下载的文件复制到WebStorm安装文件夹下的bin文件夹下(在桌面上WebStorm快捷方式上右键【打开文件所在的位置】可直接进入)双击打开WebStorm进入激活页面,点选【Evaluateforfree】试用30天,进入应用界面在菜单【Help】-&gt;【EditcustomVMoptions..】点击打开配置文件.在配置文件最下方加入一行:-javaagent:D:\software\WebStorm2019.1.3\bin\JetbrainsIdesCrack-3.4.jar这里的文件路径具体指向到刚才复制的破解文件路径下在菜单【Help】-&gt;【Register...】点击打开激活页面,选择【licenseserver】激活方式,在下面输入框中填写:https://fls.jetbrains-agent.com点击【Activate】激活后,重启WebStorm生效.

开发项目专栏
SpringBoot+Thymleaf项目初入(五) - 图片验证码

1.验证码工具类1.新建包utils2.新建VerifyCodeUtil类packagecn.coralcloud.ims.utils;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.geom.AffineTransform;importjava.awt.image.BufferedImage;importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.OutputStream;importjava.util.Arrays;importjava.util.Random;/***@authorc-geff*/publicclassVerifyCodeUtil{/***使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符*/privatestaticfinalStringVERIFY_CODES=&quot;23456789ABCDEFGHJKLMNPQRSTUVWXYZ&quot;;privatestaticRandomrandom=newRandom();/***使用系统默认字符源生成验证码*@paramverifySize验证码长度*@return*/publicstaticStringgenerateVerifyCode(intverifySize){returngenerateVerifyCode(verifySize,VERIFY_CODES);}/***使用指定源生成验证码*@paramverifySize验证码长度*@paramsources验证码字符源*@return*/publicstaticStringgenerateVerifyCode(intverifySize,Stringsources){if(sources==null||sources.length()==0){sources=VERIFY_CODES;}intcodesLen=sources.length();Randomrand=newRandom(System.currentTimeMillis());StringBuilderverifyCode=newStringBuilder(verifySize);for(inti=0;i&lt;verifySize;i++){verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));}returnverifyCode.toString();}/***生成随机验证码文件,并返回验证码值*@paramw*@paramh*@paramoutputFile*@paramverifySize*@return*@throwsIOException*/publicstaticStringoutputVerifyImage(intw,inth,FileoutputFile,intverifySize)throwsIOException{StringverifyCode=generateVerifyCode(verifySize);outputImage(w,h,outputFile,verifyCode);returnverifyCode;}/***输出随机验证码图片流,并返回验证码值*@paramw*@paramh*@paramos*@paramverifySize*@return*@throwsIOException*/publicstaticStringoutputVerifyImage(intw,inth,OutputStreamos,intverifySize)throwsIOException{StringverifyCode=generateVerifyCode(verifySize);outputImage(w,h,os,verifyCode);returnverifyCode;}/***生成指定验证码图像文件*@paramw*@paramh*@paramoutputFile*@paramcode*@throwsIOException*/publicstaticvoidoutputImage(intw,inth,FileoutputFile,Stringcode)throwsIOException{if(outputFile==null){return;}Filedir=outputFile.getParentFile();if(!dir.exists()){dir.mkdirs();}try{outputFile.createNewFile();FileOutputStreamfos=newFileOutputStream(outputFile);outputImage(w,h,fos,code);fos.close();}catch(IOExceptione){throwe;}}/***输出指定验证码图片流*@paramw*@paramh*@paramos*@paramcode*@throwsIOException*/publicstaticvoidoutputImage(intw,inth,OutputStreamos,Stringcode)throwsIOException{intverifySize=code.length();BufferedImageimage=newBufferedImage(w,h,BufferedImage.TYPE_INT_RGB);Randomrand=newRandom();Graphics2Dg2=image.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);Color[]colors=newColor[5];Color[]colorSpaces=newColor[]{Color.WHITE,Color.CYAN,Color.GRAY,Color.LIGHT_GRAY,Color.MAGENTA,Color.ORANGE,Color.PINK,Color.YELLOW};float[]fractions=newfloat[colors.length];for(inti=0;i&lt;colors.length;i++){colors[i]=colorSpaces[rand.nextInt(colorSpaces.length)];fractions[i]=rand.nextFloat();}Arrays.sort(fractions);//设置边框色g2.setColor(Color.GRAY);g2.fillRect(0,0,w,h);Colorc=getRandColor(200,250);//设置背景色g2.setColor(c);g2.fillRect(0,2,w,h-4);//绘制干扰线Randomrandom=newRandom();//设置线条的颜色g2.setColor(getRandColor(160,200));intloop=20;for(inti=0;i&lt;loop;i++){intx=random.nextInt(w-1);inty=random.nextInt(h-1);intxl=random.nextInt(6)+1;intyl=random.nextInt(12)+1;g2.drawLine(x,y,x+xl+40,y+yl+20);}//添加噪点//噪声率floatyawpRate=0.05f;intarea=(int)(yawpRate*w*h);for(inti=0;i&lt;area;i++){intx=random.nextInt(w);inty=random.nextInt(h);intrgb=getRandomIntColor();image.setRGB(x,y,rgb);}//使图片扭曲shear(g2,w,h,c);g2.setColor(getRandColor(100,160));intfontSize=h-4;Fontfont=newFont(&quot;Algerian&quot;,Font.ITALIC,fontSize);g2.setFont(font);char[]chars=code.toCharArray();for(inti=0;i&lt;verifySize;i++){AffineTransformaffine=newAffineTransform();affine.setToRotation(Math.PI/4*rand.nextDouble()*(rand.nextBoolean()?1:-1),(w/verifySize)*i+fontSize/2,h/2);g2.setTransform(affine);g2.drawChars(chars,i,1,((w-10)/verifySize)*i+5,h/2+fontSize/2-10);}g2.dispose();ImageIO.write(image,&quot;jpg&quot;,os);}privatestaticfinalIntegerCOLOR_MAX_INT=255;privatestaticColorgetRandColor(intfc,intbc){if(fc&gt;COLOR_MAX_INT){fc=COLOR_MAX_INT;}if(bc&gt;COLOR_MAX_INT){bc=COLOR_MAX_INT;}intr=fc+random.nextInt(bc-fc);intg=fc+random.nextInt(bc-fc);intb=fc+random.nextInt(bc-fc);returnnewColor(r,g,b);}privatestaticintgetRandomIntColor(){int[]rgb=getRandomRgb();intcolor=0;for(intc:rgb){color=color&lt;&lt;8;color=color|c;}returncolor;}privatestaticint[]getRandomRgb(){int[]rgb=newint[3];intloop=3;for(inti=0;i&lt;loop;i++){rgb[i]=random.nextInt(255);}returnrgb;}privatestaticvoidshear(Graphicsg,intw1,inth1,Colorcolor){shearx(g,w1,h1,color);sheary(g,w1,h1,color);}privatestaticvoidshearx(Graphicsg,intw1,inth1,Colorcolor){intperiod=random.nextInt(2);booleanborderGap=true;intframes=1;intphase=random.nextInt(2);for(inti=0;i&lt;h1;i++){doubled=(double)(period&gt;&gt;1)*Math.sin((double)i/(double)period+(6.2831853071795862D*(double)phase)/(double)frames);g.copyArea(0,i,w1,1,(int)d,0);if(borderGap){g.setColor(color);g.drawLine((int)d,i,0,i);g.drawLine((int)d+w1,i,w1,i);}}}privatestaticvoidsheary(Graphicsg,intw1,inth1,Colorcolor){intperiod=random.nextInt(40)+10;booleanborderGap=true;intframes=20;intphase=7;for(inti=0;i&lt;w1;i++){doubled=(double)(period&gt;&gt;1)*Math.sin((double)i/(double)period+(6.2831853071795862D*(double)phase)/(double)frames);g.copyArea(i,0,1,h1,0,(int)d);if(borderGap){g.setColor(color);g.drawLine(i,(int)d,i,0);g.drawLine(i,(int)d+h1,i,h1);}}}}2.UserController新增验证码接口2.1新增captcha方法@GetMapping(&quot;/captcha&quot;)publicvoidcaptcha(HttpServletResponseresponse,HttpSessionsession){Stringcode=VerifyCodeUtil.generateVerifyCode(4);session.setAttribute(SessionKey.ADMIN_CAPTCHA_KEY,code);try{VerifyCodeUtil.outputImage(150,50,response.getOutputStream(),code);}catch(IOExceptione){e.printStackTrace();}}2.2修改login方法,增加验证下方为最终的登录验证方法:@PostMapping(&quot;/login&quot;)publicModelAndViewlogin(Stringemail,Stringpassword,Stringcaptcha,HttpSessionsession){StringsessionCaptcha=(String)session.getAttribute(SessionKey.ADMIN_CAPTCHA_KEY);ModelAndViewview=newModelAndView();view.setViewName(&quot;user/login&quot;);//将email和password写回到页面,使得登录失败时输入的账号密码不会丢失view.addObject(&quot;email&quot;,email);view.addObject(&quot;password&quot;,password);if(StringUtils.isEmpty(captcha)||!Objects.equals(captcha,sessionCaptcha)){view.addObject(&quot;errmsg&quot;,&quot;验证码错误!&quot;);returnview;}Useruser=userService.login(email,password);if(user!=null){session.setAttribute(SessionKey.ADMIN_USER_KEY,user);view.setViewName(&quot;redirect:/index&quot;);returnview;}view.addObject(&quot;errmsg&quot;,&quot;用户名或密码错误!&quot;);returnview;}注:此处将Session的Key值通过一个常量类SessionKey保存2.3SessionKey类在utils包下新建SessionKey类packagecn.coralcloud.ims.utils;/***@authorc-geff*@nameSessionKey*@description*@date2020-11-0410:12*/publicclassSessionKey{publicstaticfinalStringADMIN_USER_KEY=&quot;AdminUserKey&quot;;publicstaticfinalStringADMIN_CAPTCHA_KEY=&quot;AdminLoginCaptchaCode&quot;;}3.修改login.ftl3.1引入jquery下载jqeury文件jquery.min.js在/static/js文件夹下新建jquery文件夹,将jquery.min.js复制到文件夹下修改login.ftl文件,最终login.ftl内容为下:&lt;!doctypehtml&gt;&lt;htmllang=&quot;en&quot;xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;&lt;head&gt;&lt;metacharset=&quot;UTF-8&quot;&gt;&lt;metaname=&quot;viewport&quot;content=&quot;width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0&quot;&gt;&lt;metahttp-equiv=&quot;X-UA-Compatible&quot;content=&quot;ie=edge&quot;&gt;&lt;linktype=&quot;text/css&quot;rel=&quot;stylesheet&quot;href=&quot;/static/css/main.css&quot;&gt;&lt;linktype=&quot;text/css&quot;rel=&quot;stylesheet&quot;href=&quot;/static/css/login.css&quot;&gt;&lt;scriptsrc=&quot;/static/js/jquery/jquery.min.js&quot;type=&quot;application/javascript&quot;&gt;&lt;/script&gt;&lt;title&gt;用户登录&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;divclass=&quot;login-container&quot;&gt;&lt;formaction=&quot;/user/login&quot;method=&quot;post&quot;&gt;&lt;div&gt;&lt;h3&gt;用户登录&lt;/h3&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;email&quot;&gt;登录邮箱&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;email&quot;autocomplete=&quot;off&quot;th:value=&quot;${email}&quot;placeholder=&quot;请输入邮箱&quot;type=&quot;text&quot;name=&quot;email&quot;&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;password&quot;&gt;登录密码&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;password&quot;autocomplete=&quot;new-password&quot;th:value=&quot;${password}&quot;placeholder=&quot;请输入密码&quot;type=&quot;password&quot;name=&quot;password&quot;&gt;&lt;/div&gt;&lt;divclass=&quot;ims-form-item&quot;&gt;&lt;labelclass=&quot;ims-form-label&quot;for=&quot;captcha&quot;&gt;图片验证&lt;/label&gt;&lt;inputclass=&quot;ims-form-input&quot;id=&quot;captcha&quot;type=&quot;text&quot;placeholder=&quot;请输入图片验证码&quot;name=&quot;captcha&quot;&gt;&lt;imgsrc=&quot;/user/captcha&quot;class=&quot;captcha&quot;alt=&quot;图片验证码&quot;&gt;&lt;/div&gt;&lt;pclass=&quot;errormsg&quot;th:if=&quot;${errmsg}!=null&quot;th:text=&quot;${errmsg}&quot;&gt;&lt;/p&gt;&lt;div&gt;&lt;buttontype=&quot;submit&quot;class=&quot;ims-button&quot;&gt;登录&lt;/button&gt;&lt;/div&gt;&lt;/form&gt;&lt;/div&gt;&lt;/body&gt;&lt;scripttype=&quot;application/javascript&quot;&gt;$(document).ready(function(){$(&#39;img.captcha&#39;).click(function(){$(this).attr(&quot;src&quot;,&quot;/user/captcha?_=&quot;+newDate().getTime())})})&lt;/script&gt;&lt;/html&gt;注,login.ftl变更如下:1.head新增引入jquery.min.js2.input新增读取前面保存的email和password3.新增图片验证码输入框及图片4.新建图片点击监听事件,更换图片验证码4.修改样式login.css#captcha{width:100px;border-top-right-radius:0;border-bottom-right-radius:0;}.captcha{width:1px;flex:1;height:42px;cursor:pointer;border-top-right-radius:4px;border-bottom-right-radius:4px;}5.最终效果

Java开发
基于SpringSecurity的用户权限控制

1.说明本文主要介绍基于SpringSecurity的用户权限控制的简单实现。1.1环境版本SpringBoot:2.0.7SpringSecurity:5.0.10JDK:1.82.项目配置2.1引入maven包&lt;?xmlversion=&quot;1.0&quot;encoding=&quot;UTF-8&quot;?&gt;&lt;projectxmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;&lt;parent&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;&lt;version&gt;2.0.7.RELEASE&lt;/version&gt;&lt;relativePath/&gt;&lt;!--lookupparentfromrepository--&gt;&lt;/parent&gt;&lt;groupId&gt;cn.coralcloud&lt;/groupId&gt;&lt;artifactId&gt;security&lt;/artifactId&gt;&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;&lt;name&gt;security&lt;/name&gt;&lt;description&gt;DemoSecurityprojectforSpringBoot&lt;/description&gt;&lt;properties&gt;&lt;java.version&gt;1.8&lt;/java.version&gt;&lt;/properties&gt;&lt;dependencies&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-jdbc&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;&lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;&lt;version&gt;2.1.1&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.session&lt;/groupId&gt;&lt;artifactId&gt;spring-session-data-redis&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.session&lt;/groupId&gt;&lt;artifactId&gt;spring-session-jdbc&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;&lt;scope&gt;runtime&lt;/scope&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;mysql&lt;/groupId&gt;&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;&lt;scope&gt;runtime&lt;/scope&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-configuration-processor&lt;/artifactId&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;&lt;artifactId&gt;lombok&lt;/artifactId&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;&lt;artifactId&gt;jackson-annotations&lt;/artifactId&gt;&lt;version&gt;2.9.9&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;com.google.code.gson&lt;/groupId&gt;&lt;artifactId&gt;gson&lt;/artifactId&gt;&lt;version&gt;2.8.5&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;com.alibaba&lt;/groupId&gt;&lt;artifactId&gt;fastjson&lt;/artifactId&gt;&lt;version&gt;1.2.60&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;com.google.guava&lt;/groupId&gt;&lt;artifactId&gt;guava&lt;/artifactId&gt;&lt;version&gt;22.0&lt;/version&gt;&lt;/dependency&gt;&lt;/dependencies&gt;&lt;build&gt;&lt;plugins&gt;&lt;plugin&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;&lt;/plugin&gt;&lt;/plugins&gt;&lt;/build&gt;&lt;/project&gt;2.2application.ymlserver.port:9091spring.application.name:spring-webspring.http.encoding.charset:utf8spring:session:store-type:redisredis:host:localhostport:6379password:123456datasource:driver-class-name:com.mysql.jdbc.Driverurl:jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghaiusername:rootpassword:123456servlet:multipart:max-file-size:1024MBmax-request-size:1024MBmybatis:mapper-locations:classpath*:mapper/*Mapper.xmltype-aliases-package:cn.coralcloud.security.model2.3数据库初始化脚本/*NavicatMySQLDataTransferSourceServer:localhostSourceServerVersion:50644SourceHost:localhost:3306SourceDatabase:securityTargetServerType:MYSQLTargetServerVersion:50644FileEncoding:65001Date:2019-12-0216:28:44*/SETFOREIGN_KEY_CHECKS=0;--------------------------------Tablestructureforrole------------------------------DROPTABLEIFEXISTS`role`;CREATETABLE`role`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`name`varchar(50)NOTNULL,`nickname`varchar(50)NOTNULL,`system`bit(1)NOTNULLDEFAULTb&#39;0&#39;,`description`varchar(500)DEFAULTNULL,`permission`mediumtext,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4;--------------------------------Tablestructureforuser------------------------------DROPTABLEIFEXISTS`user`;CREATETABLE`user`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`username`varchar(50)NOTNULL,`password`varchar(300)NOTNULL,`role`varchar(500)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4;3.系统初始化文件3.1users.json[{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;ea48576f30be1669971699c09ad05c94&quot;,&quot;role&quot;:&quot;ROLE_ADMINISTRATOR&quot;}]3.2roles.json[{&quot;name&quot;:&quot;ROLE_ADMINISTRATOR&quot;,&quot;nickname&quot;:&quot;管理员&quot;,&quot;description&quot;:&quot;系统超级管理员,不允许用户更改&quot;,&quot;system&quot;:true,&quot;permissions&quot;:[{&quot;resourceId&quot;:&quot;user&quot;,&quot;resourceName&quot;:&quot;用户管理&quot;,&quot;privileges&quot;:{&quot;list&quot;:&quot;查看用户列表&quot;,&quot;add&quot;:&quot;新增用户&quot;,&quot;update&quot;:&quot;修改用户信息&quot;,&quot;delete&quot;:&quot;删除用户&quot;}},{&quot;resourceId&quot;:&quot;permission&quot;,&quot;resourceName&quot;:&quot;权限&quot;,&quot;privileges&quot;:{&quot;read&quot;:&quot;查看权限&quot;,&quot;write&quot;:&quot;新增权限&quot;,&quot;update&quot;:&quot;更新权限&quot;,&quot;delete&quot;:&quot;删除权限&quot;}}]}]4.数据持久化DAO层4.1UserDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.User;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;importjava.util.List;/***@authorgeff*@nameUserDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceUserDao{/***根据用户名查找*@date2019/11/2915:24*@authorgeff*@paramusernameusername*@returncn.coralcloud.security.model.User*/UserfindByUsername(Stringusername);/***创建用户*@date2019/11/2915:24*@authorgeff*@paramuseruser*/voidsave(Useruser);/***获取用户列表*@date2019/11/2915:24*@authorgeff*@param*@returnjava.util.List&lt;cn.coralcloud.security.model.User&gt;*/List&lt;User&gt;list();}4.2RoleDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.Role;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;/***@authorgeff*@nameRoleDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceRoleDao{/***根据名称查找*@date2019/11/2915:23*@authorgeff*@paramnamename*@returncn.coralcloud.security.model.Role*/RolefindByName(Stringname);/***保存数据*@date2019/11/2915:23*@authorgeff*@paramrolerole*/voidsave(Rolerole);}4.3UserMapper.xml&lt;?xmlversion=&quot;1.0&quot;encoding=&quot;UTF-8&quot;?&gt;&lt;!DOCTYPEmapperPUBLIC&quot;-//mybatis.org//DTDMapper3.0//EN&quot;&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;&lt;mappernamespace=&quot;cn.coralcloud.security.dao.UserDao&quot;&gt;&lt;resultMapid=&quot;userMapper&quot;type=&quot;cn.coralcloud.security.model.User&quot;&gt;&lt;idproperty=&quot;id&quot;column=&quot;id&quot;/&gt;&lt;/resultMap&gt;&lt;selectid=&quot;findByUsername&quot;parameterType=&quot;String&quot;resultMap=&quot;userMapper&quot;&gt;select*from`user`where`username`=#{username}&lt;/select&gt;&lt;selectid=&quot;list&quot;resultType=&quot;cn.coralcloud.security.model.User&quot;&gt;select*from`user`&lt;/select&gt;&lt;insertid=&quot;save&quot;parameterType=&quot;cn.coralcloud.security.model.User&quot;useGeneratedKeys=&quot;true&quot;keyProperty=&quot;id&quot;&gt;insertinto`user`(username,password,role)values(#{username},#{password},#{role})&lt;/insert&gt;&lt;/mapper&gt;4.4RoleMapper.xml&lt;?xmlversion=&quot;1.0&quot;encoding=&quot;UTF-8&quot;?&gt;&lt;!DOCTYPEmapperPUBLIC&quot;-//mybatis.org//DTDMapper3.0//EN&quot;&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;&lt;mappernamespace=&quot;cn.coralcloud.security.dao.RoleDao&quot;&gt;&lt;resultMapid=&quot;roleMapper&quot;type=&quot;cn.coralcloud.security.model.Role&quot;&gt;&lt;idproperty=&quot;id&quot;column=&quot;id&quot;/&gt;&lt;/resultMap&gt;&lt;selectid=&quot;findByName&quot;parameterType=&quot;String&quot;resultMap=&quot;roleMapper&quot;&gt;select*fromrolewhere`name`=#{name}&lt;/select&gt;&lt;insertid=&quot;save&quot;parameterType=&quot;cn.coralcloud.security.model.Role&quot;useGeneratedKeys=&quot;true&quot;keyProperty=&quot;id&quot;&gt;insertintorole(name,nickname,description,system,permission)values(#{name},#{nickname},#{description},#{system},#{permission})&lt;/insert&gt;&lt;/mapper&gt;5.处理用户权限认证逻辑5.1SpringSecurity配置要在项目中使用@PreAuthorize等注解实现方法级别权限控制,则需要在项目启动类上添加注解@EnableGlobalMethodSecurity(prePostEnabled=true),本文项目启动类:packagecn.coralcloud.security;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.boot.web.servlet.support.SpringBootServletInitializer;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;/***@authorgeff*/@SpringBootApplication@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassSecurityApplicationextendsSpringBootServletInitializer{@OverrideprotectedSpringApplicationBuilderconfigure(SpringApplicationBuilderapplicationBuilder){returnapplicationBuilder.sources(SecurityApplication.class);}publicstaticvoidmain(String[]args){SpringApplication.run(SecurityApplication.class,args);}}5.2用户对象SpringSecurity自带的有UserDetails接口主要保存用户对象数据,所以我们的用户对象需要实现UserDetails接口packagecn.coralcloud.security.model;importcom.fasterxml.jackson.annotation.JsonIgnore;importlombok.Data;importlombok.NoArgsConstructor;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importjava.io.Serializable;importjava.util.*;/***@authorgeff*/@Data@NoArgsConstructorpublicclassUserimplementsUserDetails,Serializable{privateLongid;/***用户登录名*/privateStringusername;/***用户登录密码,用户的密码不应该暴露给客户端*/@JsonIgnoreprivateStringpassword;/***用户在系统中的角色列表,将根据角色对用户操作权限进行限制*/privateStringrole;privateList&lt;Role&gt;roles;@OverridepublicCollection&lt;?extendsGrantedAuthority&gt;getAuthorities(){Collection&lt;GrantedAuthority&gt;authorities=newArrayList&lt;&gt;();if(roles!=null){for(Rolerole:roles){if(role==null){continue;}for(Permissionpermission:role.getPermissions()){for(Stringprivilege:permission.getPrivileges().keySet()){authorities.add(newSimpleGrantedAuthority(String.format(&quot;%s-%s&quot;,permission.getResourceId(),privilege)));}}}}returnauthorities;}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returntrue;}}在用户对象的publicCollection&lt;?extendsGrantedAuthority&gt;getAuthorities()方法中,需要根据用户当前角色生成当前用户权限列表,本文权限机制使用resourceId-privilege方式5.3角色对象Role.javapackagecn.coralcloud.security.model;importcom.alibaba.fastjson.JSON;importlombok.Data;importorg.springframework.util.StringUtils;importjava.io.Serializable;importjava.util.List;/***@authorgeff*@nameRole*@description*@date2019-11-2910:08*/@DatapublicclassRoleimplementsSerializable{privateLongid;/***角色名,用于权限校验*/privateStringname;/***角色中文名,用于显示*/privateStringnickname;/***角色描述信息*/privateStringdescription;/***是否为内置*/privateBooleansystem;/***角色可进行的操作列表*/privateList&lt;Permission&gt;permissions;privateStringpermission;/***SpringSecurity4.0以上版本角色都默认以&#39;ROLE_&#39;开头*@paramnamename*/publicvoidsetName(Stringname){if(!name.contains(&quot;ROLE_&quot;)){this.name=&quot;ROLE_&quot;+name;}else{this.name=name;}}publicList&lt;Permission&gt;getPermissions(){if(permissions==null){if(!StringUtils.isEmpty(permission)){this.permissions=JSON.parseArray(permission,Permission.class);}}returnpermissions;}publicStringgetPermission(){if(StringUtils.isEmpty(permission)){this.permission=JSON.toJSONString(permissions);}returnpermission;}}SpringSecurity4.0以上所有的角色名称默认都要以ROLE_开头,所有本文在获取角色名称是会自动添加前缀。为了简单方便,本文角色对象的permission字段保存着该角色的所有权限列表的JSON字符串5.4权限对象Permission.javapackagecn.coralcloud.security.model;importlombok.Data;importjava.io.Serializable;importjava.util.Map;/***@authorgeff*@namePermission*@description*@date2019-11-2910:07*/@DatapublicclassPermissionimplementsSerializable{privateStringresourceId;privateStringresourceName;privateMap&lt;String,String&gt;privileges;privatebooleanabandon=false;}5.5统一数据返回对象因为是前后端分离项目,所以本文封装了统一数据返回对象Response类packagecn.coralcloud.security.model;importjava.io.Serializable;/***响应*@authorgeff*/publicclassResponse&lt;T&gt;implementsSerializable{privatefinalstaticintSUCCESS=0;privatefinalstaticintERROR=-1;privateintcode;privateTdata;privateStringmessage;publicintgetCode(){returncode;}publicvoidsetCode(intcode){this.code=code;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicTgetData(){returndata;}publicvoidsetData(Tdata){this.data=data;}publicstatic&lt;T&gt;Response&lt;T&gt;ok(){Response&lt;T&gt;response=newResponse&lt;&gt;();response.setCode(SUCCESS);returnresponse;}publicstatic&lt;T&gt;Response&lt;T&gt;ok(Tdata){Response&lt;T&gt;response=newResponse&lt;&gt;();response.setCode(SUCCESS);response.setData(data);returnresponse;}publicstatic&lt;T&gt;Response&lt;T&gt;fail(Stringmessage){Response&lt;T&gt;response=newResponse&lt;&gt;();response.setCode(ERROR);response.setMessage(message);returnresponse;}publicstatic&lt;T&gt;Response&lt;T&gt;fail(intcode,Stringmessage){Response&lt;T&gt;response=newResponse&lt;&gt;();response.setCode(code);response.setMessage(message);returnresponse;}}5.6UserDetailsService接口获取用户信息SpringSecurity同时在用户登录认证时会通过调用UserDetailsService的loadUserByUsername来获取当前登录的用户信息,当用户认证通过后会将用户对象保存到自定义的Token对象中。所以本文需要实现UserDetailsService接口,完成loadUserByUsername方法:packagecn.coralcloud.security.service;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;importjava.util.ArrayList;importjava.util.List;/***@authorgeff*/@Service(&quot;myUserDetailsService&quot;)publicclassUserDetailsServiceImplimplementsUserDetailsService{@AutowiredprivateUserServiceuserService;@AutowiredprivateRoleDaoroleDao;@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{Useruser=userService.findByUsername(username);if(user==null){thrownewUsernameNotFoundException(String.format(&quot;Nouserfoundwithusername:%s&quot;,username));}if(!StringUtils.isEmpty(user.getRole())){String[]roles=user.getRole().split(&quot;,&quot;);List&lt;Role&gt;roleList=newArrayList&lt;&gt;();for(StringroleName:roles){Rolerole=roleDao.findByName(roleName);roleList.add(role);}user.setRoles(roleList);}returnuser;}}5.7自定义方法权限处理器自定义方法权限处理器需要实现PermissionEvaluator接口,完成publicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission)方法。packagecn.coralcloud.security.config;importcn.coralcloud.security.model.User;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.access.PermissionEvaluator;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.GrantedAuthority;importjava.io.Serializable;/***@authorgeff*/@ConfigurationpublicclassMyPermissionEvaluatorimplementsPermissionEvaluator{@OverridepublicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){booleanaccessable=false;if(authentication.getPrincipal()instanceofUser){Stringprivilege=targetDomainObject+&quot;-&quot;+permission;for(GrantedAuthorityauthority:authentication.getAuthorities()){if(privilege.equalsIgnoreCase(authority.getAuthority())){accessable=true;break;}}returnaccessable;}returnfalse;}@OverridepublicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){//TODOAuto-generatedmethodstubreturnfalse;}}本文通过根据resourceId-privilege方式验证权限5.8系统初始化配置类本文系统初始化配置类主要实现在系统启动时根据配置的users.json和roles.json自动生成初始化用户和角色信息。packagecn.coralcloud.security.component;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.dao.UserDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importcom.google.gson.GsonBuilder;importcom.google.gson.reflect.TypeToken;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.beans.factory.annotation.Value;importjavax.annotation.PostConstruct;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.lang.reflect.Type;importjava.nio.charset.StandardCharsets;importjava.util.ArrayList;/***系统初始化配置类,主要用于加载内置数据到目标数据库上*@authorgeff*/@ComponentpublicclassSystemInitializer{@Value(&quot;${initialzation.file.users:users.json}&quot;)privateStringuserFileName;@Value(&quot;${initialzation.file.roles:roles.json}&quot;)privateStringroleFileName;@AutowiredprivateUserDaouserDao;@AutowiredprivateRoleDaoroleDao;@AutowiredprivateGsonBuildergsonBuilder;@PostConstructpublicbooleaninitialize(){try{InputStreamuserInputStream=getClass().getClassLoader().getResourceAsStream(userFileName);if(userInputStream==null){thrownewException(&quot;initialzationuserfilenotfound:&quot;+userFileName);}InputStreamroleInputStream=getClass().getClassLoader().getResourceAsStream(roleFileName);if(roleInputStream==null){thrownewException(&quot;initialzationrolefilenotfound:&quot;+roleFileName);}//导入初始的系统超级管理员角色TyperoleTokenType=newTypeToken&lt;ArrayList&lt;Role&gt;&gt;(){}.getType();ArrayList&lt;Role&gt;roles=gsonBuilder.create().fromJson(newInputStreamReader(roleInputStream,StandardCharsets.UTF_8),roleTokenType);for(Rolerole:roles){if(roleDao.findByName(role.getName())==null){roleDao.save(role);}}//导入初始的系统管理员用户TypeteacherTokenType=newTypeToken&lt;ArrayList&lt;User&gt;&gt;(){}.getType();ArrayList&lt;User&gt;users=gsonBuilder.create().fromJson(newInputStreamReader(userInputStream,StandardCharsets.UTF_8),teacherTokenType);for(Useruser:users){if(userDao.findByUsername(user.getUsername())==null){userDao.save(user);}}}catch(Exceptione){e.printStackTrace();}returntrue;}}6.用户认证相关自定义实现6.1.自定义用户认证过滤器​用户认证过滤器拦截用户发送的认证请求,然后从请求中获取用户账号和密码等认证信息并封装成一个未认证的AthenticationToken对象,然后调用AuthenticationManager对AthenticationToken进行认证。​自定义用户认证过滤器需要继承AbstractAuthenticationProcessingFilter,然后重写attemptAuthentication方法,在方法内部根据请求参数封装成未认证的AthenticationToken对象packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AuthenticationServiceException;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;importorg.springframework.security.web.util.matcher.AntPathRequestMatcher;importorg.springframework.util.StringUtils;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/***自定义的用户名密码认证过滤器*@authorgeff*/publicclassAuthFilterextendsAbstractAuthenticationProcessingFilter{privatestaticStringhttpMethod=&quot;POST&quot;;publicAuthFilter(){/**设置该过滤器对POST请求/api/user/login进行拦截*/super(newAntPathRequestMatcher(&quot;/api/user/login&quot;,httpMethod));}@OverridepublicAuthenticationattemptAuthentication(HttpServletRequestrequest,HttpServletResponseresponse)throwsAuthenticationException{if(!request.getMethod().equals(httpMethod)){thrownewAuthenticationServiceException(&quot;Authenticationmethodnotsupported:&quot;+request.getMethod());}else{/**从http请求中获取用户输入的用户名和密码信息*这里接收的是form形式的参数,如果要接收json形式的参数,修改这里即可*/Stringusername=this.obtainUsername(request);Stringpassword=this.obtainPassword(request);if(StringUtils.isEmpty(username)&amp;&amp;StringUtils.isEmpty(password)){thrownewUsernameNotFoundException(&quot;用户名或密码错误&quot;);}/**使用用户输入的用户名和密码信息创建一个未认证的用户认证Token*/AuthTokenauthRequest=newAuthToken(username,password);/**设置一些详情信息*/this.setDetails(request,authRequest);/**通过AuthenticationManager调用相应的AuthenticationProvider进行用户认证*/returnthis.getAuthenticationManager().authenticate(authRequest);}}privateStringobtainUsername(HttpServletRequestrequest){StringusernameParameter=&quot;username&quot;;returnrequest.getParameter(usernameParameter);}privateStringobtainPassword(HttpServletRequestrequest){StringpasswordParameter=&quot;password&quot;;returnrequest.getParameter(passwordParameter);}privatevoidsetDetails(HttpServletRequestrequest,AuthTokenauthRequest){authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}}自定义用户认证拦截器要在构造方法中指定拦截的认证请求(本文中是POST类型的/api/user/login请求,可根据需求设置),并在attemptAuthentication()方法中实现获取用户认证信息、封装AuthenticationToken对象、调用AuthenticationManager对AuthenticationToken进行认证等逻辑。6.2自定义用户认证处理器用户认证处理器主要是对用户提交的认证信息进行认证,SpringSecurity默认实现的认证处理器的认证处理逻辑并不一定符合所有的业务需求(例如,默认的认证处理无法处理验证码),因此,可以自定义用户认证处理器。自定义的用户认证处理器,需要实现AuthenticationProvider接口,主要是实现publicAuthenticationauthenticate(Authenticationauthentication)方法和publicbooleansupports(Class&lt;?&gt;authentication)方法,前者主要是实现具体的认证逻辑,后者主要是指定认证处理器能对哪种AuthenticationToken对象进行认证。packagecn.coralcloud.security.component;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.authentication.AuthenticationProvider;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.authentication.dao.DaoAuthenticationProvider;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***@authorgeff*@nameAuthProvider*@description*登录认证的Provider,自定义实现了{@linkAuthenticationProvider}&lt;br&gt;*Provider默认实现是{@linkDaoAuthenticationProvider}&lt;br&gt;*{@linkUsernamePasswordAuthenticationFilter}调用=&gt;{@linkAuthenticationManager}=&gt;{@linkAuthenticationProvider}验证&lt;br&gt;**@date2019-11-2915:52*/@Slf4j@ComponentpublicclassAuthProviderimplementsAuthenticationProvider{privatefinalUserDetailsServiceuserDetailService;privatefinalPasswordEncoderpasswordEncoder;@AutowiredpublicAuthProvider(@Qualifier(&quot;myUserDetailsService&quot;)UserDetailsServiceuserDetailService,PasswordEncoderpasswordEncoder){this.userDetailService=userDetailService;this.passwordEncoder=passwordEncoder;}@OverridepublicAuthenticationauthenticate(Authenticationauthentication)throwsAuthenticationException{/**将未认证的Authentication转换成自定义的用户认证Token*/AuthTokenauthenticationToken=(AuthToken)authentication;/**根据用户Token中的用户名查找用户信息,如果有该用户信息,则验证用户密码是否正确*/UserDetailsuser=userDetailService.loadUserByUsername((String)(authenticationToken.getPrincipal()));if(user==null){thrownewBadCredentialsException(&quot;用户名或密码不正确&quot;);}elseif(!this.passwordEncoder.matches((CharSequence)authenticationToken.getCredentials(),user.getPassword())){thrownewBadCredentialsException(&quot;用户名或密码不正确&quot;);}/**认证成功则创建一个已认证的用户认证Token*/AuthTokenauthenticationResult=newAuthToken(user,user.getPassword(),user.getAuthorities());/**设置一些详情信息*/authenticationResult.setDetails(authenticationToken.getDetails());returnauthenticationResult;}/***是否支持处理当前Authentication对象类似*/@Overridepublicbooleansupports(Class&lt;?&gt;authentication){returntrue;}}6.3自定义用户认证对象用户认证对象是在用户认证拦截器中创建的,在用户认证处理器中使用的。用户认证对象(AuthenticationToken)中封装的是用户认证信息,例如UsernamePasswordAuthenticationToken中封装的是用户名和密码。实际业务中,可能需要根据不同的用户信息进行认证(例如,手机号和验证码),此时就需要自定义用户认证对象。自定义的用户认证对象,需要继承AbstractAuthenticationToken类,并设定根据认证时使用的是哪些信息。packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AbstractAuthenticationToken;importorg.springframework.security.core.GrantedAuthority;importjava.util.Collection;/***自定义的用户名密码认证对象*@authorgeff*/publicclassAuthTokenextendsAbstractAuthenticationToken{/***用户名*/privatefinalObjectprincipal;/***密码*/privateObjectcredentials;/***创建未认证的用户名密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials){super(null);this.principal=principal;this.credentials=credentials;this.setAuthenticated(false);}/***创建已认证的用户密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials,Collection&lt;?extendsGrantedAuthority&gt;authorities){super(authorities);this.principal=principal;this.credentials=credentials;super.setAuthenticated(true);}@OverridepublicObjectgetCredentials(){returnthis.credentials;}@OverridepublicObjectgetPrincipal(){returnthis.principal;}@OverridepublicvoidsetAuthenticated(booleanisAuthenticated)throwsIllegalArgumentException{if(isAuthenticated){thrownewIllegalArgumentException(&quot;Cannotsetthistokentotrusted-useconstructorwhichtakesaGrantedAuthoritylistinstead&quot;);}else{super.setAuthenticated(false);}}@OverridepublicvoideraseCredentials(){super.eraseCredentials();this.credentials=null;}}6.4自定义用户认证成功处理器用户认证成功处理器在用户认证成功之后调用,主要是执行一些额外的操作(例如,操作Cookie、页面跳转等)。自定义的用户认证成功处理器可以通过实现AuthenticationSuccessHandler接口,或者通过继承AbstractAuthenticationTargetUrlRequestHandler类及其子类来实现。本文自定义的用户认证成功处理器是通过继承AbstractAuthenticationTargetUrlRequestHandler的子类SavedRequestAwareAuthenticationSuccessHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.User;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.Authentication;importorg.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.io.IOException;/***自定义的用户认证成功处理器*@authorgeff*/@Component@Slf4jpublicclassAuthSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{@AutowiredprivateObjectMapperobjectMapper;publicAuthSuccessHandler(){}@OverridepublicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{//认证成功返回jsonUseruser=(User)authentication.getPrincipal();//写入session?HttpSessionsession=request.getSession();session.setAttribute(&quot;User&quot;,user);StringjsonStr=objectMapper.writeValueAsString(user);log.info(&quot;认证成功:{}&quot;,jsonStr);response.setContentType(&quot;application/json;charset=UTF-8&quot;);response.getWriter().write(jsonStr);}}6.5自定义用户认证失败处理器用户认证失败处理器是在用户认证失败之后调用,主要是执行一些额外的操作(例如操作Cookie、页面跳转、返回错误信息等)。自定义的用户认证失败处理器可以通过实现AuthenticationFailureHandler接口,或者通过继承AuthenticationFailureHandler接口的其它实现类来实现。本文自定义的用户认证失败处理器是通过继承AuthenticationFailureHandler接口的实现类SimpleUrlAuthenticationFailureHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***自定义的用户认证失败处理器*@authorgeff*/@Component@Slf4jpublicclassAuthFailureHandlerextendsSimpleUrlAuthenticationFailureHandler{@AutowiredprivateObjectMapperobjectMapper;@OverridepublicvoidonAuthenticationFailure(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionexception)throwsIOException{/**返回JSON*/log.error(&quot;认证失败:{}&quot;,exception.getMessage());Responseres=Response.fail(-1,exception.getMessage());response.setStatus(HttpStatus.OK.value());response.setContentType(&quot;application/json;charset=UTF-8&quot;);response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.6自定义访问拒绝处理器自定义访问拒绝处理器用来解决认证过的用户访问无权限资源时的异常。前后端分离的情况下可以通过自定义访问拒绝处理器实现JSON格式的数据返回,自定义访问拒绝处理器通过实现AccessDeniedHandler接口,然后实现publicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)方法,在方法内部处理返回数据。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@authorgeff*@nameAuthAccessDeniedHandler*@description*@date2019-11-2916:57*/@Slf4j@ComponentpublicclassAuthAccessDeniedHandlerimplementsAccessDeniedHandler{@AutowiredprivateObjectMapperobjectMapper;@Overridepublicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)throwsIOException{log.error(&quot;无权访问:{}&quot;,e.getMessage());Responseres=Response.fail(401,&quot;无权访问&quot;);response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(&quot;application/json;charset=UTF-8&quot;);response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.7自定义加密类本文密码使用Md5(password,salt)的形式,所以需要自定义SpringSecurity加密类,然后再Config配置类注入自定义加密类需要实现PasswordEncoder,完成encode和matches方法packagecn.coralcloud.security.component;importcn.coralcloud.security.utils.SecretUtils;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.util.StringUtils;importjava.util.Objects;/***@authorgeff*@nameMd5SaltPasswordEncoder*@description*@date2019-12-0209:27*/publicclassMd5SaltPasswordEncoderimplementsPasswordEncoder{@OverridepublicStringencode(CharSequencecharSequence){Stringstring=charSequence.toString();String[]array=string.split(&quot;,&quot;);Stringsalt=&quot;&quot;;if(array.length&gt;1){salt=array[1];}returnSecretUtils.md5(array[0],salt);}@Overridepublicbooleanmatches(CharSequencecharSequence,Strings){if(!StringUtils.isEmpty(s)){StringencodePassword=encode(charSequence);returnObjects.equals(encodePassword,s);}returnfalse;}}7.SpringSecurity相关配置类7.1自定义的用户名密码认证配置类packagecn.coralcloud.security.component;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.SecurityConfigurerAdapter;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.web.DefaultSecurityFilterChain;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***自定义的用户名密码认证配置类*@authorgeff*/@ComponentpublicclassAuthConfigextendsSecurityConfigurerAdapter&lt;DefaultSecurityFilterChain,HttpSecurity&gt;{@AutowiredAuthProviderprovider;@AutowiredAuthSuccessHandlerauthSuccessHandler;@AutowiredAuthFailureHandlerauthFailureHandler;@Overridepublicvoidconfigure(HttpSecurityhttp){AuthFilterauthFilter=newAuthFilter();/**自定义用户认证处理逻辑时,需要指定AuthenticationManager,否则无法认证*/authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));/**指定自定义的认证成功和失败的处理器*/authFilter.setAuthenticationSuccessHandler(authSuccessHandler);authFilter.setAuthenticationFailureHandler(authFailureHandler);/**把自定义的用户名密码认证过滤器和处理器添加到UsernamePasswordAuthenticationFilter过滤器之前*/http.authenticationProvider(provider).addFilterBefore(authFilter,UsernamePasswordAuthenticationFilter.class);}}7.2SpringSecurity核心配置类packagecn.coralcloud.security.config;importcn.coralcloud.security.component.AuthAccessDeniedHandler;importcn.coralcloud.security.component.AuthConfig;importcn.coralcloud.security.component.Md5SaltPasswordEncoder;importcn.coralcloud.security.model.Response;importcn.coralcloud.security.service.UserDetailsServiceImpl;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importjava.io.PrintWriter;/***@authorgeff*/@Configuration@EnableWebSecurity@Slf4jpublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateAuthConfigauthConfig;@AutowiredprivateAuthAccessDeniedHandleraccessDeniedHandler;@Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException{httpSecurity.apply(authConfig).and().authorizeRequests().antMatchers(&quot;/static/**&quot;,&quot;/api/user/login&quot;).permitAll().anyRequest().authenticated().and().csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint((request,response,e)-&gt;{response.setContentType(&quot;application/json;charset=utf-8&quot;);PrintWriterout=response.getWriter();Responseres=Response.fail(-14,&quot;会话超时,请重新登录!&quot;);out.write(newObjectMapper().writeValueAsString(res));out.flush();out.close();});}@AutowiredpublicvoidconfigureGlobal(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewMd5SaltPasswordEncoder();}@Bean@OverridepublicUserDetailsServiceuserDetailsService(){returnnewUserDetailsServiceImpl();}}本文通过自定义AuthenticationEntryPoint来解决匿名用户访问无权限资源时的异常

Tags: JAVA