1.项目环境
jdk:8
springboot:1.5.10.RELEASE
2.简述处理过程
- 前端请求登录,传入用户名
userName
和密码password
和令牌token
; - 后端进入
jwtFilter
拦截,判断当前请求中是否是登录请求(可以通过是否存在token
,也可以通过判断请求的地址是否是登录地址) - 如果是登录请求,就不执行
shiro
认证和授权,直接进入控制器进行帐号和密码校验,校验成功生成token
返回; - 如果是非登录请求,
jwtFilter
执行executeLogin
方法进入自定义realm
进行认证doGetAuthenticationInfo
(认证不通过,抛出异常,异常的处理稍后详细说明)和授权doGetAuthorizationInfo
,都通过然后进入自己的控制器;
好了,说到这已经看起来很简单了,下面就开启hello world了,简单说明下具体执行细节及处理各种情况应对策略:
3.开始搭建项目
编辑器不能带有代码主题样式难看的要死,作为有洁癖程序员不能忍,只好上传图片以表慰藉,需要源码demo去最后下载就行
- 首先,新建springboot项目,引入如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
简单说明下:shiro-ehcache
是shiro处理缓存,java-jwt
是用来做请求拦截,分发认证和授权;
- jwt配置(网上一大堆,自需取就行了)
token令牌
简单翻下源码:AuthenticationToken
接口的实现默认是UsernamePasswordToken
(这个是在前后端不分离的情况下使用),而getPrincipal()
方法返回的是帐号,getCredentials()
返回的是密码,咱这类比下,getPrincipal()
和getCredentials()
咱们全部用token代替,有了这个咱们就能取到帐号了,这2个方法会在我们自定义的realm中用得到,继续往下走~~,
JWTUtils:用来生成token,校验token,从token中获取登录名,没什么说的
注意
:生成token
和校验token
中的密码必须保持一致(这里采用加密后的值)
继续继续~~~~~下面的重点来了
JWTFilter:既然是filter,那么自然就是拦截请求了
咱们看看执行过程,打断点可以看到,先进的是isAccessAllowed
,有兴趣可以追源码,这就不多说了,在这个方法中有2个操作
- 判断是否是登录请求
isLoginAttempt
这里通过判断请求的请求头中是否存在token
,没有return false,直接进入控制器,校验成功返回token
到前端,前端拿到token
后,下次非登录请求就需要携带token
,这里的R
类就是一个封装的haskMap - 如果有
token
,既为非登录请求,执行第二个方法 - 执行登录认证和授权
executeLogin
这个里边有个getSubject
(request, response).login
(token)方法,这个方法的执行就会到咱们自定的realm中,执行认证和授权 - 自定义realm
- 重写
supports
- 认证:这里有个说明下,由于帐号的校验放到控制层的
login
方法中,这里直接return new SimpleAuthenticationInfo(token, token, getName());
这个方法就不会抛出帐号相关的异常,因为2个参数token
值以同一个; - 授权:说明下,这个方法的执行是在有shiro注解标签(如:
@RequiresPermissions("sys:user:save")
)的前提下才会调用,如果配有缓存,这个方法再调用一次后,授于的角色和权限将存在缓存,下次请求就不会进入这个方法;继续,在拿到权限后,进入控制器,校验注解权限是否合法,完成权限校验 - 拿到
token
后执行其他操作,发送请求
进入授权(如果缓存没有授权,这里会先进入缓存判断),进入授权
授权后进入控制器,完成操作
如果权限不足:
3.存在哪些问题?
- 在认证过程中(执行
realm
中doGetAuthenticationInfo
),发现token
非法,此时异常如何处理?
如果出现token非法(比如密码修改了,但token还是之前密码生成的),此时异常将被JWTFilter
的isAccessAllowed
捕获,并抛出,该方法并没有自动上抛,需要手动抛出,此时这个异常是filter
抛出(跟踪源码到AdviceFilter
的doFilterInternal
方法中boolean continueChain = preHandle(request, response)
抛出),发现@RestControllerAdvice
并不能接收到改异常进行处理,此时需要配合BasicErrorController
进行处理,和下2一致; - 当认证和授权都通过时,发现控制器中的方法执行权限不足,这个异常如何捕获?
通过BasicErrorController
和@RestControllerAdvice
配合进行异常捕获GlobalFilterExceptionHandler
将JWTFilter中的isAccessAllowed方法中抛出的异常进行取出,然后丢给GlobalExceptionHandler
处理即可;
用户密码生成:
4.demo下载
sringboot+jwt+shiro demo