文章目录
3.6.1. FormAuthenticationFilter
SpringShiroFilter
对象,在getObject方法中会创建,创建的步骤如下:
org.apache.shiro.spring.web.ShiroFilterFactoryBean#applyGlobalPropertiesIfNecessary
方法applyGlobalPropertiesIfNecessary
方法设置全局属性filterChains
的属性中postProcessBeforeInitialization
方法中就是获取到ioc容器中的Filter类型的Bean将其设置到filters属性中
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Filter) {
log.debug("Found filter chain candidate filter '{}'", beanName);
Filter filter = (Filter) bean;
//
applyGlobalPropertiesIfNecessary(filter);
//放入到filters属性中
getFilters().put(beanName, filter);
} else {
log.trace("Ignoring non-Filter bean '{}'", beanName);
}
return bean;
}
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
filterChain.doFilter(request, response);
} else //noinspection deprecation
if (/* added in 1.2: */ !isEnabled(request, response) ||
/* retain backwards compatibility: */ shouldNotFilter(request) ) {
log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.",
getName());
filterChain.doFilter(request, response);
} else {
// Do invoke this filter...
log.trace("Filter '{}' not yet executed. Executing now.", getName());
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//过滤器真正执行的逻辑方法
doFilterInternal(request, response, filterChain);
} finally {
// Once the request has finished, we're done and we don't
// need to mark as 'already filtered' any more.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
doFilterInternal(request, response, filterChain)
,这个方法是执行过滤器的真正逻辑,被子类AdviceFilter
实现preHandle
、postHandle
,这两个方法能在过滤器执行前后做一些事情。public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
//在执行下一个过滤器前执行,如果返回的ttrue表示执行下一个过滤器,否则结束执行
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
if (continueChain) {
//执行下一个过滤器
executeChain(request, response, chain);
}
//后置处理,过滤器执行完毕之后执行
postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception e) {
exception = e;
} finally {
//清除
cleanup(request, response, exception);
}
}
onPreHandle
判断是否继续执行后面的过滤器protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
}
return true;
}
for (String path : this.appliedPaths.keySet()) {
// If the path does match, then pass on to the subclass implementation for specific checks
//(first match 'wins'):
if (pathsMatch(path, request)) {
log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
Object config = this.appliedPaths.get(path);
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through:
return true;
}
protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
:判断当前的访问是否有权限,如果有权限,那么正常继续执行,如果返回false,那么将会执行onAccessDenied
方法protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response)
:isAccessAllowed返回false的时候将会执行,表示当前没有权限访问,需要执行相应的逻辑public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
:在过滤器继续执行之前执行的逻辑,其中默认就是调用了isAccessAllowed判断权限,如下:public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
//逻辑或,如果第一条件为false,将会执行第二个方法,反之不执行
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
isAccessAllowed
:实现了父类的方法,只有用户认证之后才返回trueissueSuccessRedirect
:重定向方法,默认重定向到配置的successUrlpublic static final String DEFAULT_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "password";
/**
* 继承AccessControlFilter抽象类,实现权限控制的访问
*/
@Slf4j
public class LoginFilter extends AccessControlFilter {
private final static String USERNAME="userName";
private final static String PASSWORD="password";
/**
* 登录成功的url
*/
private String successUrl="success";
/**
* 登录失败的url
*/
private String failUrl="fail";
public LoginFilter(String successUrl, String failUrl) {
this.successUrl = successUrl;
this.failUrl = failUrl;
}
/**
* 判断是否认证成功
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = SecurityUtils.getSubject();
return subject.isAuthenticated();
}
/**
* 创建UserNamePasswordToken
*/
private UsernamePasswordToken createToken(ServletRequest request){
String userName = WebUtils.getCleanParam(request, USERNAME);
String password = WebUtils.getCleanParam(request, PASSWORD);
return new UsernamePasswordToken(userName,password);
}
/**
* 没有认证成功执行的方法
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject=SecurityUtils.getSubject();
try {
//执行登录
subject.login(createToken(request));
Map<String,String> params=new HashMap<>();
params.put("token", subject.getSession().getId().toString());
//此处需要将token传入重定向的url,因为重定向的url不是匿名访问的,配置的authc,并且还需要将token返回
//如果走的是匿名的url的话,那么从Subject中获取的Session并不是认证成功的
WebUtils.issueRedirect(request, response, successUrl,params);
}catch (AuthenticationException ex){
log.info("登录失败",ex);
//登录失败,重定向到指定的url
WebUtils.issueRedirect(request, response, failUrl);
}
//不在执行下面的逻辑
return false;
}
}
/**
* 创建登录的过滤器
*/
@Bean
public LoginFilter loginFilter(){
return new LoginFilter("/user/success", "/user/unauthentic");
}
//配置登录的url
filterChainDefinitionMap.put("/login", "loginFilter");
//成功的重定向的请求
/**
* LoginFilter登录成功重定向的请求
* @return
*/
@RequestMapping("success")
public PageResponse success(String token){
return new PageResponse("登录成功","0",token);
}
//失败的重定向
/**
* 没有登录跳转到的uri
* @return
*/
@GetMapping("/unauthentic")
public PageResponse unauthentic(){
return new PageResponse("尚未登录","401");
}
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Filter) {
log.debug("Found filter chain candidate filter '{}'", beanName);
Filter filter = (Filter) bean;
//设置全局属性(如果自定义的过滤器中的三个url没有改变,那么将会启用全局配置的url,否则还是使用自定义设置的url)
applyGlobalPropertiesIfNecessary(filter);
getFilters().put(beanName, filter);
} else {
log.trace("Ignoring non-Filter bean '{}'", beanName);
}
return bean;
}
filterChainDefinitionMap.put("/user/login", "anon,customFilter");
/org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
//获取管理器
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
//获取请求的url
String requestURI = getPathWithinApplication(request);
//循环遍历所有配置在filterChainDefinitionMap中的过滤器的名字
for (String pathPattern : filterChainManager.getChainNames()) {
//url匹配
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " +
"Utilizing corresponding filter chain...");
}
//如果匹配了直接返回过滤器链的代理对象
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}