问题引入:
我们通常的系统中仅仅做了Web层访问权限控制,因为大多系统只有Web方式的UI做为人机交互的接口,不提供其它的客户端,所以业务逻辑访问控制一般可以不用控制,数据访问方面由于一般系统的要求目前尚未达到这一权限控制的必要,也没有完整的进行控制。Web层的URL级控制通常采用J2EE规范中的Filter来解决,但由于JSF中的跳转并不会重写URL,默认采用Forward的方式,一般不会采用Redirect的方式,而且采用Redirect的时候,开发时会很不方便,很多Request参数无法传递,所以我们实际上在Filter中获得的URI往往都不是真实的地址。
实际案例:
我们现在有一个画面,假如从菜单中进入画面的URI为“list.jsf”,画面上有“新增”、“查询”两个Button,当点击“查询”这个Button时,查询结果并显示在“list.jsf”这个页面上,当点击“新增”这个Button时,跳转到“add.jsf”这个页面,新增画面中还有一个“返回”Button。当我们点击“新增”时,在Filter中得到的URI为“list.jsf”,实际上我们要跳转到“add.jsf”这个页面,当我们点击“返回”Button时,Filter中得到URI的是“add.jsf”,这与我们实际要跳转到的页面“list.jsf”又不符。如果我们仅仅在Filter中根据URI来判断权限,那么当一个用户不具有新增权限时,就会出现可以操作新增功能的事情,这样便未达到权限控制的目的。
问题解决:
JSF提供了自定义Application的可能,通过研究JSF的实现可以发现JSF控制页面的跳转都是在NavigationHandler中完成的。那么我们来试着实现一个自己的NavigationHandler,解决这一问题。先给出详细的实现:
1
public
class
NavigationHandleWithAuthImpl
extends
NavigationHandler
{
2
3
/**
4
* Field DEBUGLOG.
5
*/
6
private static final Logger DEBUGLOG = Logger.getLogger(Constant.LOG_DEBUG);
7
8
/**
9
* Field ERRORLOG.
10
*/
11
private static final Logger ERRORLOG = Logger.getLogger(Constant.LOG_ERROR);
12
13
/**
14
* Field IGNORED_URI.
15
*/
16
private static final String IGNORED_URI = "/login.faces;/send.faces;"
17
+ "/mainlayout.faces;/backForward.faces";
18
19
/**
20
* Application associate that contains navigation mappings loaded from
21
* configuration file(s).
22
*/
23
private ApplicationAssociate associate = null;
24
25
/**
26
* This constructor uses the current <code>Application</code> instance to
27
* obtain the navigation mappings used to make navigational decisions.
28
*/
29
public NavigationHandleWithAuthImpl() {
30
super();
31
if (DEBUGLOG.isDebugEnabled()) {
32
DEBUGLOG.debug("Created NavigationHandler instance ");
33
}
34
// if the user is using the decorator pattern, this would cause
35
// our ApplicationAssociate to be created, if it isn't already
36
// created.
37
ApplicationFactory aFactory = (ApplicationFactory) FactoryFinder
38
.getFactory(FactoryFinder.APPLICATION_FACTORY);
39
aFactory.getApplication();
40
associate = ApplicationAssociate.getInstance(ConfigureListener
41
.getExternalContextDuringInitialize());
42
}
43
44
/**
45
* 检查URL权限
46
*
47
* @param authList
48
* List<FunctionVo>
49
* @param uri
50
* String
51
* @return boolean
52
*/
53
private boolean checkURL(List<FunctionVo> authList, String uri) {
54
if (authList == null) {
55
return false;
56
}
57
for (FunctionVo vo : authList) {
58
String authUri = vo.getUrl();
59
if (authUri != null && !authUri.equals("")) {
60
int index = authUri.indexOf("?");
61
if (index >= 0) {
62
authUri = authUri.substring(0, index);
63
}
64
}
65
if (uri.equals("/" + authUri)) {
66
return true;
67
}
68
}
69
return false;
70
}
71
72
/**
73
* Determine the next view based on the current view
74
* (<code>from-view-id</code>
75
* stored in <code>FacesContext</code>), <code>fromAction</code> and
76
* <code>outcome</code>.
77
*
78
* @param context
79
* The <code>FacesContext</code>
80
* @param fromAction
81
* the action reference string
82
* @param outcome
83
* the outcome string
84
*/
85
public void handleNavigation(FacesContext context, String fromAction,
86
String outcome) {
87
if (context == null) {
88
String message = Util
89
.getExceptionMessageString(Util.
90
NULL_PARAMETERS_ERROR_MESSAGE_ID);
91
message = message + " context " + context;
92
throw new NullPointerException(message);
93
}
94
if (outcome == null) {
95
if (DEBUGLOG.isDebugEnabled()) {
96
DEBUGLOG.debug("No navigation rule found for outcome "
97
+ outcome + "and viewId "
98
+ context.getViewRoot().getViewId()
99
+ " Explicitly remain on the current view ");
100
}
101
return; // Explicitly remain on the current view
102
}
103
CaseStruct caseStruct = getViewId(context, fromAction, outcome);
104
ExternalContext extContext = context.getExternalContext();
105
if (caseStruct != null) {
106
Object obj = context.getExternalContext().getSessionMap().get(
107
Constant.LOGIN_INFO_KEY);
108
List authList = null;
109
if (obj != null) {
110
authList = ((LoginInfo) obj).getAuthorityFunctionVoList();
111
}
112
String uri = caseStruct.navCase.getToViewId().replace(".jsp",
113
".faces");
114
boolean flag=true;
115
if (this.IGNORED_URI.indexOf(uri) < 0) {
116
if (authList != null && !this.checkURL(authList, uri)) {
117
// URI is invalid
118
flag=false;
119
}
120
}
121
122
ViewHandler viewHandler = Util.getViewHandler(context);
123
Util.doAssert(null != viewHandler);
124
125
if (caseStruct.navCase.hasRedirect()) {
126
// perform a 302 redirect.
127
String newPath = viewHandler.getActionURL(context,
128
caseStruct.viewId);
129
130
try {
131
if (DEBUGLOG.isDebugEnabled()) {
132
DEBUGLOG.debug("Redirecting to path " + newPath
133
+ " for outcome " + outcome + "and viewId "
134
+ caseStruct.viewId);
135
}
136
extContext.redirect(newPath);
137
} catch (java.io.IOException ioe) {
138
String message = "Redirect to " + newPath + " failed.";
139
ERRORLOG.error(message);
140
throw new FacesException(message, ioe);
141
}
142
context.responseComplete();
143
if (DEBUGLOG.isDebugEnabled()) {
144
DEBUGLOG
145
.debug("Response complete for "
146
+ caseStruct.viewId);
147
}
148
} else {
149
UIViewRoot newRoot = null;
150
if (flag) {
151
newRoot = viewHandler
152
.createView(context, caseStruct.viewId);
153
} else {
154
newRoot = viewHandler.createView(context,
155
"/backForward.jsp");
156
}
157
context.setViewRoot(newRoot);
158
if (DEBUGLOG.isDebugEnabled()) {
159
DEBUGLOG.debug("Set new view in FacesContext for "
160
+ caseStruct.viewId);
161
}
162
}
163
}
164
}
165
166
/**
167
* This method uses helper methods to determine the new <code>view</code>
168
* identifier. Refer to section 7.4.2 of the specification for more details.
169
*
170
* @param context
171
* The Faces Context
172
* @param fromAction
173
* The action reference string
174
* @param outcome
175
* The outcome string
176
* @return The <code>view</code> identifier.
177
*/
178
private CaseStruct getViewId(FacesContext context, String fromAction,
179
String outcome) {
180
// String nextViewId = null;
181
String viewId = context.getViewRoot().getViewId();
182
CaseStruct caseStruct = null;
183
184
synchronized (this) {
185
caseStruct = findExactMatch(viewId, fromAction, outcome);
186
187
if (caseStruct == null) {
188
caseStruct = findWildCardMatch(viewId, fromAction, outcome);
189
}
190
191
if (caseStruct == null) {
192
caseStruct = findDefaultMatch(fromAction, outcome);
193
}
194
}
195
return caseStruct;
196
}
197
198
/**
199
* This method finds the List of cases for the current <code>view</code>
200
* identifier. After the cases are found, the <code>from-action</code> and
201
* <code>from-outcome</code> values are evaluated to determine the new
202
* <code>view</code> identifier. Refer to section 7.4.2 of the
203
* specification for more details.
204
*
205
* @param viewId
206
* The current <code>view</code> identifier.
207
* @param fromAction
208
* The action reference string.
209
* @param outcome
210
* The outcome string.
211
* @return The <code>view</code> identifier.
212
*/
213
214
private synchronized CaseStruct findExactMatch(String viewId,
215
String fromAction, String outcome) {
216
// String returnViewId = null;
217
// if the user has elected to replace the Application instance
218
// entirely
219
if (null == associate) {
220
return null;
221
}
222
Map caseListMap = associate.getNavigationCaseListMappings();
223
Util.doAssert(null != caseListMap);
224
List caseList = (List) caseListMap.get(viewId);
225
if (caseList == null) {
226
return null;
227
}
228
// We've found an exact match for the viewId. Now we need to evaluate
229
// from-action/outcome in the following order:
230
// 1) elements specifying both from-action and from-outcome
231
// 2) elements specifying only from-outcome
232
// 3) elements specifying only from-action
233
// 4) elements where both from-action and from-outcome are null
234
return determineViewFromActionOutcome(caseList, fromAction, outcome);
235
}
236
237
/**
238
* This method traverses the wild card match List (containing
239
* <code>from-view-id</code> strings and finds the List of cases for each
240
* <code>from-view-id</code> string. Refer to section 7.4.2 of the
241
* specification for more details.
242
*
243
* @param viewId
244
* The current <code>view</code> identifier.
245
* @param fromAction
246
* The action reference string.
247
* @param outcome
248
* The outcome string.
249
* @return The <code>view</code> identifier.
250
*/
251
private synchronized CaseStruct findWildCardMatch(String viewId,
252
String fromAction, String outcome) {
253
CaseStruct result = null;
254
255
// if the user has elected to replace the Application instance
256
// entirely
257
if (null == associate) {
258
return null;
259
}
260
261
Map caseListMap = associate.getNavigationCaseListMappings();
262
Util.doAssert(null != caseListMap);
263
TreeSet wildcardMatchList = associate.getNavigationWildCardList();
264
Util.doAssert(null != wildcardMatchList);
265
266
Iterator iter = wildcardMatchList.iterator();
267
String fromViewId;
268
List caseList;
269
String wcFromViewId = null;
270
while (iter.hasNext()) {
271
fromViewId = (String) iter.next();
272
// See if the entire wildcard string (without the trailing "*" is
273
// contained in the incoming viewId. Ex: /foobar is contained with
274
// /foobarbaz
275
// If so, then we have found our largest pattern match..
276
// If not, then continue on to the next case;
277
278
if (viewId.indexOf(fromViewId, 0) == -1) {
279
continue;
280
}
281
// Append the trailing "*" so we can do our map lookup;
282
wcFromViewId = fromViewId + "*";
283
caseList = (List) caseListMap.get(wcFromViewId);
284
285
if (caseList == null) {
286
return null;
287
}
288
289
// If we've found a match, then we need to evaluate
290
// from-action/outcome in the following order:
291
// 1) elements specifying both from-action and from-outcome
292
// 2) elements specifying only from-outcome
293
// 3) elements specifying only from-action
294
// 4) elements where both from-action and from-outcome are null
295
296
result = determineViewFromActionOutcome(caseList, fromAction,
297
outcome);
298
if (result != null) {
299
break;
300
}
301
}
302
return result;
303
}
304
305
/**
306
* This method will extract the cases for which a <code>from-view-id</code>
307
* is an asterisk "*". Refer to section 7.4.2 of the specification for more
308
* details.
309
*
310
* @param fromAction
311
* The action reference string.
312
* @param outcome
313
* The outcome string.
314
* @return The <code>view</code> identifier.
315
*/
316
317
private synchronized CaseStruct findDefaultMatch(String fromAction,
318
String outcome) {
319
// String returnViewId = null;
320
// if the user has elected to replace the Application instance
321
// entirely
322
if (null == associate) {
323
return null;
324
}
325
326
Map caseListMap = associate.getNavigationCaseListMappings();
327
Util.doAssert(null != caseListMap);
328
329
List caseList = (List) caseListMap.get("*");
330
331
if (caseList == null) {
332
return null;
333
}
334
335
// We need to evaluate from-action/outcome in the follow
336
// order: 1)elements specifying both from-action and from-outcome
337
// 2) elements specifying only from-outcome
338
// 3) elements specifying only from-action
339
// 4) elements where both from-action and from-outcome are null
340
341
return determineViewFromActionOutcome(caseList, fromAction, outcome);
342
}
343
344
/**
345
* This method will attempt to find the <code>view</code> identifier based
346
* on action reference and outcome. Refer to section 7.4.2 of the
347
* specification for more details.
348
*
349
* @param caseList
350
* The list of navigation cases.
351
* @param fromAction
352
* The action reference string.
353
* @param outcome
354
* The outcome string.
355
* @return The <code>view</code> identifier.
356
*/
357
private synchronized CaseStruct determineViewFromActionOutcome(
358
List caseList, String fromAction, String outcome) {
359
360
String cncFromAction = null;
361
String fromOutcome = null;
362
String toViewId = null;
363
CaseStruct result = new CaseStruct();
364
int size=caseList.size();
365
ConfigNavigationCase cnc = null;
366
for (int i = 0; i < size; i++) {
367
cnc = (ConfigNavigationCase) caseList.get(i);
368
cncFromAction = cnc.getFromAction();
369
fromOutcome = cnc.getFromOutcome();
370
toViewId = cnc.getToViewId();
371
if ((cncFromAction != null) && (fromOutcome != null)) {
372
if ((cncFromAction.equals(fromAction))
373
&& (fromOutcome.equals(outcome))) {
374
result.viewId = toViewId;
375
result.navCase = cnc;
376
return result;
377
}
378
}
379
}
380
for (int i = 0; i < size; i++) {
381
cnc = (ConfigNavigationCase) caseList.get(i);
382
cncFromAction = cnc.getFromAction();
383
fromOutcome = cnc.getFromOutcome();
384
toViewId = cnc.getToViewId();
385
if ((cncFromAction == null) && (fromOutcome != null)) {
386
if (fromOutcome.equals(outcome)) {
387
result.viewId = toViewId;
388
result.navCase = cnc;
389
return result;
390
}
391
}
392
}
393
394
for (int i = 0; i < size; i++) {
395
cnc = (ConfigNavigationCase) caseList.get(i);
396
cncFromAction = cnc.getFromAction();
397
fromOutcome = cnc.getFromOutcome();
398
toViewId = cnc.getToViewId();
399
if ((cncFromAction != null) && (fromOutcome == null)) {
400
if (cncFromAction.equals(fromAction)) {
401
result.viewId = toViewId;
402
result.navCase = cnc;
403
return result;
404
}
405
}
406
}
407
408
for (int i = 0; i < size; i++) {
409
cnc = (ConfigNavigationCase) caseList.get(i);
410
cncFromAction = cnc.getFromAction();
411
fromOutcome = cnc.getFromOutcome();
412
toViewId = cnc.getToViewId();
413
if ((cncFromAction == null) && (fromOutcome == null)) {
414
result.viewId = toViewId;
415
result.navCase = cnc;
416
return result;
417
}
418
}
419
420
return null;
421
}
422
423
/**
424
* @author robin
425
*/
426
class CaseStruct {
427
428
/**
429
* Field viewId.
430
*/
431
protected String viewId;
432
433
/**
434
* Field navCase.
435
*/
436
protected ConfigNavigationCase navCase;
437
}
438
439
}
440

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

来看看其中的关键部分,149行起:











当然别忘了在faces-config.xml中加入自定义Application Navigation的配置,如下:
1
<
faces-config
>
2
<
application
>
3
<
navigation-handler
id
="navigationWithAuth"
>
4
com.***.framework.NavigationHandleWithAuthImpl
5
</
navigation-handler
>
6
</
application
>
7
8
9


10
11
</
faces-config
>

2

3

4

5

6

7

8

9



10

11

注意:
在NavigationHandler中,当发现检查URL权限未能通过时,千万不要直接去修改当前的那个CaseStruts,因为JSF自己会缓存整个跳转的配置,以提高执行效率,请使用viewHandler.createView()来创建一个新CaseStruts,否则会发生跳转不正常的情况。