接上篇文章:基于Spring Boot 的RESTful权限认证
在实际项目中,用户通常有多个角色,并且不同的角色对资源的访问有不同的权限。
在项目中增加一个UserController并增加获得所有用户信息的rest服务:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Authorization
@RequestMapping(value = "all")
public ResponseEntity findAll(@PageableDefault(value = 10) Pageable pageable) {
Page<UserEntity> users = userService.findAllUser(pageable);
return new ResponseEntity<>(ResultEntity.ok("查询成功", users), HttpStatus.OK);
}
}
但是该接口并非所有用户都可调用,只有类似系统管理员角色的用户才有权限访问。
实现思路:
-
UserEntity.java
中增加一个代表用户权限的属性(用数字表示,数字越大,代表权限越高); -
Authorization
注解增加一个属性,记录访问该rest接口至少需要的权限; - 用户登陆之后缓存token时,同时将用户的权限等级(数字)缓存;
- 在
AuthorizationInterceptor
拦截器中,对比除了校验用户登陆状态之外,还判断当前用户的权限是否不低于(数字是否大于等于)当前访问的rest接口所需的权限(从Authorization
注解中获取)。
实现
1. 增加用户类型
public class UserType {
public static final int DEFAULT = 0; //普通用户
public static final int ADMINISTRATOR = 1; //权限更高的管理员账户
}
@Data
@Entity
@Table(name = "user")
public class UserEntity {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
@Column(name = "user_id", unique = true, nullable = false, length = 32)
private String userId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDate;
private String userName;
private String password;
/**
* UserEntity中增加字段,记录用户的权限,默认为普通用户
*/
private int userType = UserType.DEFAULT;
private boolean dr;
}
2. 注解增加属性
/**
* @author sukaiyi
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {
int level() default UserType.DEFAULT; //此属性表示访问rest方法时,至少需要的用户权限等级
}
3. 在需要更高权限访问的rest方法上
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 该方法需要用户至少有ADMINISTRATOR的权限
*/
@Authorization(level = UserType.ADMINISTRATOR)
@RequestMapping(value = "all")
public ResponseEntity findAll(@PageableDefault(value = 10) Pageable pageable) {
Page<UserEntity> users = userService.findAllUser(pageable);
return new ResponseEntity<>(ResultEntity.ok("查询成功", users), HttpStatus.OK);
}
}
4. 用户登陆之后将用户的权限信息和token一并存入缓存
为了方便,这里直接在TokenEntity中新建了一个UserEntity的属性,这样自然将用户的权限信息包含进去了。(缓存时直接缓存TokenEntity对象)
/**
* @author sukaiyi
*/
@Data
public class TokenEntity {
private UserEntity user;
private String token;
}
5. 改造AuthorizationInterceptor拦截器
/**
* @author sukaiyi
*/
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
@Autowired
private TokenService tokenService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
Authorization authorization = method.getAnnotation(Authorization.class);
if (authorization == null) {
return true;
}
String auth = request.getHeader(Constants.AUTHORIZATION);
TokenEntity tokenEntity = tokenService.getToken(auth);
//将authorization一并传入检查
if (tokenService.checkToken(tokenEntity, authorization)) {
request.setAttribute(Constants.CURRENT_USER_ID, tokenEntity.getUser().getUserId());
return true;
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
}
tokenService
中
@Override
public boolean checkToken(TokenEntity entity, Authorization authorization) {
if (entity == null) {
return false;
}
TokenEntity token = redisTemplate.opsForValue().get(entity.getUser().getUserId());
if (token == null) {
return false;
}
if (StringUtils.isEmpty(token.getToken())) {
return false;
}
if (!token.getToken().equals(entity.getToken())) {
return false;
}
//若需要的权限大于当前用户的权限,则授权失败
if (authorization.level() > token.getUser().getUserType()) {
return false;
}
return true;
}
注:这种多级授权方式,UserType
中数字越大,权限越高。