前言:

之前项目使用springmvc开发的cas client,由于以后新项目需要改用springboot开发,所以需要使用springboot来实现cas的单点登录、并完成对自定义需求的实现;之前使用shiro-cas,官方在1.3版本已经标注了过时,根据推荐使用了pac4j-cas来实现;

文章目录

一、集成shiro-spring也pac4j-cas,完成基本的cas单点登录功能;

二、添加单点登出支持,

          默认的shiroSessionStore中getTrackableSession返回直接返回null,导致收到cas服务端发过来的单点登出请求后不会清除对应st创建的session,无法登出;所以自定义CustomShiroSessionStore,实现了默认为null的方法,重启后可以成功登出;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override

public Object getTrackableSession(J2EContext context) {

return getSession(true);

}

@Override

public SessionStore<J2EContext> buildFromTrackableSession(

J2EContext context, Object trackableSession) {

if(trackableSession != null) {

return new ShiroProvidedSessionStore((Session) trackableSession);

}

return null;

}

三、自定义身份验证过滤器CustomCasFilter,重写anon拦截功能,实现某些页面允许未登录访问,但是当cookie中包含tgc时(在其他项目中已经登录),可以自动登录;

根据需求,有一下页面可以允许未登录访问,但是当用户在其他项目中已经登录的后,打开这些页面也需要能够显示用户信息。因此shiro默认的 anon过滤器就无法满足该需求;这边自定义了一个CustomCasFilter,在doFilter中检查cookie中是否包含了tgc,如果已经包含了,就去做登录流程;否则不处理( 这边能获取到tgc是因为已经把cas服务端写cookie的域改成了一级域名,而该项目的域名与cas服务端同属于同一一级域名)

 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
@Override

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {

assertNotNull("securityLogic", securityLogic);

assertNotNull("config", config);

final HttpServletRequest request = (HttpServletRequest) servletRequest;

final HttpServletResponse response = (HttpServletResponse) servletResponse;

final SessionStore<J2EContext> sessionStore = config.getSessionStore();

final J2EContext context = new J2EContext(request, response, sessionStore != null ? sessionStore : ShiroSessionStore.INSTANCE);

if(!SecurityUtils.getSubject().isAuthenticated()){

Collection<Cookie> cookies = context.getRequestCookies();

Optional<Cookie> fid = cookies.stream().filter(cookie -> "fid".equals(cookie.getName())).findFirst(); //我们tgc存储的name就是fid

if(fid.isPresent()&& !StringUtils.isEmpty(fid.get().getValue())) {

//在其他项目中已经登录、跳去登录验证;

securityLogic.perform(context, config, (ctx, profiles, parameters) -> {

filterChain.doFilter(request, response);

return null;

}, J2ENopHttpActionAdapter.INSTANCE, clients, authorizers, matchers, multiProfile);

}

}

// 不登录也能访问的页面

filterChain.doFilter(request, response);

}

四、添加CustomContextThreadLocalFilter,把Pac4jPrincipal身份信息存入threadlocal,避免每次需要先判断subject.getPrincipals();是否为空;

因为每次使用用户信息都需要2次npe判断,所以使用CustomContextThreadLocalFilter过滤器来减少重复代码

 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
@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,

FilterChain filterChain) throws IOException, ServletException {

try {

Subject subject = SecurityUtils.getSubject();

PrincipalCollection pcs = subject.getPrincipals();

if(null !=pcs){

Pac4jPrincipal p = pcs.oneByType(Pac4jPrincipal.class);

ContextHolder.setPac4jPrincipal(p);

}

filterChain.doFilter(servletRequest, servletResponse);

} finally {

ContextHolder.clear();

}

}
 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
package com.spean.shiro_cas.util;

import io.buji.pac4j.subject.Pac4jPrincipal;

