forked from rswheeldon/shiro
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ShiroWebModule.java
449 lines (374 loc) · 18.7 KB
/
ShiroWebModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.guice.web;
import java.util.*;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.env.Environment;
import org.apache.shiro.guice.ShiroModule;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.env.WebEnvironment;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
import org.apache.shiro.web.filter.authz.IpFilter;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.filter.authz.PortFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.filter.authz.SslFilter;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.name.Names;
import com.google.inject.servlet.ServletModule;
/**
* Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
* {@link org.apache.shiro.web.mgt.WebSecurityManager}, {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}. At least one realm must be added by
* using {@link #bindRealm() bindRealm}.
* <p/>
* Also provides for the configuring of filter chains and binds a {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information.
*/
public abstract class ShiroWebModule extends ShiroModule {
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<BearerHttpAuthenticationFilter> AUTHC_BEARER = Key.get(BearerHttpAuthenticationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<PortFilter> PORT = Key.get(PortFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<IpFilter> IP = Key.get(IpFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<UserFilter> USER = Key.get(UserFilter.class);
static final String NAME = "SHIRO";
/**
* We use a LinkedHashMap here to ensure that iterator order is the same as add order. This is important, as the
* FilterChainResolver uses iterator order when searching for a matching chain.
*/
private final Map<String, FilterConfig<? extends Filter>[]> filterChains = new LinkedHashMap<String, FilterConfig<? extends Filter>[]>();
private final ServletContext servletContext;
public ShiroWebModule(ServletContext servletContext) {
this.servletContext = servletContext;
}
public static void bindGuiceFilter(Binder binder) {
binder.install(guiceFilterModule());
}
@SuppressWarnings({"UnusedDeclaration"})
public static void bindGuiceFilter(final String pattern, Binder binder) {
binder.install(guiceFilterModule(pattern));
}
public static ServletModule guiceFilterModule() {
return guiceFilterModule("/*");
}
public static ServletModule guiceFilterModule(final String pattern) {
return new ServletModule() {
@Override
protected void configureServlets() {
filter(pattern).through(GuiceShiroFilter.class);
}
};
}
@Override
protected final void configureShiro() {
bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext);
bindWebSecurityManager(bind(WebSecurityManager.class));
bindWebEnvironment(bind(WebEnvironment.class));
bind(GuiceShiroFilter.class).asEagerSingleton();
expose(GuiceShiroFilter.class);
this.configureShiroWeb();
bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs()));
}
private Map<String, Key<? extends Filter>[]> setupFilterChainConfigs() {
// loop through and build a map of Filter Key -> Map<Path, Config>
Map<Key<? extends Filter>, Map<String, String>> filterToPathToConfig = new HashMap<Key<? extends Filter>, Map<String, String>>();
// At the same time build a map to return with Path -> Key[]
Map<String, Key<? extends Filter>[]> resultConfigMap = new LinkedHashMap<String, Key<? extends Filter>[]>();
for (Map.Entry<String, FilterConfig<? extends Filter>[]> filterChain : filterChains.entrySet()) {
String path = filterChain.getKey();
// collect the keys used for this path
List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();
for (int i = 0; i < filterChain.getValue().length; i++) {
FilterConfig<? extends Filter> filterConfig = filterChain.getValue()[i];
Key<? extends Filter> key = filterConfig.getKey();
String config = filterConfig.getConfigValue();
// initialize key in filterToPathToConfig, if it doesn't exist
if (filterToPathToConfig.get(key) == null) {
// Fix for SHIRO-621: REST filter bypassing matched path
filterToPathToConfig.put((key), new LinkedHashMap<String, String>());
}
// now set the value
filterToPathToConfig.get(key).put(path, config);
// Config error if someone configured a non PathMatchingFilter with a config value
if (StringUtils.hasText(config) && !PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
}
// store the key in keysForPath
keysForPath.add(key);
}
// map the current path to all of its Keys
resultConfigMap.put(path, keysForPath.toArray(new Key[keysForPath.size()]));
}
// now we find only the PathMatchingFilter and configure bindings
// non PathMatchingFilter, can be loaded with the default provider via the class name
for (Key<? extends Filter> key : filterToPathToConfig.keySet()) {
if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key));
}
else {
bind(key);
}
}
return resultConfigMap;
}
private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
}
@SuppressWarnings({"unchecked"})
private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) {
return (Key<? extends PathMatchingFilter>) key;
}
protected abstract void configureShiroWeb();
@SuppressWarnings({"unchecked"})
@Override
protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
bind.to(WebSecurityManager.class); // SHIRO-435
}
/**
* Binds the security manager. Override this method in order to provide your own security manager binding.
* <p/>
* By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton.
*
* @param bind
*/
protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
try {
bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
} catch (NoSuchMethodException e) {
throw new ConfigurationException("This really shouldn't happen. Either something has changed in Shiro, or there's a bug in ShiroModule.", e);
}
}
/**
* Binds the session manager. Override this method in order to provide your own session manager binding.
* <p/>
* By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton.
*
* @param bind
*/
@Override
protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
bind.to(ServletContainerSessionManager.class).asEagerSingleton();
}
@Override
protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
bind.to(WebEnvironment.class); // SHIRO-435
}
protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
bind.to(WebGuiceEnvironment.class).asEagerSingleton();
}
protected final void addFilterChain(String pattern, Key<? extends Filter> key) {
// check for legacy API
if (key instanceof FilterConfigKey) {
addLegacyFilterChain(pattern, (FilterConfigKey) key);
}
else {
addFilterChain(pattern, new FilterConfig<Filter>((Key<Filter>) key, ""));
}
}
/**
* Maps 'n' number of <code>filterConfig</code>s to a specific path pattern.<BR/>
* For example, a path of '/my_private_resource/**' to 'filterConfig(AUTHC)' would require
* any resource under the path '/my_private_resource' would be processed through the {@link FormAuthenticationFilter}.
*
* @param pattern URL patter to be mapped to a FilterConfig, e.g. '/my_private-path/**'
* @param filterConfigs FilterConfiguration representing the Filter and config to be used when processing resources on <code>pattern</code>.
* @since 1.4
*/
protected final void addFilterChain(String pattern, FilterConfig<? extends Filter>... filterConfigs) {
filterChains.put(pattern, filterConfigs);
}
/**
* Builds a FilterConfig from a Filer and configuration String
* @param baseKey The Key of the Filter class to be used.
* @param <T> A Servlet Filter class.
* @return A FilterConfig used to map a String path to this configuration.
* @since 1.4
*/
protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey, String configValue) {
return new FilterConfig<T>(baseKey, configValue);
}
/**
* Builds a FilterConfig from a Filer and configuration String
* @param baseKey The Key of the Filter class to be used.
* @param <T> A Servlet Filter class.
* @return A FilterConfig used to map a String path to this configuration.
* @since 1.4
*/
protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey) {
return filterConfig(baseKey, "");
}
/**
* Builds a FilterConfig from a Filer and configuration String
* @param typeLiteral The TyleLiteral of the filter key to be used.
* @param configValue the configuration used.
* @param <T> A Servlet Filter class.
* @return A FilterConfig used to map a String path to this configuration.
* @since 1.4
*/
@SuppressWarnings({"UnusedDeclaration"})
protected static <T extends Filter> FilterConfig<T> filterConfig(TypeLiteral<T> typeLiteral, String configValue) {
return filterConfig(Key.get(typeLiteral), configValue);
}
/**
* Builds a FilterConfig from a Filer and configuration String
* @param type The filter to be used.
* @param configValue the configuration used.
* @param <T> A Servlet Filter class.
* @return A FilterConfig used to map a String path to this configuration.
* @since 1.4
*/
@SuppressWarnings({"UnusedDeclaration"})
protected static <T extends Filter> FilterConfig<T> filterConfig(Class<T> type, String configValue) {
return filterConfig(Key.get(type), configValue);
}
/**
* Filter configuration which pairs a Filter class with its configuration used on a path.
* @param <T> The Servlet Filter class.
* @since 1.4
*/
public static class FilterConfig<T extends Filter> {
private Key<T> key;
private String configValue;
private FilterConfig(Key<T> key, String configValue) {
super();
this.key = key;
this.configValue = configValue;
}
public Key<T> getKey() {
return key;
}
public String getConfigValue() {
return configValue;
}
}
// legacy methods
static boolean isGuiceVersion3() {
try {
Class.forName("com.google.inject.multibindings.MapKey");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
private void addLegacyFilterChain(String pattern, FilterConfigKey filterConfigKey) {
FilterConfig<Filter> filterConfig = new FilterConfig<Filter>(filterConfigKey.getKey(), filterConfigKey.getConfigValue());
addFilterChain(pattern, filterConfig);
}
/**
* Adds a filter chain to the shiro configuration.
* <p/>
* NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter}, it will be registered with a proper
* provider.
*
* @param pattern
* @param keys
*/
@SuppressWarnings({"UnusedDeclaration"})
@Deprecated
protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
// We need to extract the keys and FilterConfigKey and convert to the new format.
FilterConfig[] filterConfigs = new FilterConfig[keys.length];
for (int ii = 0; ii < keys.length; ii++) {
Key<? extends Filter> key = keys[ii];
// If this is a path matching filter, we need to remember the config
if (key instanceof FilterConfigKey) {
// legacy config
FilterConfigKey legacyKey = (FilterConfigKey) key;
filterConfigs[ii] = new FilterConfig(legacyKey.getKey(), legacyKey.getConfigValue());
}
else {
// Some other type of Filter key, no config
filterConfigs[ii] = new FilterConfig(key, "");
}
}
filterChains.put(pattern, filterConfigs);
}
@Deprecated
protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
if( !isGuiceVersion3()) {
throw new ConfigurationException("Method ShiroWebModule.config(Key<? extends PathMatchingFilter>, String configValue), is not supported when using Guice 4+");
}
return new FilterConfigKey<T>(baseKey, configValue);
}
@SuppressWarnings({"UnusedDeclaration"})
@Deprecated
protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
return config(Key.get(typeLiteral), configValue);
}
@SuppressWarnings({"UnusedDeclaration"})
@Deprecated
protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
return config(Key.get(type), configValue);
}
@Deprecated
private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
private Key<T> key;
private String configValue;
private FilterConfigKey(Key<T> key, String configValue) {
super();
this.key = key;
this.configValue = configValue;
}
public Key<T> getKey() {
return key;
}
public String getConfigValue() {
return configValue;
}
}
}