基于CAS+Pa4j+Spring Boot+Shiro实现单点登录

基于CAS+Pa4j+Spring Boot+Shiro实现单点登录


第一节 依赖

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
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>

<!-- pac4j -->
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-http</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-jwt</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.buji</groupId>
<artifactId>buji-pac4j</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<artifactId>shiro-web</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
</exclusions>
</dependency>

第二节 配置部分

  Pac4jConfig对PAC4J相关部件进行配置

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
@Configuration
public class Pac4jConfig {
/**
* 工程路径
*/
@Value("${cas.serverName}")
private String projectUrl;

/**
* 服务器cas服务路径
*/
@Value("${cas.casServerUrlPrefix}")
private String casServerUrlPrefix;

/**
* cas登陆路径
*/
@Value("${cas.casServerLoginUrl}")
private String casServerLoginUrl;

/**
* 项目cas客户端名
*/
@Value("${cas.client-name}")
private String clientName;

/**
* 登录地址
*/
@Value("${cas.callbackUrl}")
private String callbackUrl;

@Autowired
private UserRepository userRepository;

@Autowired
private Environment environment;

/**
* JWT Token 生成器,对CommonProfile生成然后每次携带token访问
* @return JwtGenerator
*/
@SuppressWarnings("rawtypes")
@Bean
protected JwtGenerator jwtGenerator() {
return new JwtGenerator(new SecretSignatureConfiguration(SecurityConsts.LOGIN_SALT.getValue()),
new SecretEncryptionConfiguration(SecurityConsts.LOGIN_SALT.getValue()));
}

/**
* JwtAuthenticator用于认证由JwtGenerator或其他方法生成的JWT令牌,即JWT校验器
* 即目前设置的ParameterClient-jwtClient进行的校验器,是rest或者前后端分离的核心校验器
* HTTP客户端验证凭证需要一个认证器。它是ProfileService的一个子组件,该组件验证凭证,同时也处理用户的创建、更新和移除操作。
* @return JwtAuthenticator
*/
@Bean
protected JwtAuthenticator jwtAuthenticator() {
JwtAuthenticator jwtAuthenticator = new JwtAuthenticator();
//签名配置:SecretSignatureConfiguration,RSASignatureConfiguration,ECEncryptionConfiguration
jwtAuthenticator.addSignatureConfiguration(new SecretSignatureConfiguration(SecurityConsts.LOGIN_SALT.getValue()));
//加密配置:SecretEncryptionConfiguration,RSAEncryptionConfiguration,ECEncryptionConfiguration
jwtAuthenticator.addEncryptionConfiguration(new SecretEncryptionConfiguration(SecurityConsts.LOGIN_SALT.getValue()));
return jwtAuthenticator;
}

/**
* 请求cas服务端配置
*/
@Bean
public CasConfiguration casConfig(){
final CasConfiguration configuration = new CasConfiguration();
//CAS server登录地址
configuration.setLoginUrl(casServerLoginUrl);
//CAS 版本,默认为 CAS30,我们使用的是 CAS20
configuration.setProtocol(CasProtocol.CAS20);
configuration.setAcceptAnyProxy(true);
configuration.setPrefixUrl(casServerUrlPrefix);
configuration.setLogoutHandler(new ShiroCasLogoutHandler());
return configuration;
}

/**
* 配置多个Client,每个Client对应一种登陆协议,可以在Clients中配置所有协议,默认Client,以及如何区分回调哪个Client
* client支持多种认证机制:OAuth、SAML、CAS、OpenID Connect、HTTP、OpenID、Google APP Engine、Kerberos(SPNEGO)
* Authenticators:LDAP、SQL、JWT、MongoDB、CouchDB、IP address、REST API
* @param casClient
* @return
*/
@Bean
protected Clients clients(CasClient casClient,
CasRestFormClient casRestFormClient,
ParameterClient jwtClient,
FormClient formClient,
IndirectBasicAuthClient ibaClient,
DirectBasicAuthClient dbaClient) {
//可以设置默认client
Clients clients = new Clients();
//支持的client全部设置进去
clients.setClients(casClient,casRestFormClient,jwtClient,formClient,ibaClient,dbaClient);
clients.setCallbackUrl("https://localhost:8443/");
return clients;
}

/**
* cas客户端配置
* 客户端表示身份验证机制(流)。它执行登录过程并返回一个用户概要文件。间接客户端用于UI身份验证,而直接客户端则用于web服务身份验证。
* @param casConfig 配置通过客户端、授权器和匹配器定义安全配置
* @return cas客户端
*/
@Bean
public CasClient casClient(CasConfiguration casConfig){
CasClient casClient = new CasClient(casConfig);
//客户端回调地址
casClient.setCallbackUrl(callbackUrl);
casClient.setName(clientName);
return casClient;
}

/**
* 通过rest接口可以获取tgt,获取service ticket,甚至可以获取casProfile
*
* @return
*/
@Bean
public CasRestFormClient casRestFormClient(CasConfiguration casConfig){
CasRestFormClient casRestFormClient = new CasRestFormClient();
casRestFormClient.setConfiguration(casConfig);
casRestFormClient.setName("rest");
return casRestFormClient;
}

@Bean
public ParameterClient jwtClient(){
// token校验器,可以用HeaderClient更安全
ParameterClient jwtClient = new ParameterClient("token", jwtAuthenticator());
jwtClient.setSupportGetRequest(true);
jwtClient.setSupportPostRequest(true);
jwtClient.setName("jwt");
return jwtClient;
}

@Bean
public FormClient formClient(){
FormClient formClient = new FormClient();
formClient.setLoginUrl("https://localhost:8443/loginForm.html");
formClient.setName("form");
formClient.setAuthenticator(new UserIdPasswordAuthenticator(userRepository,environment));
// formClient.setCallbackUrl("https://localhost:8443/callback?client_name=form");
return formClient;
}

@Bean
public IndirectBasicAuthClient ibaClient(){
IndirectBasicAuthClient ibaClient = new IndirectBasicAuthClient();
ibaClient.setName("iba");
ibaClient.setAuthenticator(new UserIdPasswordAuthenticator(userRepository,environment));
return ibaClient;
}

@Bean
public DirectBasicAuthClient dbaClient(){
DirectBasicAuthClient dbaClient = new DirectBasicAuthClient();
dbaClient.setName("dba");
dbaClient.setAuthenticator(new UserIdPasswordAuthenticator(userRepository,environment));
return dbaClient;
}


/**
* pac4j配置
* 配置自定义的cas客户端和session存储
* 配置通过客户端、授权器和匹配器定义安全配置
* @param clients 客户端s
* @return 配置
*/
@Bean("authConfig")
public Config config(Clients clients) {
Config config = new Config(clients);
// config.setSessionStore(shiroSessionStore);
// config.addAuthorizer();
// config.addMatcher();
return config;
}
}

  配置了6种认证客户端:casClient是cas客户端用于cas认证,casRestFormClient是REST风格的cas认证客户端,jwtClient是JWT认证客户端,formClient是表单认证客户端,ibaClient是间接认证客户端,dbaClient是直接认证客户端。

  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