/**

public class ContextHolder {

private static final ThreadLocal<Pac4jPrincipal> threadLocal = new ThreadLocal<Pac4jPrincipal>();

public static void setPac4jPrincipal(final Pac4jPrincipal pac4jPrincipal) {

threadLocal.set(pac4jPrincipal);

}

public static Pac4jPrincipal getPac4jPrincipal() {

return threadLocal.get();

}

public static void clear() {

threadLocal.set(null);

}

}

五、添加支持登出后跳转到指定地址

手动登录出后需要指定跳转地址、默认接收的指定参数名为"url",正则校验位 /.*;这边修改正则校验,扩大url的范围

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 注销 拦截器

LogoutFilter logoutFilter = new LogoutFilter();

logoutFilter.setConfig(config);

logoutFilter.setCentralLogout(true);

logoutFilter.setLocalLogout(true);

//添加logout 跳转到指定url url的匹配规则 默认为 /.*;

logoutFilter.setLogoutUrlPattern(".*");

logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);

filters.put("logout",logoutFilter);

 

完整代码:

引入依赖

 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
<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-spring</artifactId>

<version>${shiro.version}</version>

</dependency>

<dependency>

<groupId>io.buji</groupId>

<artifactId>buji-pac4j</artifactId>

<version>${buji.version}</version>

</dependency>

<dependency>

<groupId>org.pac4j</groupId>

<artifactId>pac4j-cas</artifactId>

<version>${pac4j.version}</version>

</dependency>

创建Pac4jConfig

  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
package com.spean.shiro_cas.config.shiro;

import org.pac4j.cas.client.CasClient;

import org.pac4j.cas.config.CasConfiguration;

import org.pac4j.cas.config.CasProtocol;

import org.pac4j.cas.logout.DefaultCasLogoutHandler;

import org.pac4j.core.config.Config;

import org.pac4j.core.context.WebContext;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class Pac4jConfig {

/** 地址为:cas地址 */

@Value("${cas.server.url}")

private String casServerUrl;

/** 地址为:验证返回后的项目地址:http://localhost:8081 */

@Value("${cas.project.url}")

private String projectUrl;

/** 相当于一个标志,可以随意 */

@Value("${cas.client-name}")

private String clientName;

/**

@Bean()

public Config config(CasClient casClient, CustomShiroSessionStore shiroSessionStore) {

Config config = new Config(casClient);

config.setSessionStore(shiroSessionStore);

return config;

}

/**

@Bean

public CustomShiroSessionStore shiroSessionStore(){

return CustomShiroSessionStore.INSTANCE;

}

/**

@Bean

public CasClient casClient(CasConfiguration casConfig){

CasClient casClient = new CustomCasClient(casConfig);

//客户端回调地址

casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);

casClient.setName(clientName);

return casClient;

}

/**

@Bean

public CasConfiguration casConfig(){

final CasConfiguration configuration = new CasConfiguration();

//CAS server登录地址

configuration.setLoginUrl(casServerUrl + "/login");

//CAS 版本,默认为 CAS30,我们使用的是 CAS20

configuration.setProtocol(CasProtocol.CAS20);

configuration.setAcceptAnyProxy(true);

configuration.setPrefixUrl(casServerUrl + "/");

configuration.setLogoutHandler(new DefaultCasLogoutHandler<WebContext>());

return configuration;

}

}

创建ShiroConfig

  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
package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.filter.CallbackFilter;

import io.buji.pac4j.filter.LogoutFilter;

import io.buji.pac4j.filter.SecurityFilter;

import io.buji.pac4j.subject.Pac4jSubjectFactory;

import java.util.HashMap;

import java.util.LinkedHashMap;

import java.util.LinkedList;

import java.util.List;

import java.util.Map;

import java.util.Map.Entry;

import javax.servlet.DispatcherType;

import javax.servlet.Filter;

import org.apache.shiro.session.mgt.SessionManager;

import org.apache.shiro.session.mgt.eis.MemorySessionDAO;

import org.apache.shiro.session.mgt.eis.SessionDAO;

import org.apache.shiro.spring.LifecycleBeanPostProcessor;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.apache.shiro.web.servlet.SimpleCookie;

import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;

import org.pac4j.core.config.Config;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.DependsOn;

import org.springframework.core.Ordered;

import org.springframework.web.filter.DelegatingFilterProxy;

@Configuration

public class ShiroConfig {

/** 项目工程路径 */

