最近小编在做新项目的时候采用了前后端分离,使用shiro做权限控制,其中遇到了一些问题。
一 :OPTION请求预检问题
二:未登录未授权跳转问题
解决方案:
一:放行OPTIONS请求
二:继承FormAuthenticationFilter类重写其onAccessDenied方法解决未登录
三:未授权设置全局异常捕获为AuthorizationException
话不多多说直接上代码
package com.lepu.config;
import com.alibaba.fastjson.JSON;
import com.lepu.model.Constant;
import com.lepu.model.ResultBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @author 闫鹏程
* @date 2021-07-05 10:07
* @description
*/
@Slf4j
public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (this.isLoginRequest(request, response)) {
if (this.isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return this.executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
} else {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
//放行OPTIONS预检请求
resp.setStatus(HttpStatus.OK.value());
return true;
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [{}]" ,this.getLoginUrl());
}
/**
* 在这里实现自己想返回的信息,其他地方和源码一样就可以了
*/
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setContentType("application/json; charset=utf-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println(JSON.toJSONString(ResultBean.reponse(Constant.ERROR_401)));
out.flush();
out.close();
return false;
}
}
}
}
shiro配置自定义filter
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);
//3.通用配置(跳转登录页面,未授权跳转的页面)
//filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
//filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
//4.设置过滤器集合
Map<String,String> filterMap = new LinkedHashMap<>();
//anon -- 匿名访问
filterMap.put("/user/login","anon");
/*filterMap.put("/sys/city/**","anon");
filterMap.put("/sys/faceLogin/**","anon");*/
filterMap.put("/autherror","anon");
//注册
//authc -- 认证之后访问(登录)
filterMap.put("/**","authc");
//perms -- 具有某中权限 (使用注解配置授权)
filterFactory.setFilterChainDefinitionMap(filterMap);
Map<String, Filter> filters = filterFactory.getFilters();
//设置自定义filter
filters.put("authc",new ShiroFormAuthenticationFilter());
filterFactory.setFilters(filters);
return filterFactory;
}
全局异常捕获设置
@ExceptionHandler(value = AuthorizationException.class)
@ResponseBody
public ResultBean error(HttpServletRequest request, HttpServletResponse response, AuthorizationException e) {
return ResultBean.reponse(Constant.ERROR_402);
}
登录接口
@RequestMapping(value="/login",method = RequestMethod.POST)
public ResultBean login(@RequestBody Map<String,String> loginMap) {
String userName = loginMap.get("UserName");
String password = loginMap.get("Password");
try {
User user = userService.findByName(userName);
if(user == null || user.getIsDelete() == 1){
return ResultBean.reponse(Constant.SUCCESS_202);
}
if(user.getStatus()==1){
return ResultBean.error("该用户已禁用");
}
//1.构造登录令牌 UsernamePasswordToken
//加密密码
password = new Md5Hash(password, userName, 1).toString(); //1.密码,盐,加密次数
UsernamePasswordToken upToken = new UsernamePasswordToken(userName, password);
//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.获取sessionId
String sessionId = (String) subject.getSession().getId();
redisUtil.incr(userName+ Permanent.LOGINNUM_PREFIX,1);
int loginNum = (int)redisUtil.get(userName+Permanent.LOGINNUM_PREFIX);
if(loginNum == 1){
redisUtil.expire(userName+Permanent.LOGINNUM_PREFIX,5*60);
}
if(loginNum >= 10){
userDao.updateStatus(new User(){{this.setStatus(2);this.setUserName(userName);}});
redisUtil.expire(userName+Permanent.LOGINNUM_PREFIX,30*60);
return ResultBean.error("205","5分钟内密码输入错误10次,账号锁定30分钟");
}
//4.调用login方法,进入realm完成认证
subject.login(upToken);
userDao.updateStatus(new User(){{this.setStatus(0);this.setUserName(userName);}});
redisUtil.del(userName+Permanent.LOGINNUM_PREFIX);
UserPrincipal principal = (UserPrincipal) subject.getPrincipal();
//5.构造返回结果
return ResultBean.ok("登录成功", new HashMap<String,Object>(){{this.put("Authorization",sessionId);this.put("user",principal);}});
}catch (AuthenticationException e){
//密码错误异常
return ResultBean.reponse(Constant.SUCCESS_203);
} catch (Exception e) {
log.error("登录报错",e);
return ResultBean.reponse(Constant.SUCCESS_203);
}
}
如有不明白之处欢迎留言。