if (this.appliedPaths == null || this.appliedPaths.isEmpty()) { if (log.isTraceEnabled()) { log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately."); } returntrue; }
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: returntrue; }
/** * Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly. * * @since 1.2 */ @SuppressWarnings({"JavaDoc"}) privatebooleanisFilterChainContinued(ServletRequest request, ServletResponse response, String path, Object pathConfig)throws Exception {
if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2 if (log.isTraceEnabled()) { log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " + "Delegating to subclass implementation for 'onPreHandle' check.", new Object[]{getName(), path, pathConfig}); } //The filter is enabled for this specific request, so delegate to subclass implementations //so they can decide if the request should continue through the chain or not: return onPreHandle(request, response, pathConfig); }
if (log.isTraceEnabled()) { log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " + "The next element in the FilterChain will be called immediately.", new Object[]{getName(), path, pathConfig}); } //This filter is disabled for this specific request, //return 'true' immediately to indicate that the filter will not process the request //and let the request/response to continue through the filter chain: returntrue; }
The default implementation returns true if the user is authenticated. Will also return true if the isLoginRequest returns false and the “permissive” flag is set 如果已经认证则返回true,或者请求不需要认证并且设置了”permissive”,设置”permissive”是什么意思呢:比如说配置过滤器的路由策略时:map.add(“/**”,”authc[permissive]”)
if (token == null) { thrownew IllegalArgumentException("Method argument (authentication token) cannot be null."); }
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info; try { //调用`doAuthenticate(AuthenticationToken)`抽象方法 info = doAuthenticate(token); if (info == null) { String msg = "No account information found for authentication token [" + token + "] by this " + "Authenticator instance. Please check that it is configured correctly."; thrownew AuthenticationException(msg); } } catch (Throwable t) { AuthenticationException ae = null; if (t instanceof AuthenticationException) { ae = (AuthenticationException) t; } if (ae == null) { //Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more //severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate: String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected " + "error? (Typical or expected login exceptions should extend from AuthenticationException)."; ae = new AuthenticationException(msg, t); if (log.isWarnEnabled()) log.warn(msg, t); } try { notifyFailure(token, ae); } catch (Throwable t2) { if (log.isWarnEnabled()) { String msg = "Unable to send notification for failed authentication attempt - listener error?. " + "Please check your AuthenticationListener implementation(s). Logging sending exception " + "and propagating original AuthenticationException instead..."; log.warn(msg, t2); } }
throw ae; }
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token){ if (!realm.supports(token)) { String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type."; thrownew UnsupportedTokenException(msg); } AuthenticationInfo info = realm.getAuthenticationInfo(token); if (info == null) { String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "]."; thrownew UnknownAccountException(msg); } return info; }
AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: info = doGetAuthenticationInfo(token); log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info); if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); } } else { log.debug("Using cached authentication info [{}] to perform credentials matching.", info); }
if (info != null) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); }
publicclassJwtHttpAuthenticationFilterextendsAuthenticatingFilter{ /** * This class's private logger. */ privatestaticfinal Logger log = LoggerFactory.getLogger(JwtHttpAuthenticationFilter.class); /** * HTTP Authorization header, equal to <code>Authorization</code> */ privatestaticfinal String AUTHORIZATION_HEADER = "Authorization"; /** * HTTP Authentication header, equal to <code>WWW-Authenticate</code> */ privatestaticfinal String AUTHENTICATE_HEADER = "WWW-Authenticate"; /** * The authzScheme default value to look for in the <code>Authorization</code> header */ privatestaticfinal String DEFAULT_AUTHORIZATION_SCHEME = "Bearer"; /** * The name that is displayed during the challenge process of authentication, defauls to <code>application</code> * and can be overridden by the {@link #setApplicationName(String) setApplicationName} method. */ private String applicationName = "application"; /** * The authzScheme value to look for in the <code>Authorization</code> header, defaults to <code>Bearer</code> * Can override by {@link #setAuthzScheme(String)} */ private String authzScheme = DEFAULT_AUTHORIZATION_SCHEME; /** * <code>true</code> will enable "OPTION" request method, <code>false</code> otherwise */ privateboolean isCorsEnable = true; /** * the callback handler for successful authentication */ private SuccessfulHandler successfulHandler; /** * the callback handler for unsuccessful authentication */ private UnsuccessfulHandler unsuccessfulHandler; publicJwtHttpAuthenticationFilter(){ unsuccessfulHandler = (token, e, request, response) -> { //defaults to set 401-unauthorized http status HttpServletResponse httpResponse = ((HttpServletResponse) response); String authcHeader = getAuthzScheme() + " realm=\"" + getApplicationName() + "\""; httpResponse.setHeader(AUTHENTICATE_HEADER, authcHeader); httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); }; } publicJwtHttpAuthenticationFilter(String authzScheme, boolean isCorsEnable, SuccessfulHandler successfulHandler, UnsuccessfulHandler unsuccessfulHandler){ this.authzScheme = authzScheme; this.isCorsEnable = isCorsEnable; this.successfulHandler = successfulHandler; this.unsuccessfulHandler = unsuccessfulHandler; } /** * Returns the name that is displayed during the challenge process of authentication * Default value is <code>application</code> * * @return the name that is displayed during the challenge process of authentication */ public String getApplicationName(){ return applicationName; } /** * Sets the name that is displayed during the challenge process of authentication * Default value is <code>application</code> * * @param applicationName the name that is displayed during the challenge process of authentication */ publicvoidsetApplicationName(String applicationName){ this.applicationName = applicationName; } /** * Returns the HTTP <b><code>Authorization</code></b> header value that this filter will respond to as indicating * a login request. * <p/> * Unless overridden by the {@link #setAuthzScheme(String)} method, the * default value is <code>Bearer</code>. * * @return the Http 'Authorization' header value that this filter will respond to as indicating a login request */ public String getAuthzScheme(){ return authzScheme; } /** * Sets the HTTP <b><code>Authorization</code></b> header value that this filter will respond to as indicating a * login request. * <p/> * Unless overridden by this method, the default value is <code>Bearer</code> * * @param authzScheme the HTTP <code>Authorization</code> header value that this filter will respond to as * indicating a login request. */ publicvoidsetAuthzScheme(String authzScheme){ this.authzScheme = authzScheme; } /** * Default value is <code>true</code> * * @param corsEnable <code>true</code> will enable "OPTION" request method, <code>false</code> otherwise */ publicvoidsetCorsEnable(boolean corsEnable){ isCorsEnable = corsEnable; } /** * Default value is <code>true</code> * * @return is cors enable */ publicbooleanisCorsEnable(){ return isCorsEnable; } /** * Returns the callback handler for successful authentication * * @return the callback handler for successful authentication */ public SuccessfulHandler getSuccessfulHandler(){ return successfulHandler; } /** * @param successfulHandler the callback handler for successful authentication */ publicvoidsetSuccessfulHandler(SuccessfulHandler successfulHandler){ this.successfulHandler = successfulHandler; } /** * Returns the callback handler for unsuccessful authentication * * @return the callback handler for unsuccessful authentication */ public UnsuccessfulHandler getUnsuccessfulHandler(){ return unsuccessfulHandler; } /** * @param unsuccessfulHandler the callback handler for successful authentication */ publicvoidsetUnsuccessfulHandler(UnsuccessfulHandler unsuccessfulHandler){ this.unsuccessfulHandler = unsuccessfulHandler; } /** * The Basic authentication filter can be configured with a list of HTTP methods to which it should apply. This * method ensures that authentication is <em>only</em> required for those HTTP methods specified. For example, * if you had the configuration: * <pre> * [urls] * /basic/** = authcJwt[POST,PUT,DELETE] * </pre> * then a GET request would not required authentication but a POST would. * * @param request The current HTTP servlet request. * @param response The current HTTP servlet response. * @param mappedValue The array of configured HTTP methods as strings. This is empty if no methods are configured. */ protectedbooleanisAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue){ HttpServletRequest httpRequest = ((HttpServletRequest) request); String httpMethod = httpRequest.getMethod(); // Check whether the current request's method requires authentication. // If no methods have been configured, then all of them require auth, // otherwise only the declared ones need authentication. Set<String> methods = httpMethodsFromOptions((String[]) mappedValue); boolean authcRequired = methods.size() == 0; // If enable cors and this request_method is equal to "OPTION", do not authentication. // Can override by configuration: // /** = authcJwt[POST,DELETE,OPTION] // then a OPTION request would required authentication. // if (isCorsEnable && httpMethod.equalsIgnoreCase("OPTION")) { // authcRequired = false; // } for (String m : methods) { if (httpMethod.toUpperCase(Locale.ENGLISH).equals(m)) { // list of methods is in upper case authcRequired = true; break; } } // if (isCorsEnable && httpMethod.equalsIgnoreCase("OPTION")) { // responseForCors(request, response); // } if (authcRequired) { returnsuper.isAccessAllowed(request, response, mappedValue); } else { returntrue; } } /** * cors support */ @Override protectedbooleanpreHandle(ServletRequest request, ServletResponse response)throws Exception { if (isCorsEnable() && ((HttpServletRequest) request).getMethod().equals("OPTIONS")) { responseForCors(request, response); returnfalse; } returnsuper.preHandle(request, response); } privatevoidresponseForCors(ServletRequest request, ServletResponse response){ HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Access-Control-Allow-Origin", httpRequest.getHeader("Origin")); httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Allow-Headers")); httpResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setStatus(HttpServletResponse.SC_OK); } private Set<String> httpMethodsFromOptions(String[] options){ Set<String> methods = new HashSet<>(); if (options != null) { for (String option : options) { // to be backwards compatible with 1.3, we can ONLY check for known args // ideally we would just validate HTTP methods, but someone could already be using this for webdav if (!option.equalsIgnoreCase(PERMISSIVE)) { methods.add(option.toUpperCase(Locale.ENGLISH)); } } } return methods; } /** * Processes unauthenticated requests. It handles the two-stage request/challenge authentication protocol. * * @param request incoming ServletRequest * @param response outgoing ServletResponse * @return true if the request should be processed; false if the request should not continue to be processed */ @Override protectedbooleanonAccessDenied(ServletRequest request, ServletResponse response)throws Exception { boolean loggedIn = false; //false by default or we wouldn't be in this method if (isLoginRequest(request, response)) { if (log.isDebugEnabled()) { log.debug("Attempting to execute login with auth header"); } loggedIn = executeLogin(request, response); } return loggedIn; } /** * Returns <code>true</code> if the incoming request have {@link #getAuthzHeader(ServletRequest)} * and the header's value is start with {@link #getAuthzScheme()}, <code>false</code> otherwise. * * @param request the incoming <code>ServletRequest</code> * @param response the outgoing <code>ServletResponse</code> * @return <code>true</code> if the incoming request is required auth, <code>false</code> otherwise. */ @Override protectedbooleanisLoginRequest(ServletRequest request, ServletResponse response){ String authzHeader = getAuthzHeader(request); String scheme = getAuthzScheme().toLowerCase(Locale.ENGLISH); return authzHeader != null && authzHeader.toLowerCase(Locale.ENGLISH).startsWith(scheme); } /** * @param request the incoming <code>ServletRequest</code> * @return the <code>Authorization</code> header's value */ private String getAuthzHeader(ServletRequest request){ return ((HttpServletRequest) request).getHeader(AUTHORIZATION_HEADER); } /** * Returns the authentication token encapsulated by the value of the Authorization header * * @param request the incoming <code>ServletRequest</code> * @param response the outgoing <code>ServletResponse</code> * @return the authentication token encapsulated by the value of the Authorization header */ @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response)throws Exception { String authzHeader = getAuthzHeader(request); if (authzHeader == null || authzHeader.length() == 0) { return JWTToken.NONE; } String scheme = getAuthzScheme(); if (scheme != null && scheme.length() != 0) { authzHeader = authzHeader.substring(scheme.length()); } String token = authzHeader.trim(); String host = request.getRemoteHost(); returnnew JWTToken(token, host); } /** * Callback processing after authentication successful. */ @Override protectedbooleanonLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response)throws Exception { if (successfulHandler != null) { if (log.isDebugEnabled()) { log.debug("{} can pass auth, the auth subject is {}", token, subject); } successfulHandler.onSuccessful(token, subject, request, response); } returntrue; } /** * Callback processing after authentication failure. */ @Override protectedfinalbooleanonLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response){ if (unsuccessfulHandler != null) { if (log.isDebugEnabled()) { log.debug("{} can not pass auth, the auth exception message is {}", token, e.getMessage()); } unsuccessfulHandler.onUnsuccessful(token, e, request, response); } returnfalse; } }
interfaceUnsuccessfulHandler{ /** * Callback processing when auth successful * * @param token the token can not pass authentication * @param e the exception thrown during authentication * @param request the incoming <code>ServletRequest</code> * @param response the outgoing <code>ServletResponse</code> */ voidonUnsuccessful(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response); }
interfaceSuccessfulHandler{ /** * Callback processing when auth unsuccessful * * @param token the token can pass authentication * @param subject the incoming auth <code>Subject</code> * @param request the incoming <code>ServletRequest</code> * @param response the outgoing <code>ServletResponse</code> */ voidonSuccessful(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response); }