@Value("${cas.project.url}")

private String projectUrl;

/** 项目cas服务路径 */

@Value("${cas.server.url}")

private String casServerUrl;

/** 客户端名称 */

@Value("${cas.client-name}")

private String clientName;

@Bean("securityManager")

public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, SessionManager sessionManager, CasRealm casRealm){

DefaultWebSecurityManager manager = new DefaultWebSecurityManager();

manager.setRealm(casRealm);

manager.setSubjectFactory(subjectFactory);

manager.setSessionManager(sessionManager);

return manager;

}

@Bean

public CasRealm casRealm(){

CasRealm realm = new CasRealm();

// 使用自定义的realm

realm.setClientName(clientName);

realm.setCachingEnabled(false);

//暂时不使用缓存

realm.setAuthenticationCachingEnabled(false);

realm.setAuthorizationCachingEnabled(false);

//realm.setAuthenticationCacheName("authenticationCache");

//realm.setAuthorizationCacheName("authorizationCache");

return realm;

}

/**

@Bean

public Pac4jSubjectFactory subjectFactory(){

return new Pac4jSubjectFactory();

}

@Bean

public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean() {

FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<DelegatingFilterProxy>();

filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));

// 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理

filterRegistration.addInitParameter("targetFilterLifecycle", "true");

filterRegistration.setEnabled(true);

filterRegistration.addUrlPatterns("/*");

filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);

return filterRegistration;

}

/**

private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){

/*下面这些规则配置最好配置到配置文件中 */

Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

filterChainDefinitionMap.put("/", "securityFilter");

filterChainDefinitionMap.put("/application/**", "securityFilter");

filterChainDefinitionMap.put("/index", "securityFilter");

filterChainDefinitionMap.put("/hello", "securityFilter");

filterChainDefinitionMap.put("/userInfo", "customCasFilter");

filterChainDefinitionMap.put("/callback", "callbackFilter");

filterChainDefinitionMap.put("/logout", "logout");

filterChainDefinitionMap.put("/**","anon");

// filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

}

/**

@Bean("shiroFilter")

public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) {

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// 必须设 SecurityManager

shiroFilterFactoryBean.setSecurityManager(securityManager);

//shiroFilterFactoryBean.setUnauthorizedUrl("/403");

// 添加casFilter到shiroFilter
loadShiroFilterChain(shiroFilterFactoryBean);

Map<String, Filter> filters = new HashMap<>(4);

//cas 资源认证拦截器

SecurityFilter securityFilter = new SecurityFilter();

securityFilter.setConfig(config);

securityFilter.setClients(clientName);

filters.put("securityFilter", securityFilter);

//cas 自定义资源认证拦截器--允许未登录返回,但是如果在其他项目中已经登录的(cookie中已经包含了tgc)又需要他能够显示用户信息

CustomCasFilter customCasFilter = new CustomCasFilter();

customCasFilter.setConfig(config);

customCasFilter.setClients(clientName);

filters.put("customCasFilter", customCasFilter);

//cas 认证后回调拦截器

CallbackFilter callbackFilter = new CustomCallbackFilter();

callbackFilter.setConfig(config);

callbackFilter.setDefaultUrl(projectUrl);

filters.put("callbackFilter", callbackFilter);

// 注销 拦截器

LogoutFilter logoutFilter = new LogoutFilter();

logoutFilter.setConfig(config);

logoutFilter.setCentralLogout(true);

logoutFilter.setLocalLogout(true);

//添加logout 跳转到指定url url的匹配规则 默认为 /.*;

logoutFilter.setLogoutUrlPattern(".*");

logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);

filters.put("logout",logoutFilter);

shiroFilterFactoryBean.setFilters(filters);

return shiroFilterFactoryBean;

}

@Bean

public SessionDAO sessionDAO(){

return new MemorySessionDAO();

}

/**

@Bean

public SimpleCookie sessionIdCookie(){

SimpleCookie cookie = new SimpleCookie("sid");

cookie.setMaxAge(-1);

cookie.setPath("/");

cookie.setHttpOnly(false);

return cookie;

}

@Bean

public DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO){

DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

sessionManager.setSessionIdCookie(sessionIdCookie);

sessionManager.setSessionIdCookieEnabled(true);

//30分钟

sessionManager.setGlobalSessionTimeout(180000);

sessionManager.setSessionDAO(sessionDAO);

sessionManager.setDeleteInvalidSessions(true);

sessionManager.setSessionValidationSchedulerEnabled(true);

return sessionManager;

}

/**

@Bean

@DependsOn("lifecycleBeanPostProcessor")

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

// 强制使cglib,防止重复代理和可能引起代理出错的问题

// https://zhuanlan.zhihu.com/p/29161098

defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);

return defaultAdvisorAutoProxyCreator;

}

@Bean

public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {

return new LifecycleBeanPostProcessor();

}

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {

AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(securityManager);

return advisor;

}

@Bean

public FilterRegistrationBean<CustomContextThreadLocalFilter> casAssertionThreadLocalFilter(ShiroFilterFactoryBean shiroFilterFactoryBean) {

/**

* 所有经过身份过滤拦截的请求、都需要经过CustomAssertionThreadLocalFilter 这个过滤器、

*/

Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();

