公司自用的管理系统使用了shiro,但是对于这个登录页面跳转、登录的过程逻辑以及登录成功或者失败的跳转页面一直不理解,查看相关文档资料,整理出一些结果并本地调试测试,记录下备以后回顾之用。
对于spring+shiro,很多文档都是:搭建环境,然后配置web.xml,shiro spring配置文件,开发自己的realm、shiro的过滤器链以及spring controller等等内容。实际开发中,也是按照这套路来使用的。但是:对于localhost/xxx/login登录请求页面跳转,怎么感觉是直接请求spring而未通过shiro过滤器?
本地web.xml配置文件部分截图:
shiro配置:
spring mvc作为请求控制器,shiro作为权限过滤器,shiro对所有请求url进行判断,并指定相关的过滤器,例如/xxx/login指定的是authc过滤器(验证,这些页面必须验证后才能访问,也就是我们说的登录后才能访问),过滤器逻辑如果抛出异常表示认证失败,过滤器会转向shiroFilter配置的loginUrl地址,认证成功会转向配置的successUrl地址。而转向过后的逻辑将是由springmvc来控制。
登录时验证:
1 /** 2 * 3 */ 4 package com.autrade.xxx.shiro; 5 6 import javax.servlet.ServletRequest; 7 import javax.servlet.ServletResponse; 8 9 import org.apache.commons.lang.StringUtils; 10 import org.apache.shiro.authc.AuthenticationException; 11 import org.apache.shiro.authc.AuthenticationToken; 12 import org.apache.shiro.authc.IncorrectCredentialsException; 13 import org.apache.shiro.authc.UnknownAccountException; 14 import org.apache.shiro.subject.Subject; 15 import org.apache.shiro.web.util.WebUtils; 16 import org.springframework.stereotype.Service; 17 18 import com.autrade.sptmasterweb.util.FailCacheUtils; 19 20 /** 21 * 表单验证(包含验证码)过滤类 22 23 * 24 */ 25 @Service 26 public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter { 27 28 public static final String DEFAULT_MESSAGE_PARAM = "message"; 29 30 31 @Override 32 protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { 33 AuthenticationToken token = createToken(request, response); 34 if (token == null) { 35 String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " + 36 "must be created in order to execute a login attempt."; 37 throw new IllegalStateException(msg); 38 } 39 try { 40 Long lockTime = FailCacheUtils.isLocked(token.getPrincipal().toString()); 41 if (lockTime>0){ 42 request.setAttribute(DEFAULT_MESSAGE_PARAM, "错误次数过多,请稍后再试!"); 43 return true; 44 } 45 Subject subject = getSubject(request, response); //代表当前前台传过来的用户 46 subject.login(token); 47 return onLoginSuccess(token, subject, request, response); 48 } catch (AuthenticationException e) { 49 return onLoginFailure(token, e, request, response); 50 } 51 } 52 /** 53 * 登录失败调用事件 54 */ 55 @Override 56 protected boolean onLoginFailure(AuthenticationToken token, 57 AuthenticationException e, ServletRequest request, ServletResponse response) { 58 String className = e.getClass().getName(), message = ""; 59 if (IncorrectCredentialsException.class.getName().equals(className) 60 || UnknownAccountException.class.getName().equals(className)){ 61 message = "用户或密码错误, 请重试."; 62 } 63 else if (e.getMessage() != null && StringUtils.indexOf(e.getMessage(), "msg:")!=-1){ 64 message = StringUtils.replace(e.getMessage(), "msg:", ""); 65 } 66 else{ 67 message = "系统出现点问题,请稍后再试!"; 68 e.printStackTrace(); // 输出到控制台 69 } 70 request.setAttribute(getFailureKeyAttribute(), className); 71 request.setAttribute(DEFAULT_MESSAGE_PARAM, message); 72 String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM); 73 FailCacheUtils.addFailCnt(username); 74 return true; 75 } 76 77 @Override 78 protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, 79 ServletResponse response) throws Exception { 80 String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM); 81 FailCacheUtils.remave(username); 82 return super.onLoginSuccess(token, subject, request, response); 83 } 84 85 }
自定义realm中:
认证:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
//token令牌,也就相当于一张表格,你要去验证,你就得填个表,里面写好用户名密码,交给公安局的同志给你验证 UsernamePasswordToken token = (UsernamePasswordToken) authcToken; InternalUserDownEntity userAcctInfoEntity = internalUserService.findInternalUserInfoByUserName(token.getUsername());if (userAcctInfoEntity!=null) {
//用户我们可以前面的令牌也就是我说的表格来取的,我们前台提交给公安同志一个表格,里面有用户密码,
//但需要注意的是,我们在这里并不把表格中的用户密码路数据库中的用户密码进行比较,我们只是根据表格中的用户名把密码查出来,
//然后把数据库的用户密码放在一个SimpleAuthenticationInfo对象中返回即可,这位公安同志不负责验证,只负责验证的材料 InternalUserAuthEntity userAuthEntity = new InternalUserAuthEntity(); userAuthEntity.setUserId(userAcctInfoEntity.getUserId()); ShiroUser shiroUser = new ShiroUser(userAcctInfoEntity.getUserId(), token.getUsername(),token.getUsername(), userAuthEntity); return new SimpleAuthenticationInfo(shiroUser,userAcctInfoEntity.getPassWord(), getName()); } else { return null; } }
以上准备好比对数据,什么时候验证呢?
(1)executeLogin中调用subject.isAuthenticated()时
(2)由于之前的shiro spring配置文件中配置了/login = authc, 配了authc过滤器,shiro会自动调用subject.isAuthenticated()方法完成验证对比。