shiro配置(jeecms/WebRoot/WEB-INF/config/shiro-context.xml)
1.在地址栏中输入http://*.*.*.*:****/admin/cms/login.do,由上图可看出进入CmsAuthenticationFilter过滤器中,
执行onPreHandle方法,跳转到登录界面(解释如下)
isAccessAllowed 只要当前用户已经过认证就直接返回true,未认证返回false
onAccessDenied方法具体实现内容
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) { //判断是否是登录请求
if (isLoginSubmission(request, response)) { // 是否是http post请求
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else { //如果是get方法则会返回true,跳转到登陆页面
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
} else { //如果访问的是非登录页面,则跳转到登录页面
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]");
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
2.登录页面提交后(post请求),跳转到admin/cms/login.do,进入登录方法,由于此路径权限设置为authc,shiro对该路径进行过滤,authc权限由CmsAuthenticationFilter(extends FormAuthenticationFilter)进行过滤。
和上面执行顺序一样,进入onAccessDenied方法,因是登录请求并且是post请求,进而执行executeLogin(request, response)方法。
protected boolean executeLogin(ServletRequest request,ServletResponse response) throws Exception {
//创建用户的身份、凭证
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "create AuthenticationToken error";
throw new IllegalStateException(msg);
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体
//可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
String username = (String) token.getPrincipal();
boolean adminLogin=false;
if (req.getRequestURI().startsWith(req.getContextPath() + getAdminPrefix())){
adminLogin=true;
}
String failureUrl = req.getParameter(FAILURE_URL);
//验证码校验
if (isCaptchaRequired(username,req, res)) {
String captcha = request.getParameter(CAPTCHA_PARAM);
if (captcha != null) {
if (!imageCaptchaService.validateResponseForID(session.getSessionId(req, res), captcha)) {
return onLoginFailure(token,failureUrl,adminLogin,new CaptchaErrorException(), request, response);
}
} else {
return onLoginFailure(token,failureUrl,adminLogin,new CaptchaRequiredException(),request, response);
}
}
CmsUser user=userMng.findByUsername(username);
if(user!=null){
//用户禁用返回true 未查找到用户或者非禁用返回false
if(isDisabled(user)){
return onLoginFailure(token,failureUrl,adminLogin,new DisabledException(),request, response);
}
//用户激活了返回true 未查找到用户或者非禁用返回false
if(!isActive(user)){
return onLoginFailure(token,failureUrl,adminLogin,new InactiveException(),request, response);
}
}
try {
Subject subject = getSubject(request, response);
subject.login(token); //执行shiro的登录操作
return onLoginSuccess(token,adminLogin,subject, request, response);
} catch (AuthenticationException e) {
//e.printStackTrace();
return onLoginFailure(token,failureUrl,adminLogin, e, request, response);
}
}
调用subject的login方法,login方法实现大致流程是用token去realm中取AuthenticationInfo对象,AuthenticationInfo对象存放的是正确的登录账号和密码,并和token中数据进行匹配,然后根据匹配情况返回相应的结果。realm中方法需自己实现,大致流程:从token中取出用户登录填写的账号,去查找正确的登录信息,若是查找不到,返回null,如果查找到对应的登录账号和密码,则封装到AuthenticationInfo对象中,并返回该对象。
/**
* 登录认证
*/
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
CmsUser user = cmsUserMng.findByUsername(token.getUsername());
if (user != null) {
UnifiedUser unifiedUser = unifiedUserMng.findById(user.getId());
return new SimpleAuthenticationInfo(user.getUsername(), unifiedUser.getPassword(), getName());
} else {
return null;
}
}
/**
* 登录成功
*/
private boolean onLoginSuccess(AuthenticationToken token,boolean adminLogin,Subject subject,
ServletRequest request, ServletResponse response)
throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String username = (String) subject.getPrincipal();
CmsUser user = cmsUserMng.findByUsername(username);
String ip = RequestUtils.getIpAddr(req);
Date now = new Timestamp(System.currentTimeMillis());
String userSessionId=session.getSessionId((HttpServletRequest)request, (HttpServletResponse)response);
userMng.updateLoginInfo(user.getId(), ip,now,userSessionId);
//管理登录
if(adminLogin){
cmsLogMng.loginSuccess(req, user);
}
unifiedUserMng.updateLoginSuccess(user.getId(), ip);
loginCookie(username, req, res);
//调用父类FormAuthenticationFilter中的onLoginSuccess方法
return super.onLoginSuccess(token, subject, request, response);
}
在FormAuthenticationFilter中登录成功具体实现
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
issueSuccessRedirect(request, response); //直接重定向到成功页面
//we handled the success redirect directly, prevent the chain from continuing:
return false; //不再执行过滤器链后面的操作
}
/**
* 登录失败
*/
private boolean onLoginFailure(AuthenticationToken token,String failureUrl,boolean adminLogin,AuthenticationException e, ServletRequest request,
ServletResponse response) {
String username = (String) token.getPrincipal();
HttpServletRequest req = (HttpServletRequest) request;
//管理登录
if(adminLogin){
cmsLogMng.loginFailure(req,"username=" + username);
}
return onLoginFailure(failureUrl,token, e, request, response);
}
//登录失败具体实现
private boolean onLoginFailure(String failureUrl,AuthenticationToken token,
AuthenticationException e, ServletRequest request,
ServletResponse response) {
String className = e.getClass().getName();
request.setAttribute(getFailureKeyAttribute(), className);
//登录错误地址不为空,在跳转到登录错误地址
if(failureUrl!=null||StringUtils.isNotBlank(failureUrl)){
try {
request.getRequestDispatcher(failureUrl).forward(request, response);
} catch (Exception e1) {
//e1.printStackTrace();
}
}
//则继续执行过滤器链后面的操作
return true;
}