List<String> casUrls = new LinkedList<String>();

for (Entry<String, String> entry : filterChainDefinitionMap.entrySet()) {

if("securityFilter".equals(entry.getValue())||"customCasFilter".equals(entry.getValue())){

casUrls.add(entry.getKey());

}

}

final FilterRegistrationBean<CustomContextThreadLocalFilter> assertionTLFilter = new FilterRegistrationBean<CustomContextThreadLocalFilter>();

assertionTLFilter.setFilter(new CustomContextThreadLocalFilter());

assertionTLFilter.setOrder(Ordered.LOWEST_PRECEDENCE);

assertionTLFilter.setUrlPatterns(casUrls);

return assertionTLFilter;

}

}

创建自定义扩展类

  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
package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.context.ShiroSessionStore;

import org.apache.shiro.session.Session;

public class ShiroProvidedSessionStore extends ShiroSessionStore {

/**存储的TrackableSession,往后要操作时用这个session操作*/

private Session session;

public ShiroProvidedSessionStore(Session session) {

this.session = session;

}

@Override

protected Session getSession(final boolean createSession) {

return session;

}

}

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.context.ShiroSessionStore;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.UnavailableSecurityManagerException;

import org.apache.shiro.session.Session;

import org.pac4j.core.context.J2EContext;

import org.pac4j.core.context.session.SessionStore;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class CustomShiroSessionStore implements SessionStore<J2EContext> {

private final static Logger logger = LoggerFactory.getLogger(ShiroSessionStore.class);

public final static CustomShiroSessionStore INSTANCE = new CustomShiroSessionStore();

/**

* Get the Shiro session (do not create it if it does not exist).

*

* @param createSession create a session if requested

* @return the Shiro session

*/

protected Session getSession(final boolean createSession) {

return SecurityUtils.getSubject().getSession(createSession);

}

@Override

public String getOrCreateSessionId(final J2EContext context) {

final Session session = getSession(false);

if (session != null) {

return session.getId().toString();

}

return null;

}

@Override

public Object get(final J2EContext context, final String key) {

final Session session = getSession(false);

if (session != null) {

return session.getAttribute(key);

}

return null;

}

@Override

public void set(final J2EContext context, final String key, final Object value) {

final Session session = getSession(true);

if (session != null) {

try {

session.setAttribute(key, value);

} catch (final UnavailableSecurityManagerException e) {

logger.warn("Should happen just once at startup in some specific case of Shiro Spring configuration", e);

}

}

}

@Override

public boolean destroySession(final J2EContext context) {

getSession(true).stop();

return true;

}

@Override

public Object getTrackableSession(J2EContext context) {

return getSession(true);

}

@Override

public SessionStore<J2EContext> buildFromTrackableSession(

J2EContext context, Object trackableSession) {

if(trackableSession != null) {

return new ShiroProvidedSessionStore((Session) trackableSession);

}

return null;

}

@Override

public boolean renewSession(J2EContext context) {

return false;

}

}

