shiro有几种默认的拦截器,authc,anno,roles,user等 authc就是FormAuthenticationFilter的实例
ShiroFilterFactoryBean的配置:
private Map<String, Filter> filters; <取名,拦截器地址>,可以自定义拦截器放在这
private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// private Map<String, Filter> filters; shiro有一些默认的拦截器 比如auth,它就是FormAuthenticationFilter表单拦截器 <取名,拦截器地址>,可以自定义拦截器放在这
//private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/ajaxLogin", "anon");
filterChainDefinitionMap.put("/focus/userlogin", "anon");
filterChainDefinitionMap.put("/swagger-ui.html#", "anon");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;user:remember me的可以访问-->
filterChainDefinitionMap.put("/fine", "user");
filterChainDefinitionMap.put("/focus/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
一。请求被authc拦截,如果状态未登录,就会被跳到登录页面,登录成功后,会继续原请求页面,除非原请求就是successurl,才去successurl
PathMatchingFilter是开涛讲过得,匹配某url 拦截进行处理的拦截器,里面有匹配url方法,preHandle,onPreHandle等方法。
AccessControlFilter重写了onPreHandle
pathsMatch:该方法用于path与请求路径进行匹配的方法;如果匹配返回true;
onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
isAccessAllowed:请求是否被允许访问,此方法在AccessControlFilter是抽象方法,被AuthenticatingFilter重写
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
分别点进去看,调用父类AuthenticationFilter的方法判断 当前用户是否已认证过
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
并且判断请求url是不是配置的loginurl
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
return pathsMatch(getLoginUrl(), request);
}
回头再看 onPrehandle,如果是一个没认证过的请求,isAccessAlowed肯定是false,执行onAccessDefined方法,开涛也说过,此方法是请求未通过认证时执行的方法,按逻辑推理,请求未认证就跳转到loginurl在这里实现
这个方法在authc里,onAccessDenied(用到这点:子类继承父类,重写了A方法,父类有个B方法,子类对象调用B方法,执行的是子类的A方法)
如果请求是登录请求,发起登录,如果不是就保存原请求,并重定向到登录url ,saveRequestAndRedirectToLogin
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
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;
}
}
看看saveRequestAndRedirectToLogin
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}
saveRequest
protected void saveRequest(ServletRequest request) {
WebUtils.saveRequest(request);
}
webUtils.saveRequest:其实是放到了session里,key是SAVED_REQUEST_KEY
public static void saveRequest(ServletRequest request) {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
HttpServletRequest httpRequest = toHttp(request);
SavedRequest savedRequest = new SavedRequest(httpRequest);
session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
}
那登录成功后怎么跳转到原请求页面的?肯定是从session取出原请求,还是看authc的onAcessDefined方法
如果请求是loginurl并允许,就发起登录
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
}
executeLogin:同样是 subject.login(token)
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
"must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
onLoginSuccess:authc重写此方法
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;
}
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}
找到session的的原请求,发起请求
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
throws IOException {
String successUrl = null;
boolean contextRelative = true;
SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
successUrl = savedRequest.getRequestUrl();
contextRelative = false;
}
if (successUrl == null) {
successUrl = fallbackUrl;
}
if (successUrl == null) {
throw new IllegalStateException("Success URL not available via saved request or via the " +
"successUrlFallback method parameter. One of these must be non-null for " +
"issueSuccessRedirect() to work.");
}
WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
}
从session取出了那个key 并清空
public static SavedRequest getAndClearSavedRequest(ServletRequest request) {
SavedRequest savedRequest = getSavedRequest(request);
if (savedRequest != null) {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.removeAttribute(SAVED_REQUEST_KEY);
}
return savedRequest;
}
到这就大致明白,authc 完成 认证通过后 转发到原请求页面的流程
二。SecurityUtils.getSubject
前面的AuthenticationFilter或其他过滤器也有不少是直接通过这个方法得到subject,然后通过subject.isAuthenticated 包括check权限或角色,
就会想到是不是subject也保存到session里,登录成功一次后,以后的请求是如何判断 用户已认证通过呢?确实是这样,shiro会在浏览器写下coolie JSESSIONID
通过线程副本,
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}