一、背景
一直以来对shiro的formAuthenticationFilter的工作流程有疑惑,今天终于明白了。这篇文档不是记录formAuthenticationFilter的内部流程,或者说具体的内部代码是怎么工作的。而是记录了将shiro集成到springboot中时,使用formAuthenticationFilter如何拦截、redirect请求。
二、基础配置
shiro的配置
@Bean(name\="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(manager);
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/\*\*", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/sys/logout", "logout");
//如果新增url,在此处增加过滤(但是我不想每新增一个路径就在这里配置一次,所以新增了一个filter,CheckLoginStatusFilter)
filterChainDefinitionMap.put("/user","authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
shiroFilterFactoryBean.setLoginUrl("/sys/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/sys/success");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/unauthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
说明
上面的代码中,设置了loginUrl是/sys/login,设置了successUrl是/sys/success
三、流程
1、用户提交了一个form表单,提交的地址是/sys/login,参数包括username和password。
2、formAuthenticationFilter拦截到了该请求,执行AuthorizingRealm里的登录逻辑。
3、如果登录成功,将会执行redirect到/sys/success。如果登录失败,将会执行redirect到/sys/login。
四、疑惑
1、用户在没有登录的时候,任意提交一个表单,会不会执行登录逻辑。
答案:不会。任意提交的表单,登录地址如果不是/sys/login,首先也会被formAuthenticationFilter拦截到,但是拦截到以后因为校验到没有session,即用户还没有登录,会直接redirect到/sys/login。
2、登录失败后如何处理。
登录失败后,首先会执行formAuthenticationFilter中的onLoginFailure,这个函数是继承下来的,原始的org.apache.shiro.web.filter.authc.FormAuthenticationFilter就有这个函数,我的子类FormAuthenticationFilter是继承了该函数。登录失败后,可以在该函数中根据exception来加入一些中文提示,放到request里,再redirect到/sys/login以后,就可以给出登录错误的原因提示。
五、我的formAuthenticationFilter
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
public static final String DEFAULT\_MESSAGE\_PARAM \= "message";
private String messageParam \= DEFAULT\_MESSAGE\_PARAM;
private static final Logger logger \= LoggerFactory.getLogger(FormAuthenticationFilter.class);
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
logger.info("------------------username: {}, password: {}", username, password);
if (password == null) {
password = "";
}
boolean rememberMe = isRememberMe(request);
String host = StringUtils.getRemoteAddr((HttpServletRequest) request);
return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host);
}
public String getMessageParam() {
return messageParam;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest)request;
return super.onAccessDenied(request, response);
}
/\*\*
\* 登录失败调用事件
\*/
@Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request, ServletResponse response) {
String className = e.getClass().getName();
String message = "";
if (UnknownAccountException.class.getName().equals(className)) {
message = "用户名错误, 请输入正确的用户名!";
} else if (IncorrectCredentialsException.class.getName().equals(className)) {
message = "密码错误, 请输入正确的密码; 如果忘记密码请联系管理员重置您的密码!";
} else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")) {
message = StringUtils.replace(e.getMessage(), "msg:", "");
} else {
message = "系统出现点问题,请稍后再试!";
e.printStackTrace(); // 输出到控制台
}
request.setAttribute(getFailureKeyAttribute(), className);
request.setAttribute(getMessageParam(), message);
return true; }
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
System.out.println("登陆成功");
return super.onLoginSuccess(token, subject, request, response);
}
}
六、浏览器提交数据的方式
1、form提交,可以是post,也可以是get
2、form-data,提交文件
3、json
4、xml
我一直以为form表单提交必须是post提交
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。