-
2020-06-11 11:59:37
项目背景:项目是用 vue 写的,最终打包为 apk ,其中涉及到录音的功能,利用 h5+ 实现的。
(在vue中集成 html5 plus, 参考 https://www.cnblogs.com/luobiao/p/10552030.html)
需求:长按录音,录音之前判断是否已经获取录音权限,如果没有权限,则需要获取权限;如果有权限,则开始录音。
录音功能,使用了触摸事件,触摸开始,则录音开始,触摸结束,则录音结束。并且限制录音时长最长为一分钟
<div class="record" @touchstart="startRecord" @touchend="stopRecord"></div>
因为要使用录音,所以在 mounted 钩子函数中需要先获取设备的录音对象
使用 plus.audio.getRecorder() 可以获取设备的录音对象。
mounted () { let vm = this this.onPlusReady(() => { console.log('plus ready') // eslint-disable-next-line vm.recorderPlus = plus.audio.getRecorder() }) },
录音时,首先要先判断是否已经获取录音的权限。 plus.navigator.checkPermission() 方法可以检查运行环境的权限。
plus.navigator.checkPermission(permission)
向系统检查当前程序的权限状态,不触发权限相对应的功能API的调用。
permission : 要检查的权限名称。
返回值:"authorized"表示程序已被用户授权使用此权限; "denied"表示程序已被用户拒绝使用此权限; "undetermined"表示程序未确定是否可使用此权限,此时调用对应的API时系统会弹出提示框让用户确认; "notdeny"表示程序未被用户拒绝使用此权限(与denied相反,可能是"authorized"或者"undetermined"); "unknown"表示程序未知此权限状态(系统存在权限管理但无法查询); "unsupported"表示程序不支持此权限。
startRecord () { if (this.recorderPlus === null) { this.$toast('Device not ready!') return } // 判断权限 // eslint-disable-next-line let permission = plus.navigator.checkPermission('RECORD') console.log(permission) switch (permission) { case 'authorized': // 允许 this.record() break case 'denied': // 拒绝 this.requestPermission() break case 'undetermined': // 询问 this.requestPermission() break case 'unknown': // 未知 this.record() break default: this.$toast('设备不支持录音') break } },
获取权限时,安卓 和 ios 的处理方式不同。
对于安卓手机,plus.android 提供了获取权限的方法。
plus.android.requestPermissions(permissions, successCb, errorCb)
permissions: Array[String] 申请的权限列表(要申请的权限需在应用manifest.json的“模块权限配置”中勾选)
succesCb: 申请权限成功回调函数
返回申请权限的结果,可能被用户允许 回调函数的参数event包含以下属性:
- granted - Array[String]字符串数组,已获取权限列表;
- deniedPresent - Array[String]字符串数据,已拒绝(临时)的权限列表;
- deniedAlways - Array[String]字符串数据,永久拒绝的权限列表。
errorCb: 申请权限失败回调函数
// eslint-disable-next-line plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], function (e) { if (e.deniedAlways.length > 0) { // 权限被永久拒绝 vm.$dialog.alert({ message: '录音权限被永久拒绝,请到设置权限里找到应用手动开启权限,否则将不能使用此功能。' }) } if (e.deniedPresent.length > 0) { // 权限被临时拒绝 vm.$dialog.confirm({ message: '拒绝开启录音权限,将不能使用此功能!确定拒绝开启吗?', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {}) .catch(() => { vm.requestPermission() }) } if (e.granted.length > 0) { // 权限被允许 } }, function (e) { vm.$dialog.alert({ message: '请求录音权限失败,请到设置权限里找到应用手动开启权限,否则将不能使用此功能。' }) })
但是对于 ios , 并没有直接的方法获取权限。最后采取的解决办法是,在没有录音权限的时候,先主动调用一次录音的api,此时 ios 回去请求一次录音的权限。如果没有选择不允许使用权限,则触发录音失败的回调函数。
vm.recorderPlus.record({}, function () { }, function (e) { if (e.code === 2) { vm.$dialog.alert({ message: '录音权限未允许,请到设置手动开启权限,否则将不能使用此功能。' }) } console.log(JSON.stringify(e)) }) vm.recorderPlus.stop()
因为两个平台需要区别对待,plus 提供了判断平台的方法,plus.os.name
获取录音权限的 requestPermission() 方法的完整代码为
requestPermission () { let vm = this // eslint-disable-next-line let platform = plus.os.name if (platform === 'Android') { // 动态申请权限 // eslint-disable-next-line plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], function (e) { if (e.deniedAlways.length > 0) { // 权限被永久拒绝 vm.$dialog.alert({ message: '录音权限被永久拒绝,请到设置权限里找到应用手动开启权限,否则将不能使用此功能。' }) } if (e.deniedPresent.length > 0) { // 权限被临时拒绝 vm.$dialog.confirm({ message: '拒绝开启录音权限,将不能使用此功能!确定拒绝开启吗?', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {}) .catch(() => { vm.requestPermission() }) } if (e.granted.length > 0) { // 权限被允许 } }, function (e) { vm.$dialog.alert({ message: '请求录音权限失败,请到设置权限里找到应用手动开启权限,否则将不能使用此功能。' }) }) } else if (platform === 'iOS') { vm.recorderPlus.record({}, function () { }, function (e) { if (e.code === 2) { vm.$dialog.alert({ message: '录音权限未允许,请到设置手动开启权限,否则将不能使用此功能。' }) } console.log(JSON.stringify(e)) }) vm.recorderPlus.stop() } else { this.record() } },
录音 record()方法的完整代码
data () { return { recording: false, // 是否有录音在进行中 timeLimit: 60, // 时间限制(s) remainTime: 60, // 剩余时间(s) timeOut: 10, // 剩余时间提示(s) } }, record () { let vm = this if (vm.recording) { return false } vm.recording = true if (vm.timer) { clearInterval(vm.timer) } vm.remainTime = vm.timeLimit let duration = 0 vm.timer = setInterval(function () { duration++ vm.remainTime = vm.timeLimit - duration if (duration === vm.timeLimit) { vm.stopRecord() } }, 1000) let rates = vm.recorderPlus.supportedSamplerates vm.recorderPlus.record({filename: '_doc/audio/', format: 'wav', samplerate: rates[0]}, function (path) { console.log('录音文件的路径为:' + path) }, function (e) { let error = JSON.stringify(e) vm.$toast('Audio record failed: ' + error) if (vm.timer) { clearInterval(vm.timer) } if (vm.recording) { vm.recording = false } }) },
结束录音的方法
stopRecord () { let vm = this if (vm.timer) { clearInterval(vm.timer) } if (vm.recording) { vm.recording = false vm.recorderPlus.stop() } },
写在最后:此解决方案,在开发时,我只用 iPhone X 和 OPPO R11 进行了测试,没有发现问题。公司资源有限,后来测试也没有测试到所有机型。现在 app 已经发布到应用市场,随着用户的增加,目前为止,还没有收到录音获取权限不起作用的反馈。如有问题,欢迎大家分享反馈。
更多相关内容 -
Linux内核学习笔记——Linux中的用户组和权限管理(UID是什么?)
2022-04-22 21:30:14目录一、背景进程权限最小权限原则linux系统安全模型用户用户组用户和组的关系安全上下文进程的用户ID函数setreuid和setregid函数seteuid和setegid思考:UID能为TEE安全世界带来什么用处呢? 一、背景 Linux的用户在...目录
一、背景
Linux的用户在登录(login)之后,就带有一个用户身份(user ID, UID)和一个组身份(group ID, GID)。
一般来说,Linux的用户信息保存在
/etc/passwd
中,组信息保存在/etc/group
中,文件的每一行代表一个用户/组。早期的Linux将密码以名码的形式保存在/etc/passwd
中,而现在则多以暗码(也就是加密之后的形式)的形式保存在/etc/shadow
中。将密码存储在/etc/shadow
中提高了密码的安全性,因为/etc/passwd
允许所有人查看,而/etc/shadow
只允许root用户查看。进程权限
在Linux中,用户的指令是在进程的范围内进行的。当我们向对某个文件进行操作的时候,我们需要在进程中运行一个程序,在进程中对文件打开,并进行读、写或者执行的操作。因此,我们需要将用户的权限传递给进程,以便进程真正去执行操作。例如我们有一个文件a.txt, 文件中为一个字符串:
Hello world!
我以用户Vamei的身份登录,并在
shell
中运行如下命令:$cat a.txt
整个运行过程以及文件读取如下:
我们可以看到,整个过程中我们会有两个进程,- 一个是shell本身(
2256
) - 一个是shell复制自身,再运行
/bin/cat
(9913
)。
图中的
fork, exec, PID
可参看Linux
进程基础。第二个进程总共对文件系统进行了两次操作,一次是执行(x
)文件/bin/cat
,另外一次是读取(r
)文件a.txt
。使用$ls -l
查看这两个文件的权限:$ls -l /bin/cat -rwxr-xr-x 1 root root 46764 Apr 22 2022 /bin/cat $ls -l a.txt -rw-rw-r-- 1 Vamei Vamei 22 Apr 7 09:14 a.txt
/bin/cat
让所有用户都享有执行的权利,而Vamei作为a.txt的拥有者,对a.txt享有读取的权利。在进行这两次操作的时候,尽管用户Vamei拥有相应的权限,但我们发现,真正做工作的是进程
9913
。我们要让这个进程得到相应的权限。实际上,每个进程会维护有如下6个ID
:- 真实身份: real UID, real GID
- 有效身份: effective UID, effective GID
- 存储身份:saved UID, saved GID
其中,真实身份是我们登录使用的身份,有效身份是当该进程真正去操作文件时所检查的身份,存储身份较为特殊,我们等一下再深入。当进程fork的时候,真实身份和有效身份都会复制给子进程。大部分情况下,真实身份和有效身份都相同。当Linux完成开机启动之后,
init
进程会执行一个login
的子进程。我们将用户名和密码传递给login
子进程。login在查询了/etc/passwd
和/etc/shadow
,并确定了其合法性之后,运行(利用exec
)一个shell
进程,shell
进程真实身份被设置成为该用户的身份。由于此后fork
此shell
进程的子进程都会继承真实身份,所以该真实身份会持续下去,直到我们登出并以其他身份再次登录(当我们使用su
成为root
的时候,实际上就是以root
身份再次登录,此后真实身份成为root
)。最小权限原则
每个进程为什么不简单地只维护真实身份,却选择费尽麻烦地去维护有效身份和存储身份呢?这牵涉到Linux的“最小特权”(least priviledge)的原则。Linux通常希望进程只拥有足够完成其工作的特权,而不希望赋予更多的特权给它。从设计上来说,最简单的是赋予每个进程以super user的特权,这样进程就可以想做什么做什么。然而,这对于系统来说是一个巨大的安全漏洞,特别是在多用户环境下,如果每个用户都享有无限制的特权,就很容易破坏其他用户的文件或者系统本身。“最小特权”就是收缩进程所享有的特权,以防进程滥用特权。
然而,进程的不同阶段可能需要不同的特权。比如一个进程最开始的有效身份是真实身份,但运行到中间的时候,需要以其他的用户身份读入某些配置文件,然后再进行其他的操作。为了防止其他的用户身份被滥用,我们需要在操作之前,让进程的有效身份变更回来成为真实身份。这样,进程需要在两个身份之间变化。
二、linux系统安全模型
系统资源分派:
- Authenticaiton认证,验证用户身份
- Authorization授权,不同的用户设置不同权限
- Accounting:审计
简单概括安全模型为linux系统需要知道登录验证用户的身份,登录用户基于身份会有不同的权限访问系统文件,同时也会有审计功能来知道登录用户在系统什么时间做了什么。
用户
linux中每个用户是通过UID来唯一标识的
root
管理员UID
为0普通用户:1-60000自动分配 系统用户:1-499(Centos6以前),1-999(CentOS7以后) 登录普通用户:500+(Centos6以前),1000+(CentOS7以后)。
用户组
linux中可以将一个或者多个用户加入用户组中,用户组是通过
GID
来唯一标识的。管理员组
root 0
普通组:
系统组:1-499(CentOS6以前),1-999(CentOS7以后) 对守护进程获取资源进行权限分配 普通组:500+(CentOS6以前),1000+(CentOS7以后),给用户使用
用户和组的关系
用户的主组:用户必须属于一个切治愈后一个驻足,默认创建用户时会自动创建和用户名的组,做为用户的主要组,由于此组中只有一个用户,称为私有组。
用户附加组:一个用户可以属于0个或多个辅助组。
安全上下文
能不能访问资源,是由执行命令者的身份决定的
Linux安全上下文Context:运行中的程序,即进程 (process),以进程发起者的身份运行,进程所能够访问资源的权限取决于进程的运行者的身份
比如:分别以root
和ZP1015
的身份运行/bin/cat
/etc/shadow
,得到的结果是不同的,资源能否能被访问,是由运行者的身份决定,非程序本身。要想访问资源先检查权限,没权限先赋权!
ZP1015 用户可以查看passwd但是无法查看shadow文件[root@C8-1 ~]# su ZP1015 [ZP1015@C8-1 root]$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin systemd-coredump:x:999:997:systemd Core Dumper:/:/sbin/nologin systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin polkitd:x:998:996:User for polkitd:/:/sbin/nologin unbound:x:997:994:Unbound DNS resolver:/etc/unbound:/sbin/nologin libstoragemgmt:x:996:993:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin setroubleshoot:x:995:992::/var/lib/setroubleshoot:/sbin/nologin clevis:x:994:990:Clevis Decryption Framework unprivileged user:/var/cache/clevis:/sbin/nologin cockpit-ws:x:993:989:User for cockpit-ws:/nonexisting:/sbin/nologin sssd:x:992:988:User for sssd:/:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin chrony:x:991:987::/var/lib/chrony:/sbin/nologin tcpdump:x:72:72::/:/sbin/nologin ZP1015:x:1000:1000::/home/ZP1015:/bin/bash [ZP1015@C8-1 root]$ cat /etc/shadow cat: /etc/shadow: Permission denied
进程的用户ID
在Unix系统中,特权以及访问控制是基于用户ID和组ID的。当程序需要增加特权,或者需要访问当前并不允许访问的资源时,我们需要更换自己的用户ID或组ID,使得新ID具有合适的特权或访问权限。与此类似,当程序需要降低其特权或阻止对某些资源的访问时,也需要更换用户ID或组ID,新ID不具有相应特权或访问这些资源的能力。
一般而言,在设计应用时,我们总是试图使用最小特权(least privilege)模型。依照此模型,我们的程序应当只具有为完成给定任务所需的最小特权。这降低了由恶意用户试图哄骗我们的程序以未料的方式使用特权造成的安全性风险。
可以用setuid函数设置实际用户ID、有效用户ID。与此类似,可以用setgid函数设置实际组ID和有效组ID。
#include <sys/types.h> #include <unistd.h> int setuid(uid_t uid); int setgid(gid_t gid); /* 两个函数返回值:若成功,返回0;若出错,返回-1 */
关于谁能更改ID有若干规则。现在优先考虑更改用户ID的规则(关于用户ID我们所说明的一切都适用于组ID):
若进程具有超级用户权限,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID(saved set-user-ID)设置为UID。
若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid。不更改实际用户ID和保存的设置用户ID。
如果上面两个条件都不满足,则errno设置为EPERM,并返回-1。
关于内核所维护的3个用户ID,还需要注意以下几点:
只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,由login程序设置的,而且绝不会改变它。因为login是一个超级用户进程,当它调用setuid时,设置所有3个用户ID。
仅当对程序文件设置了设置用户ID位时,exec才设置有效用户ID。如果设置用户ID位没有设置,exec函数不会改变有效用户ID,而将维持其现有值。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID。自然地,不能将有效用户ID设置为任一随机值。
保存的设置用户ID是由exec复制有效用户ID而得到的。如果设置了文件的设置用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID以后, 这个副本就被保存起来。
下表总结了更改这3个用户ID的不同方法:
注意: getuid和geteuid函数只能获得实际的用户ID和有效公户ID的当前值。我们没有可移植的方法去获得保存的设置用户ID的当前值。函数setreuid和setregid
历史上,BSD支持setreuid函数,其功能是交换实际用户ID和有效用户ID的值。
#include <sys/types.h> #include <unistd.h> int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid); /* * 描述: * setreuid()设置调用进程的实际用户ID和有效用户ID。 * 假如上述函数参数的任何一个值为-1的话,则保持该对应的ID不变。 * 对于非特权进程,只能将有效用户ID设置为 * 实际用户ID、有效用户ID或者保存的设置用户ID * 对于非特权用户,只能将实际用户ID设置为实际用户ID或者有效用户ID * 正是由于上述这两点,所以前面才会讲 “功能是'交换'实际用户ID和有效用户ID的值” * 注: 假如实际用户ID或有效用户ID被设置为不同于原实际用户ID的话, * 保存的设置用户ID(saved set-user-ID)会被设置为 * 新的有效用户ID。 * */
规则很简单:一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置用户ID程序交换成普通的用户权限,以后又可再次交换回设置用户ID权限。POSIX.1引进了保存的设置用户ID特性后,其规则也相应加强,它允许一个非特权用户将其有效用户ID设置为保存的设置用户ID。
函数seteuid和setegid
POSIX.1包含了两个函数seteuid和setegid。它们类似于setuid和setgid,但只能更改有效用户ID和有效组ID。
下图给出了上面所述的更改3个不同用户ID的各个函数:
三、思考:UID能为TEE安全世界带来什么用处呢?
1、CA-TA访问控制,我们可以限定哪些GID下进程可以访问TA的服务。毕竟GID修改需要root权限,我们可以防止普通用户滥调用安全下服务,毕竟UID可以作为进程的身份唯一标识。
- 一个是shell本身(
-
详解nodejs中express搭建权限管理系统
2021-01-01 20:44:31通常需要定义资源,把资源调配给用户,通过判断用户是否有权限增删改查来实现。 初衷: 使用express开发过的项目大大小小加在一起也有二十多个了,之前做的各个项目都是独立存在的。最近领导建议说把这些小项目整合... -
Spring Security用户认证和权限控制(自定义实现)
2019-03-29 10:04:48Spring Security用户认证和权限控制(自定义实现)1 说明2 用户认证相关的自定义实现2.1 自定义用户认证页面2.2 自定义退出功能2.3 自定义用户认证拦截器2.4 自定义用户认证处理器2.5 自定义用户认证对象2.6 自定义...Spring Security用户认证和权限控制(自定义实现)
1 说明
Spring Security用户认证和权限控制(默认实现)这篇文章介绍了Spring Security默认实现的用户认证和权限控制这两大功能的使用方法和原理分析,但是,Spring Security框架也能够支持自定义的用户认证和权限控制的实现逻辑。
本文主要是在Spring Security用户认证和权限控制(默认实现)这篇文章的基础上介绍自定义的用户认证和权限控制的实现方法,以帮助读者实现满足个性化需求的用户认证和权限控制的功能。
2 用户认证相关的自定义实现
2.1 自定义用户认证页面
Spring Security默认的用户认证页面不太美观,也只能且必须输入用户名和密码,因此,一般都会选择自定义用户认证页面。自定义用户认证页面只需要以下几步:
- 在Spring Security配置类中通过http.formlogin()设置表单登录时,通过loginPage(" URL")来指定请求认证页面的URL,以及通过loginProcessingUrl(" URL")来指定自定义的用户认证页面提交认证表单后发送请求的URL。当然,还可以通过defaultSuccessUrl(" URL")来指定默认认证成功后请求的URL。如下图所示:
- 在Controller中写一个Get类型的方法,该方法的请求URL要与第1步中loginPage(" URL")配置的URL保持一致(本文中都为/login),且请求该方法后跳转到自定义的用户认证页面,如下图所示:
- 创建一个自定义的用户认证页面,页面的文件名称要与第2步中定义的方法的返回值保持一致(本文中都为login.html),且页面中表单提交的URL要与第1步中loginProcessingUrl(" URL")配置的URL保持一致(本文中都为/login),表单中可以根据个性化需求设定用户认证所需要的认证信息,如下图所示:
注意:如果想要在自定义的用户认证页面中使用"记住我"功能,则需要在自定义的用户认证页面中设置一个参数名为remember-me的选择框,并设置值为true,如上图中所示。
2.2 自定义退出功能
Spring Security提供了默认的用户认证和退出功能,但是当使用了自定义的用户认证页面之后,默认的退出功能就会无法使用。这是因为使用了自定义的用户认证页面后,Spring Security框架就不会创建DefaultLogoutPageGeneratingFilter对象(该对象主要是拦截GET类型的/logout请求并跳转到退出确认页面,用户在退出确认页面点击了确认按钮后才发送POST类型的/logout请求),通过在浏览器地址栏中请求/logout是GET类型的请求,因此,请求无法被处理,从而报错。而真正的退出需要POST类型的/logout请求。关于自定义用户认证页面导致退出功能异常的原因与解决方案具体请见Spring Security使用自定义的用户登录页面导致退出功能异常的原因分析与解决方案这篇文章。
我们可以在Spring Security配置类中通过http.logout()设置退出功能,并通过logoutRequestMatcher(RequestMatcher)来指定对GET类型和POST类型的/logout请求都执行退出操作,如下图所示:
同时,也可以通过logoutUrl(" URL")来指定执行退出操作的URL(本文中都为/logout),以及通过logoutSuccessUrl(" URL")指定退出成功后自动请求的URL。2.3 自定义记住我功能
Spring Security默认实现了"记住我"功能,具体而言是:用户认证时如果选择了"记住我"功能,则认证成功之后,Spring Security框架会通过UUID随机生成一个能够代表该用户已认证的Token,然后把该Token保存到内存中,同时保存到用户浏览器的Cookie中;当用户在Token的有效期内,关闭了浏览器之后再次发送访问请求时,就会通过Cookie中的Token自动通过认证,即不需要用户再输入账号和密码进行认证。
Spring Security默认是将代表用户已经认证的Token保存在内存中,但是也能够支持把Token保存到数据库中,我们可以在Spring Security配置类中通过http.rememberMe()设置记住我功能,并通过tokenRepository(PersistentTokenRepository)来指定保存Token的位置,如下图所示:
因此,需要我们自己创建一个PersistentTokenRepository对象,并指定数据源,如下图所示:
2.4 自定义用户认证拦截器
用户认证拦截器主要是拦截用户发送的认证请求,然后从请求中获取用户账号和密码等认证信息并封装成一个未认证的AthenticationToken对象,然后调用AuthenticationManager对AthenticationToken进行认证。
自定义的用户认证拦截器,需要继承AbstractAuthenticationProcessingFilter,并重写public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)方法,本文自定义的用户认证拦截器的完整代码如下所示:
package com.custom.authentication.server.authentication; import com.custom.authentication.server.constant.ConfigConstant; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定义的用户名密码认证过滤器 */ public class CustomUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private String usernameParameter = "username"; private String passwordParameter = "password"; private boolean postOnly = true; public CustomUsernamePasswordAuthenticationFilter() { /** * 设置该过滤器对POST请求/login进行拦截 */ super(new AntPathRequestMatcher(ConfigConstant.LOGIN_FORM_SUBMIT_URL, "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { /** * 从http请求中获取用户输入的用户名和密码信息 * 这里接收的是form形式的参数,如果要接收json形式的参数,修改这里即可 */ String username = this.obtainUsername(request); String password = this.obtainPassword(request); if(StringUtils.isEmpty(username) && StringUtils.isEmpty(password)) { throw new UsernameNotFoundException("CustomUsernamePasswordAuthenticationFilter获取用户认证信息失败"); } /** * 使用用户输入的用户名和密码信息创建一个未认证的用户认证Token */ CustomUsernamePasswordAuthenticationToken authRequest = new CustomUsernamePasswordAuthenticationToken(username, password); /** * 设置一些详情信息 */ this.setDetails(request, authRequest); /** * 通过AuthenticationManager调用相应的AuthenticationProvider进行用户认证 */ return this.getAuthenticationManager().authenticate(authRequest); } } protected String obtainUsername(HttpServletRequest request) { return request.getParameter(this.usernameParameter); } protected String obtainPassword(HttpServletRequest request) { return request.getParameter(this.passwordParameter); } protected void setDetails(HttpServletRequest request, CustomUsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); } public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return this.usernameParameter; } public final String getPasswordParameter() { return this.passwordParameter; } }
自定义的用户认证拦截器要在构造方法中指定拦截的认证请求(本文中是POST类型的/login请求,可根据需求设置),并在attemptAuthentication()方法中实现获取用户认证信息、封装AuthenticationToken对象、调用AuthenticationManager对AuthenticationToken进行认证等逻辑。
注意:自定义的用户认证拦截器的使用请见2.9小节。
2.5 自定义用户认证处理器
用户认证处理器主要是对用户提交的认证信息进行认证,Spring Security默认实现的认证处理器的认证处理逻辑并不一定符合所有的业务需求(例如,默认的认证处理无法处理验证码),因此,可以自定义用户认证处理器。
自定义的用户认证处理器,需要实现AuthenticationProvider接口,主要是实现public Authentication authenticate(Authentication authentication)方法和public boolean supports(Class<?> authentication)方法,前者主要是实现具体的认证逻辑,后者主要是指定认证处理器能对哪种AuthenticationToken对象进行认证。本文自定义的用户认证处理器的完整代码如下所示:
package com.custom.authentication.server.authentication; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * 自定义的用户名密码认证实现类 */ @Component("customUsernamePasswordAuthenticationProvider") public class CustomUsernamePasswordAuthenticationProvider implements AuthenticationProvider { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserDetailsService userDetailsServiceImpl; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { /** * 将未认证的Authentication转换成自定义的用户认证Token */ CustomUsernamePasswordAuthenticationToken authenticationToken = (CustomUsernamePasswordAuthenticationToken) authentication; /** * 根据用户Token中的用户名查找用户信息,如果有该用户信息,则验证用户密码是否正确 */ UserDetails user = userDetailsServiceImpl.loadUserByUsername((String)(authenticationToken.getPrincipal())); if(user == null) { throw new InternalAuthenticationServiceException("CustomUsernamePasswordAuthenticationProvider获取认证用户信息失败"); } else if(!this.passwordEncoder.matches((CharSequence) authenticationToken.getCredentials(), user.getPassword())) { throw new BadCredentialsException("用户名或密码不正确"); } /** * 认证成功则创建一个已认证的用户认证Token */ CustomUsernamePasswordAuthenticationToken authenticationResult = new CustomUsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); /** * 设置一些详情信息 */ authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { /** * 指定该认证处理器能对CustomUsernamePasswordAuthenticationToken对象进行认证 */ return CustomUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
注意:自定义的用户认证处理器的使用请见2.9小节。
2.6 自定义用户认证对象
用户认证对象是在用户认证拦截器中创建的,在用户认证处理器中使用的,具体可见2.4节和2.5节中。
用户认证对象(AuthenticationToken)中封装的是用户认证信息,例如,UsernamePasswordAuthenticationToken中封装的是用户名和密码。实际业务中,可能需要根据不同的用户信息进行认证(例如,手机号和验证码),此时就需要自定义用户认证对象。
自定义的用户认证对象,需要继承AbstractAuthenticationToken类,并设定根据认证时使用的是哪些信息。本文自定义的用户认证对象的完整代码如下所示:
package com.custom.authentication.server.authentication; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; /** * 自定义的用户名密码认证对象 */ public class CustomUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { /** * 用户名 */ private final Object principal; /** * 密码 */ private Object credentials; /** * 创建未认证的用户名密码认证对象 */ public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials) { super((Collection)null); this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); } /** * 创建已认证的用户密码认证对象 */ public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } else { super.setAuthenticated(false); } } @Override public void eraseCredentials() { super.eraseCredentials(); this.credentials = null; } }
注意:自定义的用户认证对象的使用请见2.9小节。
2.7 自定义用户认证成功处理器
用户认证成功处理器是在用户认证成功之后调用,主要是执行一些额外的操作(例如,操作Cookie、页面跳转等)。
自定义的用户认证成功处理器可以通过实现AuthenticationSuccessHandler接口,或者通过继承AbstractAuthenticationTargetUrlRequestHandler类及其子类来实现。本文自定义的用户认证成功处理器是通过继承AbstractAuthenticationTargetUrlRequestHandler的子类SavedRequestAwareAuthenticationSuccessHandler来实现的,完整代码如下所示:
package com.custom.authentication.server.authentication; import com.custom.authentication.server.constant.ConfigConstant; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定义的用户认证成功处理器 */ @Component("customAuthenticationSuccessHandler") public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; public CustomAuthenticationSuccessHandler() { /** * 指定默认登录成功请求的URL和是否总是使用默认登录成功请求的URL * 注意:自定义的认证成功处理器,如果不指定,默认登录成功请求的URL是"/" */ this.setDefaultTargetUrl(ConfigConstant.DEFAULT_LOGIN_SUCCESSFUL_REQUEST_URL); this.setAlwaysUseDefaultTargetUrl(ConfigConstant.ALWAYS_USE_DEFAULT_LOGIN_SUCCESSFUL_REQUEST_URL); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { /** * 如果配置ConfigConstant.LOGIN_RESPONSE_TYPE="JSON",则返回JSON,否则使用页面跳转 */ if("JSON".equalsIgnoreCase(ConfigConstant.LOGIN_RESPONSE_TYPE)) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); } else { super.onAuthenticationSuccess(request, response, authentication); } } }
需要说明的是,如果自定义了用户认证成功处理器,则在Spring Security配置类中通过http.formlogin().defaultSuccessUrl(" URL")来指定的默认认证成功后请求的URL配置并不会对自定义的用户认证成功处理器有效,因为该配置只对Spring Security默认的用户认证成功处理器有效。要想使得自定义的用户认证成功之后自动请求指定的URL,则需要在自定义的用户认证成功处理器中通过设置defaultTargetUrl参数的值的方式来实现,如下图所示:
注意:自定义的用户认证成功处理器的使用请见2.9小节。2.8 自定义用户认证失败处理器
用户认证失败处理器是在用户认证失败之后调用,主要是执行一些额外的操作(例如,操作Cookie、页面跳转等)。
自定义的用户认证失败处理器可以通过实现AuthenticationFailureHandler接口,或者通过继承AuthenticationFailureHandler接口的其它实现类来实现。本文自定义的用户认证失败处理器是通过继承AuthenticationFailureHandler接口的实现类SimpleUrlAuthenticationFailureHandler来实现的,完整代码如下所示:
package com.custom.authentication.server.authentication; import com.custom.authentication.server.constant.ConfigConstant; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定义的用户认证失败处理器 */ @Component("customAuthenticationFailureHandler") public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { /** * 如果配置ConfigConstant.LOGIN_RESPONSE_TYPE="JSON",则返回JSON,否则使用页面跳转 */ if("JSON".equalsIgnoreCase(ConfigConstant.LOGIN_RESPONSE_TYPE)) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage())); } else { super.onAuthenticationFailure(request, response, exception); } } }
注意:自定义的用户认证失败处理器的使用请见2.9小节。
2.9 自定义用户认证处理逻辑的应用
自定义的用户认证页面、退出功能、"记住我"功能的使用方法比较简单,分别按照2.1节、2.2节、2.3节中的实现即可。
自定义的用户认证拦截器、用户认证处理器、用户认证对象、认证成功处理器、认证失败处理器的使用方法却要稍微复杂,仅仅只是自定义实现了这些部分还不够,还要使这些自定义的实现生效才行。具体做法是:
- 通过继承SecurityConfigurerAdapter类实现一个配置类,并重写public void configure(HttpSecurity http)方法,在该方法中对自定义的用户认证拦截器进行设置,然后再将自定义的用户认证拦截器和处理器加入到过滤器链中特定的位置。
- 然后在Spring Security配置类中应用第1步实现的配置类。
本文实现的配置类的完整代码如下所示:
package com.custom.authentication.server.config; import com.custom.authentication.server.authentication.CustomAuthenticationFailureHandler; import com.custom.authentication.server.authentication.CustomAuthenticationSuccessHandler; import com.custom.authentication.server.authentication.CustomUsernamePasswordAuthenticationFilter; import com.custom.authentication.server.authentication.CustomUsernamePasswordAuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.stereotype.Component; /** * 自定义的用户名密码认证配置类 */ @Component public class CustomUsernamePasswordAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired CustomUsernamePasswordAuthenticationProvider customUsernamePasswordAuthenticationProvider; @Autowired CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; @Autowired CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Override public void configure(HttpSecurity http) throws Exception { CustomUsernamePasswordAuthenticationFilter authenticationFilter = new CustomUsernamePasswordAuthenticationFilter(); /** * 自定义用户认证处理逻辑时,需要指定AuthenticationManager,否则无法认证 */ authenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); /** * 自定义用户认证处理逻辑时,需要指定RememberMeServices,否则自定义用户认证的"记住我"功能异常 */ authenticationFilter.setRememberMeServices(http.getSharedObject(RememberMeServices.class)); /** * 指定自定义的认证成功和失败的处理器 */ authenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); authenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler); /** * 把自定义的用户名密码认证过滤器和处理器添加到UsernamePasswordAuthenticationFilter过滤器之前 */ http .authenticationProvider(customUsernamePasswordAuthenticationProvider) .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
注意:必须通过setAuthenticationManager()方法给自定义的用户认证拦截器注入一个AuthenticationManager对象才能调用相应的用户认证处理器进行认证。必须通过setRememberMeServices()方法给自定义的用户认证拦截器注入一个RememberMeServices对象才能在自定义的用户认证中使用"记住我"功能。
本文的Spring Security配置类的完整代码如下所示:
package com.custom.authentication.server.config; import com.custom.authentication.server.constant.ConfigConstant; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import javax.sql.DataSource; /** * Spring Security配置类 */ @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private UserDetailsService userDetailsServiceImpl; @Autowired private CustomUsernamePasswordAuthenticationConfig customUsernamePasswordAuthenticationConfig; /** * 用户认证配置 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 指定用户认证时,默认从哪里获取认证用户信息 */ auth.userDetailsService(userDetailsServiceImpl); } /** * Http安全配置 */ @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .apply(customUsernamePasswordAuthenticationConfig) .and() .formLogin() .loginPage(ConfigConstant.REQUEST_LOGIN_PAGE_URL) .loginProcessingUrl(ConfigConstant.LOGIN_FORM_SUBMIT_URL) .defaultSuccessUrl(ConfigConstant.DEFAULT_LOGIN_SUCCESSFUL_REQUEST_URL, ConfigConstant.ALWAYS_USE_DEFAULT_LOGIN_SUCCESSFUL_REQUEST_URL) .permitAll() .and() .logout() .logoutUrl(ConfigConstant.LOGOUT_URL) .logoutSuccessUrl(ConfigConstant.LOGOUT_SUCCESSFUL_REQUEST_URL) .logoutRequestMatcher(getLogoutRequestMatchers()) .permitAll() .and() .rememberMe() .tokenRepository(getPersistentTokenRepository()) .tokenValiditySeconds(ConfigConstant.REMEMBER_ME_SECOND) .and() .authorizeRequests() .antMatchers(ConfigConstant.PERMIT_ALL_REQUEST_URL_ARRAY) .permitAll() .anyRequest() .authenticated(); } /** * 密码加密器 */ @Bean public PasswordEncoder passwordEncoder() { /** * BCryptPasswordEncoder:相同的密码明文每次生成的密文都不同,安全性更高 */ return new BCryptPasswordEncoder(); } /** * 指定保存用户登录“记住我”功能的Token的方法: * 默认是使用InMemoryTokenRepositoryImpl将Token放在内存中, * 如果使用JdbcTokenRepositoryImpl,会创建persistent_logins数据库表,并将Token保存到该表中。 */ @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); /** * 系统启动时自动创建表,只需要在第一次启动系统时创建即可,因此这行代码只在需要创建表时才启用 */ // jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } /** * 自定义退出登录的RequestMatcher */ private OrRequestMatcher getLogoutRequestMatchers() { /** * 用户退出登录时,匹配GET请求/logout和POST请求/logout,使得这两种请求都执行退出登录操作 * 默认情况(未禁用跨域请求伪造,且自定义用户登录页面)下,只对POST请求/logout才执行退出登录操作 */ AntPathRequestMatcher getLogoutRequestMatcher = new AntPathRequestMatcher(ConfigConstant.LOGOUT_URL, "GET"); AntPathRequestMatcher postLogoutRequestMatcher = new AntPathRequestMatcher(ConfigConstant.LOGOUT_URL, "POST"); return new OrRequestMatcher(getLogoutRequestMatcher, postLogoutRequestMatcher); } }
其中应用第1步实现的配置类的代码如下图中所示:
3 权限控制相关的自定义实现
Spring Security用户认证和权限控制(默认实现)这篇文章中介绍了方法级别的权限控制,而本文要介绍的是URL级别的权限控制。
3.1 自定义权限数据获取类
Spring Security的方法级别的权限控制的默认实现是把权限信息保存在内存中,而基于URL级别的权限控制的实际应用中通常是把权限信息保存在数据库中。因此,URL级别的权限控制通常都需要我们自己实现从数据库中获取权限信息。
自定义的权限数据获取类可以通过实现FilterInvocationSecurityMetadataSource 接口来实现。本文自定义的权限数据获取类的完整代码如下所示:
package com.custom.authentication.server.permission; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * 自定义的资源(url)权限(角色)数据获取类 */ @Component("customFilterInvocationSecurityMetadataSource") public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { /** * 每个资源(url)所需要的权限(角色)集合 */ private static HashMap<String, Collection<ConfigAttribute>> map =null; /** * 获取每个资源(url)所需要的权限(角色)集合 * 这里应该从数据库中动态查询,这里为了方便而直接创建 */ private void getResourcePermission(){ map = new HashMap<>(); /** * 创建两个权限:ROLE_ADMIN 和 ROLE_EMPLOYEE */ ConfigAttribute adminRole = new SecurityConfig("ROLE_ADMIN"); ConfigAttribute employeeRole = new SecurityConfig("ROLE_EMPLOYEE"); /** * 创建两个权限集合 */ List<ConfigAttribute> adminUrlRoles = new ArrayList<>(); adminUrlRoles.add(adminRole); List<ConfigAttribute> employeeUrlRoles = new ArrayList<>(); employeeUrlRoles.add(employeeRole); /** * 设置资源(url)所需要的权限(角色)集合 */ map.put("/toAdmin", adminUrlRoles); map.put("/toEmployee", employeeUrlRoles); map.put("/toUser", null); map.put("/toAbout", null); map.put("/toHome", null); map.put("/getPrincipal", null); map.put("/getUserDetails", null); } /** * 获取用户请求的某个具体的资源(url)所需要的权限(角色)集合 * @param object 包含了用户请求的request信息 */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if(map ==null) { getResourcePermission(); } HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); /** * 遍历每个资源(url),如果与用户请求的资源(url)匹配,则返回该资源(url)所需要的权限(角色)集合, * 如果全都不匹配,则表示用户请求的资源(url)不需要权限(角色)即可访问 */ Iterator<String> iter = map.keySet().iterator(); while(iter.hasNext()) { String url = iter.next(); if(new AntPathRequestMatcher(url).matches(request)) { return map.get(url); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
注意:自定义的权限数据获取类的使用请见3.3小节。
3.2 自定义权限控制管理器
权限控制管理器主要是比对用户所拥有的权限和用户请求的URL或方法所需要的权限,以决定是否允许用户执行请求,如果比对成功,则允许,否则拒绝。
自定义的权限控制管理器主要是要实现权限比对的处理逻辑,可以通过实现AccessDecisionManager接口来实现。本文自定义的权限控制管理器的完整代码如下所示:
package com.custom.authentication.server.permission; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Iterator; /** * 自定义的权限控制管理器 */ @Component("customAccessDecisionManager") public class CustomAccessDecisionManager implements AccessDecisionManager { /** * 判断是否有权限 * @param auth 包含了UserDetails用户信息 * @param object 包含了request请求信息 * @param configAttributes 由CustomFilterInvocationSecurityMetadataSource.getAttributes(object)返回的请求的资源(url)所需要的权限(角色)集合 */ @Override public void decide(Authentication auth, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { /** * 如果请求的资源不需要权限,则直接放行 */ if(configAttributes == null || configAttributes.size() <= 0) { return; } /** * 判断用户所拥有的权限是否是资源所需要的权限之一,如果是则放行,否则拦截 */ Iterator<ConfigAttribute> iter = configAttributes.iterator(); while(iter.hasNext()) { String needRole = iter.next().getAttribute(); for(GrantedAuthority grantRole : auth.getAuthorities()) { if(needRole.trim().equals(grantRole.getAuthority().trim())) { return; } } } throw new AccessDeniedException("no privilege"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
注意:自定义的权限控制管理器的使用请见3.3小节。
3.3 自定义权限控制处理逻辑的应用
启用方法级别的权限控制,需要在主启动类上使用@EnableGlobalMethodSecurity()注解,并在请求方法的入口处使用类似于@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)的注解指定方法执行所需要的权限。
自定义的URL级别的权限控制的启用方法与方法级别的权限控制的启用方法不太一样,而是通过把自定义的权限数据获取类和权限控制管理器添加到自定义的过滤器中实现的,具体做法是:
- 自定义一个既继承了AbstractSecurityInterceptor类又实现了Filter接口的过滤器,并重写和实现其中的方法。
- 把自定义的权限数据获取类的对象注入到自定义的过滤器中,并且要能够通过调用重写的public SecurityMetadataSource obtainSecurityMetadataSource()方法即可获取到自定义的权限数据获取类的对象。
- 以构造方法的形式把自定义的权限控制管理器注入到自定义的过滤器中。
本文实现的自定义过滤器的完整代码如下所示:
package com.custom.authentication.server.permission; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * 自定义的权限验证过滤器 */ @Component public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { /** * 注入自定义的资源(url)权限(角色)获取类 */ @Autowired private FilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; /** * 注入自定义的权限验证管理器 */ @Autowired public void setAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) { super.setAccessDecisionManager(customAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); InterceptorStatusToken token = super.beforeInvocation(fi); try { /** * 执行下一个拦截器 */ fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public void destroy() {} @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.customFilterInvocationSecurityMetadataSource; } }
3 总结
本文对于使用Spring Security实现用户认证和权限控制功能当中可能需要个性化的部分进行了介绍,同时也对自定义实现中需要注意的一些小细节进行了特别说明。关于自定义实现的测试方法和Spring Security用户认证和权限控制(默认实现)这篇文章中的一样,因此就不过多重复。关于授权服务器、资源服务器的内容可以查阅以下几篇文章:
OAuth2授权服务器和四种授权方式 这篇文章介绍了授权服务器和四种授权方式的配置与使用方法。
OAuth2资源服务器 这篇文章介绍了基于方法级别的权限控制的资源服务器的配置与使用方法。如果觉得本文对您有帮助,请关注博主的微信公众号,会经常分享一些Java和大数据方面的技术案例!
- 在Spring Security配置类中通过http.formlogin()设置表单登录时,通过loginPage(" URL")来指定请求认证页面的URL,以及通过loginProcessingUrl(" URL")来指定自定义的用户认证页面提交认证表单后发送请求的URL。当然,还可以通过defaultSuccessUrl(" URL")来指定默认认证成功后请求的URL。如下图所示:
-
Android 网络请求、网络状态及各种权限判断
2018-04-26 11:36:33一、判断网络连接是否可用public static boolean isNetworkAvailable(Context context) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); ...一、判断网络连接是否可用
public static boolean isNetworkAvailable(Context context) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) { } else { //如果仅仅是用来判断网络连接 //则可以使用 cm.getActiveNetworkInfo().isAvailable(); NetworkInfo[] info = cm.getAllNetworkInfo(); if (info != null) { for (int i = 0; i < info.length; i++) { if (info[i].getState() == NetworkInfo.State.CONNECTED) { return true; } } } } return false; }
二、判断GPS是否打开
public static boolean isGpsEnabled(Context context) { LocationManager lm = ((LocationManager) context .getSystemService(Context.LOCATION_SERVICE)); List<String> accessibleProviders = lm.getProviders(true); return accessibleProviders != null && accessibleProviders.size() > 0; }
三、判断WIFI是否打开
public static boolean isWifiEnabled(Context context) { ConnectivityManager mgrConn = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); TelephonyManager mgrTel = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); return ((mgrConn.getActiveNetworkInfo() != null && mgrConn .getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED) || mgrTel .getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS); }
四、判断是否是3G网络
public static boolean is3rd(Context context) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkINfo = cm.getActiveNetworkInfo(); if (networkINfo != null && networkINfo.getType() == ConnectivityManager.TYPE_MOBILE) { return true; } return false; }
五、判断是wifi还是3g网络,用户的体现性在这里了,wifi就可以建议下载或者在线播放。
public static boolean isWifi(Context context) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkINfo = cm.getActiveNetworkInfo(); if (networkINfo != null && networkINfo.getType() == ConnectivityManager.TYPE_WIFI) { return true; } return false; }
Android开发请求网络方式详解
现在就简单的介绍一下Android常用的联网方式,包括JDK支持的HttpUrlConnection,Apache支持的HttpClient,以及开源的一些联网框架(譬如AsyncHttpClient)的介绍。本篇博客只讲实现过程和方式,不讲解原理,否则原理用文字很难以讲清,其实我们知道怎么去用,就可以解决一些基本开发需要了。
绝大多数的Android应用联网都是基于Http协议的,也有很少是基于Socket的,我们这里主要讲解基于Http协议的联网方式。讲解实例是建立在一个模拟的登录小模块中进行,登录请求数据仅仅只有username和password两个简单字段。HttpUrlConnection
HttpUrlConnection是JDK里提供的联网API,我们知道Android SDK是基于Java的,所以当然优先考虑HttpUrlConnection这种最原始最基本的API,其实大多数开源的联网框架基本上也是基于JDK的HttpUrlConnection进行的封装罢了,掌握HttpUrlConnection需要以下几个步骤:
1、将访问的路径转换成URL。
URL url = new URL(path);
2、通过URL获取连接。
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
3、设置请求方式。
conn.setRequestMethod(GET);
4、设置连接超时时间。
conn.setConnectTimeout(5000);
5、设置请求头的信息。
conn.setRequestProperty(User-Agent, Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0));
6、获取响应码
int code = conn.getResponseCode();
7、针对不同的响应码,做不同的操作
7.1、请求码200,表明请求成功,获取返回内容的输入流
InputStream is = conn.getInputStream();
7.2、将输入流转换成字符串信息
public class StreamTools { /** * 将输入流转换成字符串 * * @param is * 从网络获取的输入流 * @return */ public static String streamToString(InputStream is) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } baos.close(); is.close(); byte[] byteArray = baos.toByteArray(); return new String(byteArray); } catch (Exception e) { Log.e(tag, e.toString()); return null; } } }
7.3、若返回值400,则是返回网络异常,做出响应的处理。
HttpUrlConnection发送GET请求
/** * 通过HttpUrlConnection发送GET请求 * * @param username * @param password * @return */ public static String loginByGet(String username, String password) { String path = http://192.168.0.107:8080/WebTest/LoginServerlet?username= + username + &password= + password; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod(GET); int code = conn.getResponseCode(); if (code == 200) { InputStream is = conn.getInputStream(); // 字节流转换成字符串 return StreamTools.streamToString(is); } else { return 网络访问失败; } } catch (Exception e) { e.printStackTrace(); return 网络访问失败; } }
HttpUrlConnection发送POST请求
/** * 通过HttpUrlConnection发送POST请求 * * @param username * @param password * @return */ public static String loginByPost(String username, String password) { String path = http://192.168.0.107:8080/WebTest/LoginServerlet; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, application/x-www-form-urlencoded); String data = username= + username + &password= + password; conn.setRequestProperty(Content-Length, data.length() + ); // POST方式,其实就是浏览器把数据写给服务器 conn.setDoOutput(true); // 设置可输出流 OutputStream os = conn.getOutputStream(); // 获取输出流 os.write(data.getBytes()); // 将数据写给服务器 int code = conn.getResponseCode(); if (code == 200) { InputStream is = conn.getInputStream(); return StreamTools.streamToString(is); } else { return 网络访问失败; } } catch (Exception e) { e.printStackTrace(); return 网络访问失败; } }
HttpClient
HttpClient是开源组织Apache提供的Java请求网络框架,其最早是为了方便Java服务器开发而诞生的,是对JDK中的HttpUrlConnection各API进行了封装和简化,提高了性能并且降低了调用API的繁琐。
Android因此也引进了这个联网框架,我们再不需要导入任何jar或者类库就可以直接使用,值得注意的是Android官方已经宣布不建议使用HttpClient了,我们再开发的时候尽量少用吧,但是用了也无妨!HttpClient发送GET请求
1, 创建HttpClient对象
2,创建HttpGet对象,指定请求地址(带参数)
3,使用HttpClient的execute(),方法执行HttpGet请求,得到HttpResponse对象
4,调用HttpResponse的getStatusLine().getStatusCode()方法得到响应码
5,调用的HttpResponse的getEntity().getContent()得到输入流,获取服务端写回的数据
/** * 通过HttpClient发送GET请求 * * @param username * @param password * @return */ public static String loginByHttpClientGet(String username, String password) { String path = http://192.168.0.107:8080/WebTest/LoginServerlet?username= + username + &password= + password; HttpClient client = new DefaultHttpClient(); // 开启网络访问客户端 HttpGet httpGet = new HttpGet(path); // 包装一个GET请求 try { HttpResponse response = client.execute(httpGet); // 客户端执行请求 int code = response.getStatusLine().getStatusCode(); // 获取响应码 if (code == 200) { InputStream is = response.getEntity().getContent(); // 获取实体内容 String result = StreamTools.streamToString(is); // 字节流转字符串 return result; } else { return 网络访问失败; } } catch (Exception e) { e.printStackTrace(); return 网络访问失败; } }
HttpClient发送POST请求
1,创建HttpClient对象
2,创建HttpPost对象,指定请求地址
3,创建List,用来装载参数
4,调用HttpPost对象的setEntity()方法,装入一个UrlEncodedFormEntity对象,携带之前封装好的参数
5,使用HttpClient的execute()方法执行HttpPost请求,得到HttpResponse对象
6, 调用HttpResponse的getStatusLine().getStatusCode()方法得到响应码
7, 调用的HttpResponse的getEntity().getContent()得到输入流,获取服务端写回的数据
/** * 通过HttpClient发送POST请求 * * @param username * @param password * @return */ public static String loginByHttpClientPOST(String username, String password) { String path = http://192.168.0.107:8080/WebTest/LoginServerlet; try { HttpClient client = new DefaultHttpClient(); // 建立一个客户端 HttpPost httpPost = new HttpPost(path); // 包装POST请求 // 设置发送的实体参数 List<namevaluepair> parameters = new ArrayList<namevaluepair>(); parameters.add(new BasicNameValuePair(username, username)); parameters.add(new BasicNameValuePair(password, password)); httpPost.setEntity(new UrlEncodedFormEntity(parameters, UTF-8)); HttpResponse response = client.execute(httpPost); // 执行POST请求 int code = response.getStatusLine().getStatusCode(); if (code == 200) { InputStream is = response.getEntity().getContent(); String result = StreamTools.streamToString(is); return result; } else { return 网络访问失败; } } catch (Exception e) { e.printStackTrace(); return 访问网络失败; } }
开源联网框架
1、AsyncHttpClient
除了上述Android官方推荐的联网框架以外,在开源世界里关于联网框架真是太多太多了,例如afinal,xutils等等,都是一些开源大牛自己封装的联网框架,并且在GitHub开源社区中可以下载到,其实类似的开源联网框架基本上也是基于HttpUrlConnection的进一步封装,大大提高了性能,同时更加简化了使用方法,这里先介绍AsyncHttpClient的一些使用方法。 AsyncHttpClient是一个非常优秀的联网框架,不仅支持所有Http请求的方式,而且还支持文件的上传和下载,要知道用HttpUrlConnection写一个文件上传和下载健全功能是很需要花费一定时间和精力的,因为请求头实在是太多了,稍有不慎就会写错。但是AsyncHttpClient已经封装好了这些“麻烦”,我们只需要下载到AsyncHttpClient的jar包或者源码导入项目中,Http,上传,下载等等,只需要几个简单的api即可搞定。 AsyncHttpClient的GitHub主页:https://github.com/AsyncHttpClient/async-http-client/
AsyncHttpClient发送GET请求
1,将下载好的源码拷贝到src目录下
2,创建一个AsyncHttpClient的对象
3,调用该类的get方法发送GET请求,传入请求资源地址URL,创建AsyncHttpResponseHandler对象
4,重写AsyncHttpResponseHandler下的两个方法,onSuccess和onFailure方法
/** * 通过AsyncHttpClient发送GET请求 */ public void loginByAsyncHttpGet() { String path = http://192.168.0.107:8080/WebTest/LoginServerlet?username=zhangsan&password=123; AsyncHttpClient client = new AsyncHttpClient(); client.get(path, new AsyncHttpResponseHandler() { @Override public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { // TODO Auto-generated method stub Log.i(TAG, 请求失败: + new String(arg2)); } @Override public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { // TODO Auto-generated method stub Log.i(TAG, 请求成功: + new String(arg2)); } }); }
AsyncHttpClient发送POST请求
1,将下载好的源码拷贝到src目录下
2,创建一个AsyncHttpClient的对象
3,创建请求参数,RequestParams对象
4,调用该类的post方法发POST,传入请求资源地址URL,请求参数RequestParams,创建AsyncHttpResponseHandler对象
5,重写AsyncHttpResponseHandler下的两个方法,onSuccess和onFailure方法
/** * 通过AsyncHttpClient发送POST请求 */ public void loginByAsyncHttpPost() { String path = http://192.168.0.107:8080/WebTest/LoginServerlet; AsyncHttpClient client = new AsyncHttpClient(); RequestParams params = new RequestParams(); params.put(username, zhangsan); params.put(password, 123); client.post(path, params, new AsyncHttpResponseHandler() { @Override public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { // TODO Auto-generated method stub Log.i(TAG, 请求失败: + new String(arg2)); } @Override public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { // TODO Auto-generated method stub Log.i(TAG, 请求成功: + new String(arg2)); } });
AsyncHttpClient上传文件
1,将下载好的源码拷贝到src目录下
2,创建一个AsyncHttpClient的对象
3,创建请求参数,RequestParams对象,请求参数仅仅包含文件对象即可,例如:
params.put(profile_picture, new File(/sdcard/pictures/pic.jpg));
4,调用该类的post方法发POST,传入请求资源地址URL,请求参数RequestParams,创建AsyncHttpResponseHandler对象
5,重写AsyncHttpResponseHandler下的两个方法,onSuccess和onFailure方法
2、Android Volley框架使用详解
Volley是一个由Google官方推出的网络通信库,它使得Android进行网络请求时更加方便、快速、健壮,同时对网络图片加载也提供了良好的支持。
1、获取volley源码
将编译得到的jar包引入到我们的项目中;可以通过网络搜索下载,也可以在此下载
http://download.csdn.net/detail/fenghai22/84720452、使用实例
使用Volley必须在AndroidManifest.xml中添加 android.permission.INTERNET权限,使用Volley时Google建议创建volley单例工具类
public class VolleySingleton { private static VolleySingleton volleySingleton; private RequestQueue mRequestQueue; private ImageLoader mImageLoader; private Context mContext; public VolleySingleton(Context context) { this.mContext = context; mRequestQueue = getRequestQueue(); mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache(){ private final LruCache<String,Bitmap> cache = new LruCache<String ,Bitmap>(20); @Override public Bitmap getBitmap(String url){ return cache.get(url); } @Override public void putBitmap(String url,Bitmap bitmap){ cache.put(url,bitmap); } }); } public static synchronized VolleySingleton getVolleySingleton(Context context){ if(volleySingleton == null){ volleySingleton = new VolleySingleton(context); } return volleySingleton; } public RequestQueue getRequestQueue(){ if(mRequestQueue == null){ mRequestQueue = Volley.newRequestQueue(mContext.getApplicationContext()); } return mRequestQueue; } public <T> void addToRequestQueue(Request<T> req){ getRequestQueue().add(req); } public ImageLoader getImageLoader() { return mImageLoader; } }
首先使用 Volley.newRequestQueue获取 RequestQueue对象
RequestQueue mRequestQueue = VolleySingleton.getVolleySingleton(this.getApplicationContext()).getRequestQueue();
RequestQueue是请求队列对象,它可以缓存所有的HTTP网络请求,然后按照其内部算法并发的发送这些网络请求,它能够很好的支撑高并发请求,不要每个请求都创建RequestQueue对象,创建多个RequestQueue会耗费资源
发送StringRequest请求
StringRequest stringRequest = new StringRequest(Request.Method.GET,"https://www.baidu.com",new Listener<String>(){ @Override public void onResponse(String s) { //打印请求返回结果 Log.e("volley",s); } },new ErrorListener(){ @Override public void onErrorResponse(VolleyError volleyError) { Log.e("volleyerror","erro2"); } }); //将StringRequest对象添加进RequestQueue请求队列中 VolleySingleton.getVolleySingleton(this.getApplicationContext()).addToRequestQueue(stringRequest);
到此已经完成了StringRequest请求。StringRequest提供了两个构造方法
public StringRequest(java.lang.String url, com.android.volley.Response.Listener<java.lang.String> listener, com.android.volley.Response.ErrorListener errorListener); public StringRequest(int method, java.lang.String url, com.android.volley.Response.Listener<java.lang.String> listener, com.android.volley.Response.ErrorListener errorListener);
参数method是HTTP的请求类型,通常有GET和POST两种;参数url是请求地址;参数listener是服务器响应成功的回调,参数errorListener是服务器响应失败的回调。如果想通过POST方式请求并携带参数,遗憾的是StringRequest并没有提供带参数请求,但是当发送POST请求时,Volley会调用StringRequest的父类Request的getParams()方法来获取POST参数,所以我们只要使用StringRequest匿名类重写getParams()方法将参数传递进去就可以实现带参数的StringRequest请求。
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> map = new HashMap<String, String>(); map.put("params1", "value1"); map.put("params2", "value2"); return map; } };
发送JsonObjectRequest请求
JsonObjectRequest jr = new JsonObjectRequest(Request.Method.GET,url,null,new Response.Listener<JSONObject>(){ @Override public void onResponse(JSONObject jsonObject) { Log.e("volley",jsonObject.toString()); } },new ErrorListener(){ @Override public void onErrorResponse(VolleyError volleyError) { Log.e("volleyerror","erro"); } }); VolleySingleton.getVolleySingleton(this.getApplicationContext()).addToRequestQueue(jr);
JsonObjectRequest的构造方法参数和StringRequest一致,不在此累赘。
使用ImageRequest加载图片
ImageView mImageView; String url = "http://i.imgur.com/7spzG.png"; mImageView = (ImageView) findViewById(R.id.myImage); ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); } }, 0, 0, Config.RGB_565, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { mImageView.setImageResource(R.drawable.image_load_error); } }); VolleySingleton.getVolleySingleton(this.getApplicationContext()).addToRequestQueue(request);
ImageRequest的构造函数
public ImageRequest(java.lang.String url, com.android.volley.Response.Listener<android.graphics.Bitmap> listener, int maxWidth, int maxHeight, android.graphics.Bitmap.Config decodeConfig, com.android.volley.Response.ErrorListener errorListener) { /* compiled code */ }
参数url是图片地址,参数listener是请求响应成功回调,参数maxWidth是图片最大宽度,参数maxHeight是图片最大高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。参数decodeConfig是图片的颜色属性,其值是Bitmap.Config类的几个常量,参数errorListener是请求响应失败回调
使用 ImageLoader 加载图片
ImageLoader mImageLoader; ImageView mImageView; private static final String IMAGE_URL = "http://developer.android.com/images/training/system-ui.png"; mImageView = (ImageView) findViewById(R.id.regularImageView); mImageLoader = VolleySingleton.getVolleySingleton(this.getApplicationContext()).getImageLoader(); //IMAGE_URL是图片网络地址 //mImageView是ImageView实例 //R.drawable.def_image默认图片id //R.drawable.err_image加载图片错误时的图片 mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView, R.drawable.def_image, R.drawable.err_image));
使用NetworkImageView加载图片
XML布局文件
<com.android.volley.toolbox.NetworkImageView android:id="@+id/networkImageView" android:layout_width="150dp" android:layout_height="170dp" android:layout_centerHorizontal="true" />
代码
ImageLoader mImageLoader; NetworkImageView mNetworkImageView; private static final String IMAGE_URL = "http://developer.android.com/images/training/system-ui.png"; mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView); mImageLoader = VolleySingleton.getVolleySingleton(this.getApplicationContext()).getImageLoader(); mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);
我们可以调用它的setDefaultImageResId()方法、setErrorImageResId()方法和setImageUrl()方法来分别设置加载中显示的图片,加载失败时显示的图片
取消网络请求
Volley还提供了取消网络请求的方法并且可以联动Activity的生命周期,比如在Activity的onStop()方法中调用cance()方法取消网络请求。
public static final String TAG = "MyTag"; StringRequest stringRequest; // Assume this exists. RequestQueue mRequestQueue; // Assume this exists. // Set the tag on the request. stringRequest.setTag(TAG); // Add the request to the RequestQueue. mRequestQueue.add(stringRequest);
Activity的onStop()方法
@Override protected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); } }
-
uni-app解决权限问题,引导用户跳转至设置界面
2021-10-28 10:37:08插件下载地址:App权限判断和提示 - DCloud 插件市场 App平台https://ext.dcloud.net.cn/plugin?id=594 一、使用方法 1.下载插件并存放到项目目录下,比如:js_sdk/wa-permission/permisson.js 2.在页面中引入js... -
拦截器实现用户权限验证
2019-08-29 21:28:46// flag变量用于判断用户是否登录,默认为false boolean flag = false; //获取请求的路径进行判断 String servletPath = request.getServletPath(); // 判断请求是否需要拦截 for (String s : IGNORE_URI) { ... -
Windows安全机制——UAC(用户权限控制)
2020-04-22 09:42:21我反正知道XP就有了,2000不知道,XP上也有UAC,只不过默认没有开启,你要是愿意可以自己开启,只不过太不人性化了,和Linux上的switch user一样不人性化,没次提权必须输入管理员用户的密码。(见下面图) 所以... -
springmvc拦截器实现对用户登录状态的判断
2017-05-29 16:05:48之前对用户登录状态一直使用的是过滤器filter实现的,今天尝试了下使用springmvc的拦截器interceptor来实现对用户登录状态的判断。 1.spring-mvc.xml配置文件中加入下面代码: --> -
判断Android应用是否打开了使用网络权限
2017-02-13 18:35:181. 手机系统层面的权限,即手机系统本身的无线是否打开,4G是否打开,GPS是否打开(Android手机一般在主界面下滑就能看到,位置明显),定位位置是否打开(这个可以在系统设置-》隐私中查看)。 2. 应用本身层面的... -
Linux文件访问权限
2021-05-09 07:08:352.有效用户ID,有效组ID以及附加组ID决定了我们的文件访问权限。3.通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。但是可以在文件模式模式字中设置一个特殊标志,将进程的有效用户ID设置为文件所有者的用户... -
Android 申请权限结果返回处理
2019-05-09 13:41:551、方法一 private static final int PERMISSION_REQUEST_CODE = 1; private String[] mPermissions = {Manifest.permission.CAMERA}; /** * 申请权限结果返回处理 */ @Overr... -
java用户权限管理与权限设置(二)
2018-05-04 19:23:54实现业务系统中的用户权限管理--实现篇 在设计篇中,我们已经为大家阐述了有关权限管理系统的数据库设计,在本篇中,我们将重点放在其实现代码部分。为了让你能够更直接更有效的看到全部动作的代码,我们使用“动作... -
Android开发之 permission动态权限获取
2019-08-19 10:57:20权限查看:戳这里 在文章最后附上DEMO DEMO效果: 一、说明: ...1、支持单个权限、多个权限、单个权限组、多个权限组...5、可设置被拒绝后继续申请,直到用户授权或者永久拒绝 6、支持请求6.0及以上的悬浮窗... -
实现业务系统中的用户权限管理
2018-05-30 13:05:05本文转自:http://www.uml.org.cn/zjjs/201008111.asp#biao设计篇B/S...浏览器是每一台计算机都已具备的,如果不建立一 个完整的权限检测,那么一个“非法用户”很可能就能通过浏览器轻易访问到B/S系统中的所有功能... -
项目之用户登录和访问权限的控制(5)
2020-07-26 22:59:2313. 用户登录-准备工作 在开发注册功能时,在SecurityConfig类中配置以如下代码: @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } 以上代码的作用是关闭... -
Android6.0动态申请权限那些坑--以及避免用户选择不再提示后无法获取权限的问题
2016-10-20 16:54:33Android 6.0 为了保护用户隐私,将一些权限的申请放在了应用运行的时候去申请, 比如以往的开发中,开发人员只需要将需要的权限在清单文件中配置即可,安装后用户可以在设置中的应用信息中看到:XX应用以获取****... -
Shiro的权限、角色、用户管理
2017-03-02 13:20:25Shiro的权限、角色、用户管理 -
android权限处理详解
2021-06-04 04:25:19写在前面对于android 6.0来说,增加了权限的管理,能够更好的保护用户的隐私,当用户需要某权限时,才动态的去申请。用户也可以在应用权限管理里面关闭和打开。为了方便以后使用,这里对权限使用相关做一个简单的... -
iOS Swift 判断应用定位权限
2016-01-26 17:31:20最近做到地图定位的东西,在搜索应用定位权限判断的这一块儿的时候搜到了许多答案,现在把自己的一些理解说一下,有错误请大家指正。 iOS的应用定位权限有种状态 1 .NotDetermined 表示用户还未对该应用的定位权限... -
导出excel,并将数据返回给前端(包含权限判断)
2019-03-14 10:41:00此接口对用户权限进行判断 2.此接口将前端的参数组合拼凑到下一个接口的url中去,用于条件筛选 3.用户有权限的情况下将用户的权限信息保存到redis中去,并将token写到url的参数中,以便对用户下载时权限的验证... -
ASP.NET通用权限管理系统(FrameWork) 1.0.8源码版
2016-02-24 16:32:223.Url地址权限判断,需要做长度对比.如设置defautl.aspx?cmd=122 则会自动匹配 defautl.aspx?cmd=122xxxxxxx 4.修改模块分类,提示请输入权限名称,其内容不可以为空 5.插入在线人员出错,重复值InsertOnlineUser 6.... -
shiro中给某个接口添加权限的两种方法,若没有权限则返回特定值
2020-11-04 11:27:55shiro中给某个接口添加权限的两种方法,若没有权限则返回特定值 1、在ShiroConfig中的ShiroFilterFactoryBean这个bean中添加过滤器,在过滤器中对接口添加访问权限 1> 添加访问权限 Map<String, String> ... -
Spring Boot AOP处理方法的入参和返回值
2021-03-06 19:14:51IOC和AOP是Spring 中很重要的两个模块,这里练习一下如何使用Spring Boot AOP处理方法的入参和返回值。 -
Spring Security 中的四种权限控制方式
2020-06-17 09:21:49Spring Security 中对于权限控制默认已经提供了很多了,但是,一个优秀的框架必须具备良好的扩展性,恰好,Spring Security 的扩展性就非常棒,我们既可以使用 Spring Security 提供的方式做授权,也可以自定义授权... -
SpringSecurity动态加载用户角色权限实现登录及鉴权
2019-11-27 07:39:55很多人觉得Spring Security实现登录验证很难,我最开始学习的时候也这样觉得。...后来我搞懂了:根本不用你...你只需要告诉Spring Security用户信息、角色信息、权限信息、登录页是什么?登陆成功页是什么?或者其他... -
java判断题
2021-02-12 09:58:25判断题1、Java语言有三种技术平台,分别是JavaSE、JavaME、JavaEE(T) 2、Java是一门高级计算机语言。(T) 3、Java语言具体跨平台的特性(T) 4、JDK工具中自带了一个JRE工具(T)5、JDK安装的时候不可以修改安装目录(F)6... -
微信小程序之获取用户位置权限(拒绝后提醒)
2020-02-17 17:08:57微信小程序获取用户当前位置有三个方式: 1. wx.getLocation(多与wx.openLocation一起用) 获取当前的精度、纬度、速度。不需要授权。当type设置为gcj02 返回可用于wx.openLocation的坐标2. wx.chooseLocation ... -
常见的几种循环&&有返回值类型的方法
2020-09-29 23:09:04} 格式的详细介绍: 权限修饰符:public(公共的,访问权限足够大) static(静态) 返回值类型:数据类型(目前基本数据类型:四类八种: ) 举例:求两个数据之后 结果:int类型 方法名:遵循标识符的规则(见... -
SpringBoot(一) 如何实现AOP的权限控制
2019-04-18 20:06:09其中涉及到了人员权限的校验问题。于是想到了用spring AOP的思路去实现,避免每次需要手动去添加代码校验。 Spring AOP是什么,Aspect Oriented Programming, 面向切面编程,是Spring的核心之一。面向切面很明显就是...