@Configuration
public class ShiroConfig {
/**
* 项目工程路径
*/
@Value("${cas.serverName}")
private String serverName;
/**
* 项目cas服务路径
*/
@Value("${cas.casServerUrlPrefix}")
private String casServerUrlPrefix;
/**
* 登陆
*/
@Value("${cas.casServerLoginUrl}")
private String casServerLoginUrl;
/**
* 登出
*/
@Value("${cas.casServerLogoutUrl}")
private String casServerLogoutUrl;

@Value("${cas.client-name}")
private String clientName;
@Value("${cas.loginUrl}")
private String loginUrl;
@Value("${cas.logoutUrl}")
private String logoutUrl;
@Value("${cas.casFilterUrlPattern}")
private String casFilterUrlPattern;
@Value("${cookie-path}")
private String cookiePath;
@Autowired
public ShiroCacheManager shiroCacheManager;

@Bean
public Pac4jSubjectFactory subjectFactory() {
return new Pac4jSubjectFactory();
}

/**
* 注册自定义CasRealm
* @return CasRealm
*/
@Bean
public CasRealm casRealm() {
CasRealm realm = new CasRealm();
realm.setName("casRealm");
return realm;
}

/**
* 注册和配置SecurityManager
* 需要注意:
* 1.多realm认证只会抛出AuthenticationException,因此如果要想在外部判断到底是在认证的哪一步发生的错误需要自己定义一些异常类型。
* 2.shiro没有提供根据条件指定realm的功能,如果需要实现这样的功能只能通过继承与重写来实现。
* @return SecurityManager
*/
@Bean("securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// // TODO-多个realms进配置在这里
// ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
// modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
// //Shiro提供了三种策略:AllSuccessFulStrategy, AtLeastOneSuccessFulAtrategy, FirstSuccessFulStrategy,默认使用AtLeastOneSuccessFulAtrategy,通常不需要特别配置
// manager.setAuthenticator(modularRealmAuthenticator);
// List<Realm> realms = new ArrayList<>();
// realms.add(getAdminRealm(userService, roleService, authService, jedisUtils));
// realms.add(getCasRealm(userService, roleService, authService,jedisUtils));
// manager.setRealms(realms);
// 设置自定义Realm
manager.setRealm(casRealm());
// // 指定 SubjectFactory
// manager.setSubjectFactory(subjectFactory());
// // 记住密码管理
// manager.setRememberMeManager(rememberMeManager());
// // session管理
// manager.setSessionManager(sessionManager());
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
// 设置缓存管理器
manager.setCacheManager(shiroCacheManager);
return manager;
}

/**
* 注册shiroFilter,配置相关过滤器
* @return ShiroFilterFactoryBean
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(SecurityManager securityManager,
Config config) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl(casFilterUrlPattern);
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/hello");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//自定义拦截器
Map<String, Filter> filters = new HashMap<>();
//Cas资源认证拦截器
//(或者不论使用什么原理去拦截的HTTP请求)根据客户端和授权配置,通过检查用户已认证和已授权来保护一个URL。
// 如果用户没有被认证,它对直接客户端展示认证,对间接客户端启动登录进程。
SecurityFilter formSecurityFilter = new SecurityFilter();
formSecurityFilter.setConfig(config);
formSecurityFilter.setClients("form");
filters.put("formSecurityFilter", formSecurityFilter);
SecurityFilter basicAuthSecurityFilter = new SecurityFilter();
basicAuthSecurityFilter.setConfig(config);
basicAuthSecurityFilter.setClients("iba");
filters.put("basicAuthSecurityFilter", basicAuthSecurityFilter);
SecurityFilter casSecurityFilter = new SecurityFilter();
casSecurityFilter.setConfig(config);
casSecurityFilter.setClients("cas");
filters.put("casSecurityFilter", casSecurityFilter);
SecurityFilter jwtSecurityFilter = new SecurityFilter();
jwtSecurityFilter.setConfig(config);
jwtSecurityFilter.setClients("jwt");
filters.put("jwtSecurityFilter", jwtSecurityFilter);
SecurityFilter dbaSecurityFilter = new SecurityFilter();
dbaSecurityFilter.setConfig(config);
dbaSecurityFilter.setClients("dba,jwt");
filters.put("dbaSecurityFilter", dbaSecurityFilter);

//Cas认证后回调拦截器
//对间接客户端结束登录进程后的回调
io.buji.pac4j.filter.CallbackFilter callbackFilter = new io.buji.pac4j.filter.CallbackFilter();
callbackFilter.setConfig(config);
callbackFilter.setDefaultUrl("https://localhost:8443/");
callbackFilter.setMultiProfile(true);
filters.put("callbackFilter", callbackFilter);
//Cas注销拦截器
//处理应用程序和/或身份服务器注销
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setConfig(config);
logoutFilter.setCentralLogout(true);
logoutFilter.setLocalLogout(true);
logoutFilter.setDefaultUrl(serverName + "callback?client_name" + clientName);
filters.put("logoutFilter", logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
loadShiroFilterChain(shiroFilterFactoryBean);
return shiroFilterFactoryBean;
}

/**
* 加载shiroFilter权限控制规则(从数据库读取然后配置)
*
* @param shiroFilterFactoryBean
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) {
/*下面这些规则配置最好配置到配置文件中 */
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// authc:该过滤器下的页面必须登录后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
// anon: 可以理解为不拦截
// user: 登录了就不拦截
// roles["admin"] 用户拥有admin角色
// perms["permission1"] 用户拥有permission1权限
// filter顺序按照定义顺序匹配,匹配到就验证,验证完毕结束。
// url匹配通配符支持:? * **,分别表示匹配1个,匹配0-n个(不含子路径),匹配下级所有路径
filterChainDefinitionMap.put("/form/**", "formSecurityFilter");
filterChainDefinitionMap.put("/cas/**", "casSecurityFilter");
filterChainDefinitionMap.put("/iba/**", "basicAuthSecurityFilter");
filterChainDefinitionMap.put("/jwt/**", "noSessionCreation,jwtSecurityFilter");
filterChainDefinitionMap.put("/dba/**", "noSessionCreation,dbaSecurityFilter");
filterChainDefinitionMap.put("/callback", "callbackFilter");
filterChainDefinitionMap.put("/logout", "logoutFilter");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/token", "formSecurityFilter");
filterChainDefinitionMap.put("/**", "noSessionCreation,jwtSecurityFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}


@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* 下面的代码是添加注解支持
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
// https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

  自定义Realm-CasRealm,继承Pac4jRealm,支持Pac4jToken,重写相关方法。

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
@Data
@Service
public class CasRealm extends Pac4jRealm {
private Logger logger = Logger.getLogger(CasRealm.class);
@Autowired
private UserService userService;

public CasRealm() {
super();
}

/**
* 返回一个唯一的Realm名字
*/
@Override
public String getName() {
return super.getName();
}

/**
* 判断此Realm是否支持此Token
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof Pac4jToken;
}

/**
* 认证
* Shiro登录认证(原理:用户提交 用户名和密码 --- shiro 封装令牌 ---- realm 通过用户名将密码查询返回 ---- shiro
* 自动去比较查询出密码和用户输入密码是否一致---- 进行登陆控制 )
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("Shiro开始登录认证");
Pac4jToken pac4jToken = (Pac4jToken) authenticationToken;
List<CommonProfile> profiles = pac4jToken.getProfiles();
Pac4jPrincipal principal = new Pac4jPrincipal(profiles,getPrincipalNameAttribute());
System.out.println("Pac4jPrincipal: " + principal.toString());
CommonProfile profile = principal.getProfile();
System.out.println("CommonProfile: " + profile.toString());
//获取用户信息
String userId = profile.getId();
System.out.println("userId: " + userId);
User user = userService.findByUserId(userId);
// 账号不存在
if(user == null){
return null;
}
// 账号未启用
if (user.getDelFlag()) {
return null;
}
List<Role> roles = userService.findAllRoleByUserId(user.getUserId());
List<Auth> auths = userService.findAllAuthByUserId(user.getUserId());
Set<String> roleSet = new HashSet<>();
Set<String> authSet = new HashSet<>();
for (Role role : roles) {
roleSet.add(role.getCode());
}
for (Auth auth : auths) {
authSet.add(auth.getCode());
}
profile.addAttribute("userId",user.getUserId());
profile.addAttribute("userName",user.getName());
profile.addAttribute("orgId",user.getUnit().getUnitId());
profile.addAttribute("currentTimeMillis",new Timestamp(System.currentTimeMillis()).toString());
profile.setRoles(authSet);
profile.setPermissions(roleSet);
final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal,getName());
return new SimpleAuthenticationInfo(principalCollection, pac4jToken.getCredentials());
}

/**
* 授权/验权(todo 后续有权限在此增加)
* 单点登陆只需关心角色权限数据即可
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("doGetAuthorizationInfo()获取角色权限等信息进行校验");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Pac4jPrincipal principal = (Pac4jPrincipal)principals.getPrimaryPrincipal();
CommonProfile profile = principal.getProfile();
authorizationInfo.setRoles(profile.getRoles());
authorizationInfo.addStringPermissions(profile.getPermissions());
return authorizationInfo;
}
}

  application.properties相关配置

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

cookie-path=/

# 项目工程路径
cas.serverName=https://127.0.0.1:8443/
# 项目cas服务路径
cas.casServerUrlPrefix=https://xxx.xxx.xxx.xxx/authserver/
# 项目cas登陆
cas.casServerLoginUrl=${cas.casServerUrlPrefix}login
# 项目cas登出
cas.casServerLogoutUrl=${cas.casServerUrlPrefix}logout
# 项目cas回调
cas.casServerCallbackUrl=${cas.serverName}callback
# 项目cas客户端名
cas.client-name=cas
# casFilter UrlPattern
cas.casFilterUrlPattern=cas
# 登录地址
cas.loginUrl=${cas.casServerLoginUrl}?service=${cas.serverName}${cas.casFilterUrlPattern}
# 登出地址(casserver启用service跳转功能,需在webapps\cas\WEB-INF\cas.properties文件中启用cas.logout.followServiceRedirects=true)
cas.logoutUrl=${cas.casServerLogoutUrl}?service=${cas.serverName}${cas.casFilterUrlPattern}
# 登录地址
cas.callbackUrl=${cas.serverName}callback?client_name=${cas.client-name}
cas.salt=12345678901234567890123456789012


第三节 测试部分

  MainController

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
@RestController
public class MainController {
@Autowired
private JwtGenerator jwtGenerator;

@RequestMapping("/")
public String index(){
return "https://localhost:8443/form/index </br>" +
" FormClient: Protected url by form authentication 需要(use login = pwd) </br>" +
"</br>" +
"https://localhost:8443/iba/index </br>" +
" IndirectBasicAuthClient: Protected url by indirect basic auth 需要(use login = pwd) </br>"+
"</br>" +
"https://localhost:8443/cas/index </br>" +
" CasClient: Protected url by CAS 需要(use login = pwd) </br>" +
"</br>" +
"https://localhost:8443/token </br>" +
" Generate a JWT token - after being authenticated </br>" +
"</br>" +
"https://localhost:8443/dba/index </br>" +
" DirectBasicAuthClient: Protected url by DirectBasicAuthClient </br>" +
" 需要POST the Authorization header with value: (Basic amxlbGV1OmpsZWxldQ==) </br>" +
" then by (/dba/index) ParameterClient: /dba/index (with request parameter: token=jwt_generated_token </br>" +
"</br>" +
"https://localhost:8443/jwt/index </br>" +
" ParameterClient: Protected url by ParameterClient 需要(with request parameter: token=jwt_generated_token) </br>";
}

@RequestMapping("/token")
public String token(){
Subject subject = SecurityUtils.getSubject();
String token = "null";
final PrincipalCollection collection = subject.getPrincipals();
if (collection != null) {
final Pac4jPrincipal principal = collection.oneByType(Pac4jPrincipal.class);
if (principal != null) {
CommonProfile profile = principal.getProfile();
token = jwtGenerator.generate(profile);
}
}
return token;
}
}

  FormController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class FormController {
@RequestMapping("/form/index")
public String index() {
Subject subject = SecurityUtils.getSubject();
if(subject == null){
return "subject is null";
}else {
System.out.println("isAuthenticated ? " + subject.isAuthenticated());
PrincipalCollection collection = SecurityUtils.getSubject().getPrincipals();
final Pac4jPrincipal principal = collection.oneByType(Pac4jPrincipal.class);
if (principal == null) {
return "user is null";
}else {
return principal.toString();
}
}
}
}

  JwtController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class JwtController {
@RequestMapping("/jwt/index")
public Object index() {
Subject subject = SecurityUtils.getSubject();
if(subject == null){
return "subject is null";
}else {
System.out.println("isAuthenticated ? " + subject.isAuthenticated());
PrincipalCollection collection = SecurityUtils.getSubject().getPrincipals();
final Pac4jPrincipal principal = collection.oneByType(Pac4jPrincipal.class);
if (principal == null) {
return "user is null";
}else {
return principal.toString();
}
}
}
}

  其他类似:/iba/index,/dba/index,/cas/index

  分别访问主页面:https://localhost:8443/

  表单提交账号密码:https://localhost:8443/callback?client_name=form ,携带username&password,表单登录获得JSESSIONID。

  表单客户端访问:https://localhost:8443/form/index ,返回用户信息Pac4jPrincipal。

  获得令牌:https://localhost:8443/token ,返回token。

  JWT客户端访问:https://localhost:8443/jwt/index?client_name=jwt&token=eyJjdH... ,返回用户信息Pac4jPrincipal。

(未完待续…)


参考博客和文章书籍等:

pac4j官网文档

GitHub pac4j/pac4j

GitHub bujiio/buji-pac4j

GitHub pac4j/buji-pac4j-demo

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容