package com.spean.shiro_cas.config.shiro;

import org.pac4j.cas.client.CasClient;

import org.pac4j.cas.config.CasConfiguration;

import org.pac4j.core.context.Pac4jConstants;

import org.pac4j.core.context.WebContext;

import org.pac4j.core.context.session.SessionStore;

import org.pac4j.core.redirect.RedirectAction;

import org.pac4j.core.util.CommonHelper;

public class CustomCasClient extends CasClient {

public CustomCasClient() {

super();

}

public CustomCasClient(CasConfiguration configuration) {

super(configuration);

}

@Override

public RedirectAction getRedirectAction(WebContext context) {

this.init();

if (getAjaxRequestResolver().isAjax(context)) {

this.logger.info("AJAX request detected -> returning the appropriate action");

RedirectAction action = getRedirectActionBuilder().redirect(context);

this.cleanRequestedUrl(context);

return getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context);

} else {

final String attemptedAuth = (String)context.getSessionStore().get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);

if (CommonHelper.isNotBlank(attemptedAuth)) {

this.cleanAttemptedAuthentication(context);

this.cleanRequestedUrl(context);

//这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面

//throw HttpAction.unauthorized(context);

return this.getRedirectActionBuilder().redirect(context);

} else {

return this.getRedirectActionBuilder().redirect(context);

}

}

}

private void cleanRequestedUrl(WebContext context) {

SessionStore<WebContext> sessionStore = context.getSessionStore();

if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != null) {

sessionStore.set(context, Pac4jConstants.REQUESTED_URL, "");

}

}

private void cleanAttemptedAuthentication(WebContext context) {

SessionStore<WebContext> sessionStore = context.getSessionStore();

if (sessionStore.get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != null) {

sessionStore.set(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "");

}

}

}

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.realm.Pac4jRealm;

import io.buji.pac4j.subject.Pac4jPrincipal;

import io.buji.pac4j.token.Pac4jToken;

import java.util.List;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.subject.PrincipalCollection;

import org.apache.shiro.subject.SimplePrincipalCollection;

import org.pac4j.core.profile.CommonProfile;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class CasRealm extends Pac4jRealm {

Logger logger = LoggerFactory.getLogger(CasRealm.class);

private String clientName;

public String getClientName() {

return clientName;

}

public void setClientName(String clientName) {

this.clientName = clientName;

}

/**

* 认证

*

* @param authenticationToken

* @return

* @throws AuthenticationException

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(

AuthenticationToken authenticationToken)

throws AuthenticationException {

final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken;

final List<CommonProfile> commonProfileList = pac4jToken.getProfiles();

final CommonProfile commonProfile = commonProfileList.get(0);

logger.info("单点登录返回的信息" + commonProfile.toString());

final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList,

getPrincipalNameAttribute());

final PrincipalCollection principalCollection = new SimplePrincipalCollection(

principal, getName());

return new SimpleAuthenticationInfo(principalCollection,

commonProfileList.hashCode());

}

/**

* 授权/验权(todo 后续有权限在此增加)

*

* @param principals

* @return

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(

PrincipalCollection principals) {

SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();

authInfo.addStringPermission("user");

return authInfo;

}

}

package com.spean.shiro_cas.config.shiro;

import io.buji.pac4j.filter.CallbackFilter;

import java.io.IOException;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

/**

public class CustomCallbackFilter extends CallbackFilter {

@Override

public void doFilter(ServletRequest servletRequest,

ServletResponse servletResponse, FilterChain filterChain)

throws IOException, ServletException {

super.doFilter(servletRequest, servletResponse, filterChain);

}

}