继上一篇 Springboot2 + Shiro + Redis + Jwt 前后端分离整合(2)继续补充
码云项目地址 : Springboot + shiro + redis + jwt + jpa
shiro的权限设置在AuthRealm中doGetAuthorizationInfo设置,具体的设置权限和角色根据自己的业务逻辑设置,我这边只用了权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.debug("开始执行授权操作.......");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//如果身份认证的时候没有传入User对象,这里只能取到userName
//也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
UserEntity user = (UserEntity) principalCollection.getPrimaryPrincipal();
Long userId = user.getId();
//获取权限并设置
List<PermissionEntity> list = permissionRepsitory.findByUserId(userId);
if(!list.isEmpty()){
list.forEach(o ->{
authorizationInfo.addStringPermission(o.getRoleId().toString());
});
}
return authorizationInfo;
}
shiro权限注解,根据自己的需要进行使用,可多个一起使用
(1)@RequiresAuthentication :表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated()返回true
(2)@RequiresUse:表示当前Subject已经身份验证或者通过记住我登录的
(3)@RequiresGuest :表示当前Subject没有身份验证或通过记住我登录过,即是游客身份
(4)@RequiresRoles:需要相应的角色
(5)@RequiresPermissions:需要相应的权限
//删除用户 必须拥有权限2的用户才能使用该操作
@PostMapping(value = "/del")
@RequiresPermissions({
"2"})
public Map<String,String> deleteUser(Long userId){
Map<String,String> result = new HashMap<>();
Optional<UserEntity> o = userRepository.findById(userId);
if(o.isPresent()){
userRepository.deleteById(userId);
RedisUtil.del(ShiroConstant.ROLE_SHIRO_CACHE +userId,ShiroConstant.LOGIN_SHIRO_CACHE + userId);
result.put("code","200");
result.put("msg","用户删除成功");
}else{
result.put("code","400");
result.put("msg","没有这个用户");
}
return result;
}
可以看处用户1拥有删除用户的权限而用户2没有,所以给出的响应不同且权限异常我们可以用@ControllerAdvice来捕获
Shiro的认证其实时一个Filter,在Filter中的异常用@ControllerAdvice是没用的,@ControllerAdvice具体是捕获controller层后的异常无法捕获Filter层的异常,具体我们看下例子吧。
加入GlobalExceptionHandler类用来捕获全局异常
@Slf4j
@ControllerAdvice
@ResponseBody
@Component
public class GlobalExceptionHandler {
/**
* @param ex
* @Description: 捕获CustomException全局异常
*/
@ExceptionHandler(CustomException.class)
public Map<String,String> CustomExceptionHandler(CustomException ex) {
log.error(ex.getMessage(), ex);
Map<String,String> result = new HashMap<>();
result.put("msg",ex.getMessage());
result.put("code","400");
return result;
}
/**
* @param ex
* @Description: 权限认证异常
*/
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
public Map<String,String> unauthorizedExceptionHandler(Exception ex) {
log.error(ex.getMessage(), ex);
Map<String,String> result = new HashMap<>();
result.put("msg",ex.getMessage());
result.put("code","400");
result.put("obj","权限不够");
return result;
}
}
自定义异常CustomException类
public class CustomException extends RuntimeException {
public CustomException() {
}
public CustomException(String message) {
super(message);
}
}
发送请求我们看下结果
很明显抛出了CustomException异常但没有捕获到且输出的内容为**“Typical or expected login exceptions should extend from AuthenticationException”,** CustomException异常属于RuntimeException,被重新生成成AuthenticationException异常了
将CustomException继承RuntimeException类后
并没有输出我们想要的格式,可见@ControlllerAdvice没有捕获到异常。
//输出方法
private void customResponse(String msg,ServletResponse response){
try {
Map<String,Object> map = new HashMap<>();
response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString());
map.put("code",405);
map.put("msg",msg);
String resultJson= JSON.toJSONString(map);
OutputStream out=response.getOutputStream();
out.write(resultJson.getBytes());
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//修改捕获到异常的时候直接输出响应并返回false进入onAccessDenied方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
AuthToken jwtToken = (AuthToken)this.createToken(request,response);
if(jwtToken != null){
String token = jwtToken.getToken();
try {
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
// 如果没有抛出异常则代表登入成功,返回true
getSubject(request, response).login(jwtToken);
}catch (AuthenticationException e){
//获取异常并输出
this.customResponse(e.getMessage(),response);
return false;
}
//判断是否要更新token
String refreshToken = this.refreshToken(token);
if(!StringUtils.isEmpty(refreshToken)){
log.info("更新token时间!!!!!");
UserEntity user = (UserEntity) SecurityUtils.getSubject().getPrincipal();
user.setToken(refreshToken);
RedisUtil.set(ShiroConstant.LOGIN_SHIRO_CACHE + user.getId(),user);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("token", refreshToken);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
}
return true;
}else{
this.customResponse("token不能为空", response);
return false;
}
}
@RestController
public class ErrorController extends BasicErrorController {
/**
* 必须实现的一个构造方法
**/
public ErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
/**
* produces 设置返回的数据类型:application/json
* @param request 请求
* @return 自定义的返回实体类
*/
@Override
@RequestMapping(produces = {
MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
// 获取错误信息
String message = body.get("message").toString();
return new ResponseEntity<>(new ResultMap(400,message), status);
}
}
//该类用于ResponseEntity回参设置
class ResultMap extends HashMap<String, Object> {
/**
* 状态码
*/
private String status;
/**
* 返回内容
*/
private String message;
public ResultMap() {
}
/**
* 初始化一个新创建的 Result 对象
* @param status
* @param message
*/
public ResultMap(Integer status, Object message) {
this.put("status", status);
this.put("message", message);
}
}