Constants
ACCESS_ANON = /\bisAnonymous\s*\(/
ACCESS_AUTH = /\b(?:isAuthenticated|isFullyAuthenticated)\b/
ACCESS_DECISION_TEST = /\.map\(\s*\(?\w+\)?\s*->\s*!?\s*[\w$.]*\b\w+\s*\(\s*\w+\s*\)\s*\)|new\s+AuthorizationDecision\s*\(\s*!?\s*[\w$.]*\b\w+\s*\(/
ACCESS_NEG_ANON = /!\s*[\w$.]*\bisAnonymous\s*\(/
ACCESS_NEG_AUTH = /!\s*[\w$.]*\b(?:isAuthenticated|isFullyAuthenticated)\s*\(/
ACCESS_NOTE = "; a custom authorization manager decides, and the roles or permissions it reads can admit anonymous callers"
ACCESS_TYPE_TEST = /\bisInstance\b|\binstanceof\b/
ADD_CALL = /\b(?:add|addAll|put|putAll)\s*\(/
ADDS_URL = /\b(?:add|addAll|put|putAll)\s*\(/
ANNOTATION_PATTERNS = [/\@PreAuthorize\s*\(/, /\@PostAuthorize\s*\(/, /\@Secured\s*\(/, /\@RolesAllowed\s*\(/]
ANONYMOUS_ROLES =
Set {"
IS_AUTHENTICATED_ANONYMOUSLY", "
ROLE_ANONYMOUS", "
ANONYMOUS"}
ANY_REQUEST_AUTH = /\.\s*any(?:Request|Exchange)\s*\(\s*\)\s*\.\s*(permitAll|anonymous|denyAll|authenticated|fullyAuthenticated|hasRole|hasAnyRole|hasAuthority|hasAnyAuthority|access)\s*(?:\(|\{)/
ATTRIBUTE_EQUALS = /\A[\w$.]+\s*\.\s*equals\s*\(\s*([\w$]+)\s*\.\s*getAttribute\s*\([^()]*\)\s*\)\z/
A request attribute is set on the server side (an earlier filter marking
the request authenticated), never by the caller: unset for a caller
without a credential.
ATTRIBUTE_NULL = /\A([\w$]+)\s*\.\s*getAttribute\s*\([^()]*\)\s*(!=|==)\s*null\z/
AUTHORIZATION_DSL = /\b(?:authorizeHttpRequests|authorizeRequests|authorizeExchange)\b/
BASE_CONFIG_FILES = ["application.yml", "application.yaml", "application.properties"] of ::
StringBOOLEAN_READ_NAME = /\A(?:has|isAnnot)/
BOOTSTRAP_CONFIG_FILE = /\Abootstrap(?:-[^\/]+)?\.(?:ya?ml|properties)\z/
CHAIN_BOUNDARY = /Security(?:Web)?FilterChain\b|configure\s*\(\s*(?:final\s+)?HttpSecurity/
CHAIN_DECLARATION = /\b(?:SecurityFilterChain|SecurityWebFilterChain|WebSecurityConfigurerAdapter)\b|configure\s*\(\s*(?:final\s+)?(?:Server)?HttpSecurity/
CLASS_ANNOTATION_NAMES = ["@PreAuthorize", "@PostAuthorize", "@Secured", "@RolesAllowed", "@RequiresAuthentication", "@RequiresUser", "@RequiresPermissions", "@RequiresRoles", "@RequiresGuest"]
The same authorization annotations, as the bare names they are matched by
when they decorate the controller class instead of a single handler.
CLASS_DECLARATION = /\b(?:class|object)\s+[A-Za-z_$][\w$]*[^{;=]*\{/
CONDITIONAL_REGISTRATION = /@(?:ConditionalOn\w+|Profile)\b(?:\s*\([^()]*\))?/
A bean or filter registered only under a property or profile.
CONFIG_DIRS = {"src/main/resources", "src/main/resources/config"}
CONFIG_FIELD_TYPE = /(?:Properties|Config|Configuration|Settings)\??\z/
A field holding configuration: @Value-injected, or typed *Properties / *Config.
CONFIG_IMPORT_KEYS = ["spring.config.import", "spring.config.location", "spring.config.additional-location"] of ::
StringKeys that pull configuration from elsewhere (another file, a config
server, Nacos) which can override the list.
CREDENTIAL_EXPRESSION = /(?:[\w$]+\s*\.\s*)*(?:#{FILTER_CREDENTIAL_READ.source})/i
A credential read with its receiver (request.getHeader("X-Api-Key")).
DECLARATION_START = /[;}][ \t]*\r?\n/
DO_FILTER_DECLARATION = /(?:\bvoid|\bfun)\s+(?:doFilterInternal|doFilter)\s*\(/
ENABLE_SECURITY = /@Enable(?:Web|WebFlux|RSocket)Security\b/
Spring Security switched on without a chain of the app's own: its
default chain authenticates every request.
FIELD_DECLARATION = /\b([A-Z][\w$]*)(?:<[^<>]*>)?\s+([a-z_$][\w$]*)\s*[;=]/
FILTER_CREDENTIAL_READ = /\bget(?:Header|Parameter)\s*\(\s*(?:HttpHeaders\.AUTHORIZATION\b|"[^"]*(?:auth|token|key|jwt|bearer|session|credential)[^"]*"|[\w$.]*(?:auth|token|key|jwt|session)[\w$]*(?:\s*\(\s*\))?\s*\))|\bgetSession\s*\(|\brequest\.session\b|\b(?:getUserPrincipal|userPrincipal|getRemoteUser|remoteUser|SecurityContextHolder)\b|\bgetCookies\s*\(|\.cookies\b|\b\w*(?:token|auth|jwt|session|credential|principal)\w*\s*\(\s*(?:request|req|httpRequest|httpServletRequest|servletRequest)\b[^()]*\)/i
Reading what the caller presents as a credential. Without one, a filter
that refuses with 403 checks something else (a Referer allowlist, an IP).
FILTER_REGISTRATION_NEW = /\bnew\s+FilterRegistrationBean\b(?:\s*<[^>]*>)?\s*\(|(?<![\w.$])FilterRegistrationBean\s*(?:<[^>()]*>)?\s*\(/
new FilterRegistrationBean<>(..) / Kotlin FilterRegistrationBean(..);
not the type in a declaration (FilterRegistrationBean<X> reg().
FOR_EACH_BINDING = /([\w$.]+\s*\(\s*\)(?:\s*\.\s*[\w$]+\s*\(\s*\))*)\s*\.\s*forEach\s*(\()\s*\(?\s*(?:[\w$]+\s+)?([a-z_$][\w$]*)\s*\)?\s*->/
FORM_LOGIN_DSL = /(?:\.\s*formLogin\s*\(|\bformLogin\s*\(|\bformLogin\s*\{)/
FORM_LOGIN_SETTER = /\b(loginPage|loginProcessingUrl|failureUrl)\s*(?:\(\s*|=\s*)/
HANDLER_PRINCIPAL_PARAM = /(?:\b(?:java\.security\.)?(?:Principal|Authentication)\s+([a-z_$][\w$]*)\s*[,)]|@AuthenticationPrincipal\s+(?:final\s+)?[\w$.<>?]+\s+([a-z_$][\w$]*)\s*[,)]|\b([a-z_][\w]*)\s*:\s*(?:java\.security\.)?(?:Principal|Authentication)\?)/
IDENTIFIER = /[A-Za-z_$][\w$]*/
INDEXED_PROPERTY_NAME = /\[\d+\]\z/
INTERCEPTOR_REJECTION_RE = /(?:setStatus\s*\(\s*(?:40[13]|(?:HttpServletResponse\.)?SC_(?:UNAUTHORIZED|FORBIDDEN)|HttpStatus\.(?:UNAUTHORIZED|FORBIDDEN))\b|sendError\s*\(\s*(?:40[13]|(?:HttpServletResponse\.)?SC_(?:UNAUTHORIZED|FORBIDDEN)|HttpStatus\.(?:UNAUTHORIZED|FORBIDDEN))\b|\bstatus\s*=\s*(?:40[13]|(?:HttpServletResponse\.)?SC_(?:UNAUTHORIZED|FORBIDDEN)|HttpStatus\.(?:UNAUTHORIZED|FORBIDDEN))\b|throw\s+new\s+(?:\w*(?:Auth(?!or)|AccessDenied|Unauthorized|Forbidden|Security)\w*|ResponseStatusException\s*\(\s*HttpStatus\.(?:UNAUTHORIZED|FORBIDDEN))\b)/i
What a preHandle that turns the caller away does: a 401/403 status or
an auth exception. return false alone is not read: a rate limiter or
maintenance switch returns false too.
KOTLIN_AUTHORIZE_RULE = /\bauthorize\s*\(\s*(?:HttpMethod\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*,\s*)?(?:"([^"]+)"|(any(?:Request|Exchange)))\s*,\s*(permitAll|anonymous|denyAll|authenticated|fullyAuthenticated|hasRole|hasAnyRole|hasAuthority|hasAnyAuthority|access)\b/
The Kotlin DSL Spring Security documents for Kotlin apps:
authorizeHttpRequests { authorize("/public/**", permitAll); authorize(anyRequest, authenticated) } (anyExchange in WebFlux).
KOTLIN_FUN_DECLARATION = /(?:^|\s)fun\s/
LEAVES_LOOP = /\b(?:continue|return)\b/
LOOP_HEADER = /\A\s*(?:final\s+)?[\w$.<>\[\]]+\s+([a-z_$][\w$]*)\s*:\s*(.+?)\s*\z/m
MEMBERSHIP_TEST = /(!\s*)?(?:(?:[\w$]+(?:\s*\(\s*\))?\s*\.\s*)*[\w$]+(?:\s*\(\s*\))?\s+(!?in)\s+([A-Za-z_$][\w$]*)\b(?!\s*[.(])|([A-Za-z_$][\w$]*)\s*\.\s*contains\s*\(\s*(?:[\w$]+(?:\s*\(\s*\))?\s*\.\s*)*[\w$]+(?:\s*\(\s*\))?\s*\))/
request.requestURI in OPEN, OPEN.contains(uri) over a field of literals.
MERGED_ANNOTATION_READ = /\b(?:getMethodAnnotation|hasMethodAnnotation|findMergedAnnotation|getMergedAnnotation|findAnnotation|hasAnnotation|isAnnotated)\s*\((?:[^()]*?,\s*)?([A-Z][\w$]*)(?:\.class|::class)/
Lookups that see meta-annotations (@AnonymousGetMapping carrying
@AnonymousAccess), against plain reflection that does not.
METHOD_DECLARATION = /\b([a-zA-Z_$][\w$]*)\s*\(((?:[^()]|\([^()]*\))*)\)\s*(?:throws\s+[\w.,\s]+)?\{/
MURKY_VALUE = /\bnull\b|\?:|\s\?\s|\|\||&&|\b(?:is
Empty|is
Blank|is
NullOrEmpty|is
NullOrBlank|has
Text|has
Length|is
NotEmpty|is
NotBlank|or
Else\w*|get
OrDefault|if
Empty|default\w*|require
NonNullElse\w*|first
NonNull|
Objects\.to
String)\b/
A credential local whose value for a missing credential is not plain:
it tests for null or emptiness itself, or falls back with ?: / a ternary.
NON_AUTH_FILTER_NAME = /Csrf|Xsrf|Origin|Refer|RateLimit|Throttl|Cors|Xss/i
Filters that refuse for another reason than a missing credential.
ponytail: a name list; a filter named for its credential check is not read further.
NON_METHOD_WORDS =
Set {"if", "for", "while", "switch", "catch", "synchronized", "try", "else", "return", "new"}
ORDER_ANNOTATION = /@Order\s*\(\s*(?:value\s*=\s*)?(-?\d+|Ordered\.HIGHEST_PRECEDENCE|Ordered\.LOWEST_PRECEDENCE)\s*\)/
PARAMETER_ANNOTATION_READ = /(?:hasParameterAnnotation|getParameterAnnotation)\s*\(\s*([A-Z][\w$]*)(?:\.class|::class)/
PATH_TEST = /(!\s*)?((?:[\w$]+(?:\s*\(\s*\))?\s*\.\s*)*[\w$]+(?:\s*\(\s*\))?)\s*(?:\.\s*(startsWith|equals)\s*\(\s*"(\/[^"]*)"\s*\)|(==|!=)\s*"(\/[^"]*)")/
path.startsWith("/admin"), request.servletPath == "/login".
PERMIT_ALL_PATTERN = /\bpermitAll\s*\(\s*(?:true\s*)?\)/
PERMIT_MARKER_DESCRIPTION = /\A#{PERMIT_MARKER_PREFIX}(\w+)/
PERMIT_MARKER_PREFIX = "Public by permitAll marker @"
PLAIN_ANNOTATION_READ = /\b(?:isAnnotationPresent|getAnnotation|getDeclaredAnnotation)\s*\((?:[^()]*?,\s*)?([A-Z][\w$]*)(?:\.class|::class)/
POSITIVE_TEST = /\A\s*(?:R\s*!=\s*null|null\s*!=\s*R|R)\s*\z/
PROFILE_CONFIG_FILE = /\Aapplication-[^\/]+\.(?:ya?ml|properties)\z/
PUBLIC_SPEL = /\A(?:permitAll\s*\(\s*\)|anonymous(?:\s*\(\s*\))?|isAnonymous\s*\(\s*\)|hasRole\s*\(\s*['"](?:ROLE_)?ANONYMOUS['"]\s*\)|hasAuthority\s*\(\s*['"](?:ROLE_)?ANONYMOUS['"]\s*\))\z/
A method-security expression that admits an anonymous caller. Anything
else, including a mixed permitAll() or hasRole(...), stays a guard.
RECEIVER_CALL = /([\w$]+)\s*\.\s*(add|addAll|put|putAll|remove|removeAll|removeIf|retainAll|clear)\s*\(/
REDIRECT_LOGIN = /"redirect:[^"]*\blogin\b/i
REFUSAL_STATUS_CONSTANT = /\b(?:HttpServletResponse\.)?SC_(?:UNAUTHORIZED|FORBIDDEN)\b|\bHttpStatus\.(?:UNAUTHORIZED|FORBIDDEN)\b/
A 401/403 status handed to a response helper (writeError(res, SC_UNAUTHORIZED)).
REFUSAL_WINDOW = 600
How much of a method's start is read for its opening refusal.
REFUSING_RETURN = /\b(?:unauthori[sz]ed|forbidden|denied|40[13])\b|\b(?:un_?log_?in|not_?(?:log(?:ged)?_?in|authenticated)|need_?log_?in)\b/i
A refusal written as code: an unauthorized/forbidden factory or status,
a 401/403 literal, an access-denied exception.
REQUEST_HOLDER = /\b(?:RequestContextHolder|RequestAttributes|ServletRequestAttributes|HttpServletRequest|currentRequest)\b/
Reads of the current request that do not name the request parameter.
RULE_AFTER_CALL = /\A\s*\.\s*(permitAll|anonymous|denyAll|authenticated|fullyAuthenticated|hasRole|hasAnyRole|hasAuthority|hasAnyAuthority|access)\s*(?:\(|\{)/
Security config URL rules. access { ... } is a protected rule too;
permitAll() is intentionally tracked so a more-specific public matcher
can suppress a broader protected one. The matcher's argument list is
found by paren matching (it nests antMatcher(X) calls and spans lines),
so only the call opener is a regex.
SECURITY_CONFIGURER_LAMBDA = /\bhttp\s*->\s*http\s*\.\s*(?:authorizeExchange|authorizeHttpRequests|authorizeRequests)\b/
A chain's authorization DSL: a chain that has one yet yields no rule
noir can read is a configuration it does not read.
A SecurityConfigurer bean (http -> http.authorizeExchange(..)): a
chain assembled from configurers, with no chain method of its own.
SERVLET_FILTER_CLASS = /\bclass\s+([A-Za-z_$][\w$]*)[^{;]*?(?:\bextends\s+(?:[\w.]+\.)?(?:OncePerRequestFilter|GenericFilterBean|HttpFilter)\b|\bimplements\s+[^{;]*?\b(?:[\w.]+\.)?Filter\b(?!\s*[<.])|:\s*(?:[^{;]*?,\s*)?(?:[\w.]+\.)?(?:(?:OncePerRequestFilter|GenericFilterBean|HttpFilter)\s*\(|Filter\b(?!\s*[<.(])))/
A servlet filter class header: Java extends OncePerRequestFilter /
implements Filter, Kotlin : OncePerRequestFilter() / : Filter.
SHIRO_CHAIN_ENTRY = /(?:\b([\w$]+)\s*\.\s*put|\.\s*addPathDefinition)\s*\(\s*"(\/[^"\s]*)"\s*,\s*"([^"]+)"\s*\)/
SHIRO_CHAIN_MAP = /setFilterChainDefinitionMap\s*\(\s*([\w$]+)\s*\)/
SHIRO_GUARDING = /\A(?:authc\w*|user|perms|roles|rest)\z/
SHIRO_NON_AUTH = /\A(?:anon|logout|ssl|port|noSessionCreation|invalidRequest)\z/
SHOULD_NOT_FILTER = /(?:\bboolean|\bfun)\s+shouldNotFilter\s*\(/
STATEMENT_CALL = /\A(?:return\s+|throw\s+(?:new\s+)?)?(?:[\w$]+\s*(?:\(\s*\))?\s*\.\s*)*([\w$]+)\s*\(/
A statement that is one call (sendUnauthorized(res), throw ForbiddenException()), and its name.
XML_ACCESS = /\baccess\s*=\s*"([^"]*)"/
XML_HTTP_ELEMENT = /<(?:[\w-]+:)?http[\s>]/
XML_HTTP_TAG = /<(?:[\w-]+:)?http(?:\s[^>]*)?>/
XML_OTHER_AUTHORIZATION = /<(?:[\w-]+:)?custom-filter\b|\b(?:access-decision-manager|authorization-manager)-ref\s*=/
An authorization noir does not read off access attributes.
XML_PUBLIC_ACCESS = /\A(?:permitAll(?:\(\))?|IS_AUTHENTICATED_ANONYMOUSLY|isAnonymous\(\))\z/
XML_SECURITY_NAMESPACE = "springframework.org/schema/security"
An XML <http> element in the Spring Security namespace.
XML_SECURITY_NONE = /\bsecurity\s*=\s*"none"/