key = ConfigurationKeys.CAS_SERVER_URL_PREFIX;
188 | Method setter = singleSignOutFilter.getClass()
189 | .getDeclaredMethod("set" + StringUtils.capitalize(key.getName()));
190 | setter.invoke(singleSignOutFilter, casSecurityProperties.getServer().getBaseUrl().toASCIIString());
191 | } catch (NoSuchMethodException | SecurityException e) {
192 | // since commit :
193 | // https://github.com/apereo/java-cas-client/commit/fdc948b8ec697be0ae04da2f91c66c6526d463b5#diff-676b9d196aacd4b54bc978c62ccbacd8d29552fcd694061355bd930405560fb5
194 | // setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX)); does NOT exists anymore
195 | logger.info(
196 | "Since apereo CAS client 3.6.0 setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX)); does NOT exists anymore");
197 | }
198 | singleSignOutFilterConfigurer.configure(singleSignOutFilter);
199 |
200 | http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
201 | .and()
202 | .addFilterBefore(singleSignOutFilter, CsrfFilter.class)
203 | .addFilter(filter);
204 | }
205 |
206 | @Override
207 | public void configure(HttpSecurity http) throws Exception {
208 | for (CasSecurityConfigurer configurer : configurers) {
209 | configurer.configure(http);
210 | }
211 | }
212 |
213 | void configure(AuthenticationManagerBuilder auth) throws Exception {
214 | CasAuthenticationProvider provider = providerBuilder.build();
215 | provider.setServiceProperties(serviceProperties);
216 | Field field = ReflectionUtils.findField(CasAuthenticationProvider.class, "ticketValidator");
217 | ReflectionUtils.makeAccessible(field);
218 | if (ReflectionUtils.getField(field, provider) == null) {
219 | provider.setTicketValidator(ticketValidator);
220 | }
221 | provider.afterPropertiesSet();
222 | auth.authenticationProvider(provider);
223 | }
224 |
225 | AuthenticationManager authenticationManager() throws Exception {
226 | if (!authenticationManagerInitialized) {
227 | configure(authenticationManagerBuilder);
228 | for (CasSecurityConfigurer configurer : configurers) {
229 | configurer.configure(authenticationManagerBuilder);
230 | }
231 | authenticationManager = authenticationManagerBuilder.build();
232 | authenticationManagerInitialized = true;
233 | }
234 | return authenticationManager;
235 | }
236 |
237 | void setAuthenticationManager(@NonNull AuthenticationManager authenticationManager) {
238 | authenticationManagerInitialized = true;
239 | this.authenticationManager = authenticationManager;
240 | }
241 |
242 | private RequestMatcher getAuthenticationRequestMatcher() {
243 | return new AntPathRequestMatcher(casSecurityProperties.getService().getPaths().getLogin());
244 | }
245 | }
246 |
247 | }
248 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSecurityCondition.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5 |
6 | /**
7 | * @author Thibaud Leprêtre
8 | */
9 | @SuppressWarnings("unused")
10 | public class CasSecurityCondition extends AllNestedConditions {
11 |
12 | public CasSecurityCondition() {
13 | super(ConfigurationPhase.REGISTER_BEAN);
14 | }
15 |
16 | @ConditionalOnProperty(value = "security.cas.enabled", havingValue = "true", matchIfMissing = true)
17 | static class EnabledProperty {
18 | }
19 |
20 | @ConditionalOnProperty(value = "security.cas.server.base-url")
21 | static class ServerInstanceProperty {
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSecurityConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5 |
6 | /**
7 | * @author Thibaud Leprêtre
8 | */
9 | public interface CasSecurityConfigurer {
10 |
11 | void configure(CasAuthenticationFilterConfigurer filter);
12 |
13 | void configure(CasAuthenticationProviderSecurityBuilder provider);
14 |
15 | void configure(CasSingleSignOutFilterConfigurer filter);
16 |
17 | void configure(HttpSecurity http) throws Exception;
18 |
19 | void configure(CasTicketValidatorBuilder ticketValidator);
20 |
21 | void configure(AuthenticationManagerBuilder auth) throws Exception;
22 | }
23 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSecurityConfigurerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5 |
6 | /**
7 | * @author Thibaud Leprêtre
8 | */
9 | public abstract class CasSecurityConfigurerAdapter implements CasSecurityConfigurer {
10 |
11 | @Override
12 | public void configure(CasAuthenticationFilterConfigurer filter) {
13 | }
14 |
15 | @Override
16 | public void configure(CasSingleSignOutFilterConfigurer filter) {
17 | }
18 |
19 | @Override
20 | public void configure(CasAuthenticationProviderSecurityBuilder provider) {
21 | }
22 |
23 | @Override
24 | public void configure(HttpSecurity http) throws Exception {
25 | }
26 |
27 | @Override
28 | public void configure(CasTicketValidatorBuilder ticketValidator) {
29 | }
30 |
31 | @Override
32 | public void configure(AuthenticationManagerBuilder auth) throws Exception {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSecurityProperties.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import lombok.Data;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
6 |
7 | import java.net.URI;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.UUID;
11 |
12 | import static org.springframework.boot.autoconfigure.security.SecurityProperties.BASIC_AUTH_ORDER;
13 |
14 | /**
15 | * @author Thibaud Leprêtre
16 | */
17 | @Data
18 | @ConfigurationProperties(prefix = "security.cas")
19 | public class CasSecurityProperties {
20 |
21 | public static final int CAS_AUTH_ORDER = BASIC_AUTH_ORDER - 1;
22 |
23 | private boolean enabled = true;
24 |
25 | private Authorization authorization = new Authorization();
26 |
27 | private User user = new User();
28 |
29 | private Server server = new Server();
30 |
31 | private Service service = new Service();
32 |
33 | /**
34 | * @see org.springframework.security.cas.authentication.CasAuthenticationProvider
35 | */
36 | private String key = UUID.randomUUID().toString();
37 |
38 | /**
39 | * Comma-separated list of paths to secure.
40 | */
41 | private String[] paths = new String[]{"/**"};
42 |
43 | /**
44 | * Security authorize mode to apply.
45 | */
46 | private SecurityAuthorizeMode authorizeMode = SecurityAuthorizeMode.AUTHENTICATED;
47 |
48 | private ProxyValidation proxyValidation = new ProxyValidation();
49 |
50 | @Deprecated
51 | @DeprecatedConfigurationProperty(replacement = "security.cas.authorization.mode")
52 | public SecurityAuthorizeMode getAuthorizeMode() {
53 | return authorizeMode;
54 | }
55 |
56 | public enum ServiceResolutionMode {
57 | STATIC, DYNAMIC
58 | }
59 |
60 | public enum SecurityAuthorizeMode {
61 | /**
62 | * Must be a member of one of the security roles.
63 | */
64 | ROLE,
65 |
66 | /**
67 | * Must be an authenticated user.
68 | */
69 | AUTHENTICATED,
70 |
71 | /**
72 | * No security authorization is setup.
73 | */
74 | NONE
75 | }
76 |
77 | @Data
78 | public static class Authorization {
79 | private SecurityAuthorizeMode mode = SecurityAuthorizeMode.AUTHENTICATED;
80 |
81 | private String[] roles = new String[] { "USER" };
82 | }
83 |
84 | @Data
85 | public static class User {
86 | private String[] rolesAttributes = new String[0];
87 |
88 | /**
89 | * Default roles are roles that will be automatically appends to other roles definitions
90 | * For example if you defined: {@code security.cas.user.roles = USER}
91 | * and {@code security.cas.user.defaultRoles = MEMBER}. At the end the user will have both {@code USER} and
92 | * {@code MEMBER} role.
93 | *
94 | * Same thing if you're using {@link #rolesAttributes}, {@code defaultRoles} will be append to the list of
95 | * roles retrieve from {@code CAS Attributes}.
96 | */
97 | private String[] defaultRoles = new String[0];
98 | }
99 |
100 | @Data
101 | public static class Server {
102 |
103 | /**
104 | * CAS Server protocol version used to define which {@link org.jasig.cas.client.validation.TicketValidator} to
105 | * use.
106 | *
107 | * By default {@code ProxyTicketValidator} is selected rather than {@code ServiceTicketValidator}.
108 | *
109 | * @see org.jasig.cas.client.validation.Cas30ProxyTicketValidator
110 | * @see org.jasig.cas.client.validation.Cas20ProxyTicketValidator
111 | * @see org.jasig.cas.client.validation.Cas10TicketValidationFilter
112 | */
113 | private int protocolVersion = 3;
114 |
115 | /**
116 | * CAS Server base url, example https://my-cas.server.com/
117 | */
118 | private URI baseUrl;
119 |
120 | /**
121 | * CAS Server validation base url, example https://my-cas.server.internal/
122 | *
123 | * If defined it will be used to compute complete validation base url (for ticket validation)
124 | * instead of using {@link Server#baseUrl}.
125 | *
126 | * Validation url request is executed by the java CAS client when intercepting a
127 | * service ticket or proxy ticket . Thus it can be useful to be different than
128 | * {@link Server#baseUrl} when CAS server can't share the same network as your browser (for example).
129 | *
130 | * For example when using containers (Docker or others) or VM, you can't use {@code localhost} hostname
131 | * in your validation url since your CAS service inside a container or VM doesn't have the same
132 | * {@code localhost} as you host machine.
133 | *
134 | * @see Server#baseUrl
135 | * @see Paths#validationBaseUrl
136 | * @see Service#callbackBaseUrl
137 | */
138 | private URI validationBaseUrl;
139 |
140 | private Paths paths = new Paths();
141 |
142 | @Data
143 | public static class Paths {
144 | /**
145 | * CAS Server login path that will be append to {@link Server#baseUrl}
146 | *
147 | * @see org.springframework.security.cas.web.CasAuthenticationEntryPoint
148 | */
149 | private String login = "/login";
150 |
151 | /**
152 | * CAS Server logout path that will be append to {@link Server#baseUrl}
153 | */
154 | private String logout = "/logout";
155 | }
156 | }
157 |
158 | @Data
159 | public static class Service {
160 |
161 | private ServiceResolutionMode resolutionMode = ServiceResolutionMode.STATIC;
162 |
163 | /**
164 | * CAS Service base url (your application base url)
165 | */
166 | private URI baseUrl;
167 |
168 | /**
169 | * CAS Service callback base url, example https://my.service.com/
170 | *
171 | * If defined it will be used to compute complete proxy callback url instead of using
172 | * {@link Service#baseUrl}.
173 | * It will also be use even if {@link #baseUrl} is not defined and you're using {@link Service#resolutionMode}
174 | * is equals to {@link ServiceResolutionMode#DYNAMIC}.
175 | *
176 | * Proxy callback request is a fully new request executed from CAS server (using its own http client)
177 | * to your service. Thus it can be useful to be different than {@link Service#baseUrl} when CAS server
178 | * can't share the same network as your browser (for example).
179 | *
180 | * For example when using containers (Docker or others) or VM, you can't use {@code localhost} hostname
181 | * in your proxy callback url since CAS server inside a container or VM doesn't have the same
182 | * {@code localhost} as your host machine.
183 | *
184 | * @see Service#baseUrl
185 | * @see Paths#proxyCallback
186 | * @see Server#validationBaseUrl
187 | */
188 | private URI callbackBaseUrl;
189 |
190 | private Paths paths = new Paths();
191 |
192 | @Data
193 | public static class Paths {
194 |
195 | /**
196 | * CAS Service login path that will be append to {@link Service#baseUrl}
197 | */
198 | private String login = "/login";
199 |
200 | /**
201 | * CAS Service logout path that will be append to {@link Service#baseUrl}
202 | *
203 | * @see org.springframework.security.web.authentication.logout.LogoutFilter
204 | */
205 | private String logout = "/logout";
206 |
207 | /**
208 | * CAS Service proxy callback path that will be append to {@link Service#callbackBaseUrl} if defined else
209 | * fallback to {@link Service#baseUrl}
210 | *
211 | * @see Service#callbackBaseUrl
212 | * @see org.jasig.cas.client.validation.Cas20ServiceTicketValidator
213 | */
214 | private String proxyCallback;
215 | }
216 |
217 | }
218 |
219 | @Data
220 | public static class ProxyValidation {
221 |
222 | private boolean enabled = true;
223 |
224 | private List> chains = new ArrayList<>();
225 | }
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSingleSignOutFilterConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import lombok.NonNull;
4 | import lombok.Setter;
5 | import lombok.experimental.Accessors;
6 | import org.jasig.cas.client.session.SessionMappingStorage;
7 | import org.jasig.cas.client.session.SingleSignOutFilter;
8 | import org.springframework.util.StringUtils;
9 |
10 | /**
11 | * @author Thibaud Leprêtre
12 | */
13 | @Setter
14 | @Accessors(fluent = true)
15 | public class CasSingleSignOutFilterConfigurer {
16 |
17 | @NonNull
18 | private SessionMappingStorage sessionMappingStorage;
19 |
20 | @NonNull
21 | private String relayStateParameterName;
22 |
23 | @NonNull
24 | private String logoutParameterName;
25 |
26 | @NonNull
27 | private String artifactParameterName;
28 |
29 | void configure(SingleSignOutFilter filter) {
30 | if (sessionMappingStorage != null) {
31 | filter.setSessionMappingStorage(sessionMappingStorage);
32 | }
33 |
34 | if (StringUtils.hasText(relayStateParameterName)) {
35 | filter.setRelayStateParameterName(relayStateParameterName);
36 | }
37 |
38 | if (StringUtils.hasText(logoutParameterName)) {
39 | filter.setLogoutParameterName(logoutParameterName);
40 | }
41 |
42 | if (StringUtils.hasText(artifactParameterName)) {
43 | filter.setArtifactParameterName(artifactParameterName);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasTicketValidatorBuilder.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import lombok.Setter;
4 | import lombok.experimental.Accessors;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
7 | import org.jasig.cas.client.proxy.ProxyRetriever;
8 | import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
9 | import org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator;
10 | import org.jasig.cas.client.validation.Cas10TicketValidator;
11 | import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
12 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
13 | import org.jasig.cas.client.validation.Cas30ProxyTicketValidator;
14 | import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
15 | import org.jasig.cas.client.validation.ProxyList;
16 | import org.jasig.cas.client.validation.TicketValidator;
17 | import org.springframework.util.StringUtils;
18 |
19 | import java.util.Map;
20 |
21 | /**
22 | * @author Thibaud Leprêtre
23 | */
24 | @Accessors(fluent = true)
25 | @Setter
26 | @Slf4j
27 | public class CasTicketValidatorBuilder {
28 |
29 | private final String casServerUrlPrefix;
30 |
31 | String proxyCallbackUrl;
32 |
33 | ProxyGrantingTicketStorage proxyGrantingTicketStorage;
34 |
35 | ProxyRetriever proxyRetriever;
36 |
37 | HttpURLConnectionFactory urlConnectionFactory;
38 |
39 | boolean renew;
40 |
41 | Map customParameters;
42 |
43 | Boolean proxyChainsValidation;
44 |
45 | ProxyList proxyChains;
46 |
47 | Boolean allowEmptyProxyChain;
48 |
49 | private int protocolVersion = 3;
50 |
51 | private Boolean proxyTicketValidator;
52 |
53 | CasTicketValidatorBuilder(String casServerUrlPrefix) {
54 | this.casServerUrlPrefix = casServerUrlPrefix;
55 | }
56 |
57 | public String getCasServerUrlPrefix() {
58 | return casServerUrlPrefix;
59 | }
60 |
61 | public TicketValidator build() {
62 | CasTicketValidatorBuilder builder;
63 | if (proxyTicketValidator == null && protocolVersion > 1) {
64 | logger.debug("\"proxyTicketValidator\" configuration is missing, fallback on proxyTicketValidation = true");
65 | proxyTicketValidator = true;
66 | }
67 | if (isUnsupportedVersion()) {
68 | logger.warn("Protocol version {} is not valid protocol, will be fallback to version 3", protocolVersion);
69 | }
70 | if (protocolVersion == 1) {
71 | builder = buildCas10TicketValidatorBuilder();
72 | } else if (protocolVersion == 2) {
73 | builder = buildCas20TicketValidatorBuilder();
74 | } else {
75 | builder = buildCas30TicketValidatorBuilder();
76 | }
77 | configure(builder);
78 | return builder.build();
79 | }
80 |
81 | private CasTicketValidatorBuilder buildCas30TicketValidatorBuilder() {
82 | CasTicketValidatorBuilder builder;
83 | if (proxyTicketValidator != null && !proxyTicketValidator) {
84 | builder = new Cas30ServiceTicketValidatorBuilder(casServerUrlPrefix);
85 | } else {
86 | builder = new Cas30ProxyTicketValidatorBuilder(casServerUrlPrefix);
87 | }
88 | return builder;
89 | }
90 |
91 | private CasTicketValidatorBuilder buildCas20TicketValidatorBuilder() {
92 | CasTicketValidatorBuilder builder;
93 | if (proxyTicketValidator != null && !proxyTicketValidator) {
94 | builder = new Cas20ServiceTicketValidatorBuilder(casServerUrlPrefix);
95 | } else {
96 | builder = new Cas20ProxyTicketValidatorBuilder(casServerUrlPrefix);
97 | }
98 | return builder;
99 | }
100 |
101 | private CasTicketValidatorBuilder buildCas10TicketValidatorBuilder() {
102 | CasTicketValidatorBuilder builder;
103 | if (proxyTicketValidator != null) {
104 | logger.warn("Proxy ticket validator isn't possible using protocol version 1, will be omitted!");
105 | }
106 | builder = new Cas10TicketValidatorBuilder(casServerUrlPrefix);
107 | return builder;
108 | }
109 |
110 | private boolean isUnsupportedVersion() {
111 | return protocolVersion > 3 || protocolVersion < 1;
112 | }
113 |
114 | private void configure(CasTicketValidatorBuilder builder) {
115 | builder.proxyCallbackUrl(proxyCallbackUrl)
116 | .proxyGrantingTicketStorage(proxyGrantingTicketStorage)
117 | .proxyRetriever(proxyRetriever)
118 | .urlConnectionFactory(urlConnectionFactory)
119 | .renew(renew)
120 | .customParameters(customParameters)
121 | .proxyChainsValidation(proxyChainsValidation)
122 | .proxyChains(proxyChains)
123 | .allowEmptyProxyChain(allowEmptyProxyChain);
124 | }
125 |
126 | private abstract static class AbstractTicketValidatorBuilder
127 | extends CasTicketValidatorBuilder {
128 |
129 | AbstractTicketValidatorBuilder(String casServerUrlPrefix) {
130 | super(casServerUrlPrefix);
131 | }
132 |
133 | protected void configure(T ticketValidator) {
134 | if (urlConnectionFactory != null) {
135 | ticketValidator.setURLConnectionFactory(urlConnectionFactory);
136 | }
137 | if (customParameters != null) {
138 | ticketValidator.setCustomParameters(customParameters);
139 | }
140 | ticketValidator.setRenew(renew);
141 | }
142 | }
143 |
144 | private static class Cas10TicketValidatorBuilder extends AbstractTicketValidatorBuilder {
145 |
146 | private static final String OMISSION_MESSAGE_TEMPLATE =
147 | "Configuration \"{}\" isn't possible using protocol version 1, will be omitted!";
148 |
149 | Cas10TicketValidatorBuilder(String casServerUrlPrefix) {
150 | super(casServerUrlPrefix);
151 | }
152 |
153 | @Override
154 | public TicketValidator build() {
155 | Cas10TicketValidator ticketValidator = new Cas10TicketValidator(getCasServerUrlPrefix());
156 | if (StringUtils.hasText(proxyCallbackUrl)) {
157 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyCallbackUrl");
158 | }
159 | if (proxyGrantingTicketStorage != null) {
160 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyGrantingTicketStorage");
161 | }
162 | if (proxyRetriever != null) {
163 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyRetriever");
164 | }
165 | if (proxyChainsValidation != null) {
166 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChainsValidation");
167 | }
168 | if (proxyChains != null) {
169 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChains");
170 | }
171 | if (allowEmptyProxyChain != null) {
172 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "allowEmptyProxyChain");
173 | }
174 | super.configure(ticketValidator);
175 | return ticketValidator;
176 | }
177 | }
178 |
179 | private static class Cas20ServiceTicketValidatorBuilder
180 | extends AbstractTicketValidatorBuilder {
181 |
182 | static final String OMISSION_MESSAGE_TEMPLATE = "Configuration \"{}\" isn't possible using " +
183 | "service ticket validator (please consider proxy ticket validator), will be omitted!";
184 |
185 | Cas20ServiceTicketValidatorBuilder(String casServerUrlPrefix) {
186 | super(casServerUrlPrefix);
187 | }
188 |
189 | @Override
190 | public TicketValidator build() {
191 | Cas20ServiceTicketValidator ticketValidator = new Cas20ServiceTicketValidator(getCasServerUrlPrefix());
192 | if (proxyChainsValidation != null) {
193 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChainsValidation");
194 | }
195 | if (proxyChains != null) {
196 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChains");
197 | }
198 | if (allowEmptyProxyChain != null) {
199 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "allowEmptyProxyChain");
200 | }
201 | configure(ticketValidator);
202 | return ticketValidator;
203 | }
204 |
205 | @Override
206 | protected void configure(Cas20ServiceTicketValidator ticketValidator) {
207 | super.configure(ticketValidator);
208 | if (proxyGrantingTicketStorage != null) {
209 | ticketValidator.setProxyGrantingTicketStorage(proxyGrantingTicketStorage);
210 | }
211 | if (proxyRetriever != null) {
212 | ticketValidator.setProxyRetriever(proxyRetriever);
213 | }
214 | if (StringUtils.hasText(proxyCallbackUrl)) {
215 | ticketValidator.setProxyCallbackUrl(proxyCallbackUrl);
216 | }
217 | }
218 | }
219 |
220 | private static class Cas20ProxyTicketValidatorBuilder extends Cas20ServiceTicketValidatorBuilder {
221 |
222 | Cas20ProxyTicketValidatorBuilder(String casServerUrlPrefix) {
223 | super(casServerUrlPrefix);
224 | }
225 |
226 | @Override
227 | public TicketValidator build() {
228 | Cas20ProxyTicketValidator ticketValidator = new Cas20ProxyTicketValidator(getCasServerUrlPrefix());
229 | super.configure(ticketValidator);
230 |
231 | if (proxyChainsValidation != null) {
232 | ticketValidator.setAcceptAnyProxy(!proxyChainsValidation);
233 | }
234 | if (allowEmptyProxyChain != null) {
235 | ticketValidator.setAllowEmptyProxyChain(allowEmptyProxyChain);
236 | }
237 | if (proxyChains != null) {
238 | ticketValidator.setAllowedProxyChains(proxyChains);
239 | }
240 |
241 | return ticketValidator;
242 | }
243 | }
244 |
245 | private static class Cas30ServiceTicketValidatorBuilder extends Cas20ServiceTicketValidatorBuilder {
246 |
247 | Cas30ServiceTicketValidatorBuilder(String casServerUrlPrefix) {
248 | super(casServerUrlPrefix);
249 | }
250 |
251 | @Override
252 | public TicketValidator build() {
253 | Cas30ServiceTicketValidator ticketValidator = new Cas30ServiceTicketValidator(getCasServerUrlPrefix());
254 | if (proxyChainsValidation != null) {
255 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChainsValidation");
256 | }
257 | if (proxyChains != null) {
258 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "proxyChains");
259 | }
260 | if (allowEmptyProxyChain != null) {
261 | logger.warn(OMISSION_MESSAGE_TEMPLATE, "allowEmptyProxyChain");
262 | }
263 | super.configure(ticketValidator);
264 | return ticketValidator;
265 | }
266 | }
267 |
268 | private static class Cas30ProxyTicketValidatorBuilder extends Cas20ServiceTicketValidatorBuilder {
269 |
270 | Cas30ProxyTicketValidatorBuilder(String casServerUrlPrefix) {
271 | super(casServerUrlPrefix);
272 | }
273 |
274 | @Override
275 | public Cas30ProxyTicketValidator build() {
276 | Cas30ProxyTicketValidator ticketValidator = new Cas30ProxyTicketValidator(getCasServerUrlPrefix());
277 | super.configure(ticketValidator);
278 |
279 | if (proxyChainsValidation != null) {
280 | ticketValidator.setAcceptAnyProxy(!proxyChainsValidation);
281 | }
282 | if (allowEmptyProxyChain != null) {
283 | ticketValidator.setAllowEmptyProxyChain(allowEmptyProxyChain);
284 | }
285 | if (proxyChains != null) {
286 | ticketValidator.setAllowedProxyChains(proxyChains);
287 | }
288 |
289 | return ticketValidator;
290 | }
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasTicketValidatorConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.jasig.cas.client.validation.TicketValidator;
4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | import java.net.URI;
8 | import java.util.List;
9 |
10 | /**
11 | * @author Thibaud Leprêtre
12 | */
13 | @ConditionalOnMissingBean(TicketValidator.class)
14 | public class CasTicketValidatorConfiguration {
15 |
16 | private final CasSecurityProperties casSecurityProperties;
17 |
18 | public CasTicketValidatorConfiguration(CasSecurityProperties casSecurityProperties) {
19 | this.casSecurityProperties = casSecurityProperties;
20 | }
21 |
22 | @Bean
23 | TicketValidator ticketValidator(List casSecurityConfigurers) {
24 | URI baseUrl = casSecurityProperties.getServer().getValidationBaseUrl() != null
25 | ? casSecurityProperties.getServer().getValidationBaseUrl()
26 | : casSecurityProperties.getServer().getBaseUrl();
27 | CasTicketValidatorBuilder builder = new CasTicketValidatorBuilder(baseUrl.toASCIIString());
28 | casSecurityConfigurers.forEach(c -> c.configure(builder));
29 | return builder.build();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/com/kakawait/spring/boot/security/cas/autoconfigure/SpringBoot1CasHttpSecurityConfigurerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.springframework.context.ApplicationContext;
4 | import org.springframework.core.annotation.Order;
5 | import org.springframework.security.authentication.AuthenticationManager;
6 | import org.springframework.security.cas.web.CasAuthenticationFilter;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
9 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
10 | import org.springframework.util.ReflectionUtils;
11 |
12 | import java.lang.reflect.Method;
13 | import java.util.List;
14 |
15 | import static com.kakawait.spring.boot.security.cas.autoconfigure.SpringBoot1CasHttpSecurityConfigurerAdapter.SpringBoot1SecurityProperties.SECURITY_PROPERTIES_HEADERS_CLASS;
16 |
17 | /**
18 | * @author Thibaud Leprêtre
19 | */
20 | @Order(CasSecurityProperties.CAS_AUTH_ORDER - 10)
21 | class SpringBoot1CasHttpSecurityConfigurerAdapter extends CasSecurityConfigurerAdapter {
22 |
23 | private static final String SPRING_BOOT_WEB_SECURITY_CONFIGURATION_CLASS =
24 | "org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration";
25 |
26 | private final SpringBoot1SecurityProperties securityProperties;
27 |
28 | SpringBoot1CasHttpSecurityConfigurerAdapter(SpringBoot1SecurityProperties securityProperties) {
29 | this.securityProperties = securityProperties;
30 | }
31 |
32 | @Override
33 | public void configure(HttpSecurity http) throws Exception {
34 | if (securityProperties.isRequireSsl()) {
35 | http.requiresChannel().anyRequest().requiresSecure();
36 | }
37 | if (!securityProperties.isEnableCsrf()) {
38 | http.csrf().disable();
39 | }
40 | configureHeaders(http);
41 | if (securityProperties.getBasic().isEnabled()) {
42 | BasicAuthenticationFilter basicAuthFilter = new BasicAuthenticationFilter(
43 | http.getSharedObject(ApplicationContext.class).getBean(AuthenticationManager.class));
44 | http.addFilterBefore(basicAuthFilter, CasAuthenticationFilter.class);
45 | }
46 | }
47 |
48 | @SuppressWarnings("ConstantConditions")
49 | private void configureHeaders(HttpSecurity http) throws Exception {
50 | Method method = ReflectionUtils.findMethod(Class.forName(SPRING_BOOT_WEB_SECURITY_CONFIGURATION_CLASS),
51 | "configureHeaders", HeadersConfigurer.class, Class.forName(SECURITY_PROPERTIES_HEADERS_CLASS));
52 | ReflectionUtils.invokeMethod(method, null, http.headers(), securityProperties.getHeaders());
53 | }
54 |
55 | @SuppressWarnings("ConstantConditions")
56 | static class SpringBoot1SecurityProperties {
57 |
58 | static final String SECURITY_PROPERTIES_HEADERS_CLASS =
59 | "org.springframework.boot.autoconfigure.security.SecurityProperties$Headers";
60 |
61 | private final Object securityProperties;
62 |
63 | SpringBoot1SecurityProperties(Object securityProperties) {
64 | this.securityProperties = securityProperties;
65 | }
66 |
67 | boolean isRequireSsl() {
68 | Method method = ReflectionUtils.findMethod(securityProperties.getClass(), "isRequireSsl");
69 | return (boolean) ReflectionUtils.invokeMethod(method, securityProperties);
70 | }
71 |
72 | boolean isEnableCsrf() {
73 | Method method = ReflectionUtils.findMethod(securityProperties.getClass(), "isEnableCsrf");
74 | return (boolean) ReflectionUtils.invokeMethod(method, securityProperties);
75 | }
76 |
77 | public Object getHeaders() {
78 | Method method = ReflectionUtils.findMethod(securityProperties.getClass(), "getHeaders");
79 | return ReflectionUtils.invokeMethod(method, securityProperties);
80 | }
81 |
82 | public Basic getBasic() {
83 | Method method = ReflectionUtils.findMethod(securityProperties.getClass(), "getBasic");
84 | return new Basic(ReflectionUtils.invokeMethod(method, securityProperties));
85 | }
86 |
87 | public User getUser() {
88 | Method method = ReflectionUtils.findMethod(securityProperties.getClass(), "getUser");
89 | return new User(ReflectionUtils.invokeMethod(method, securityProperties));
90 | }
91 |
92 | static class Basic {
93 | private final Object basicProperties;
94 |
95 | public Basic(Object basicProperties) {
96 | this.basicProperties = basicProperties;
97 | }
98 |
99 | public boolean isEnabled() {
100 | Method method = ReflectionUtils.findMethod(basicProperties.getClass(), "isEnabled");
101 | return (boolean) ReflectionUtils.invokeMethod(method, basicProperties);
102 | }
103 | }
104 |
105 | static class User {
106 | private final Object userProperties;
107 |
108 | public User(Object userProperties) {
109 | this.userProperties = userProperties;
110 | }
111 |
112 | @SuppressWarnings("unchecked")
113 | public List getRoles() {
114 | Method method = ReflectionUtils.findMethod(userProperties.getClass(), "getRole");
115 | return (List) ReflectionUtils.invokeMethod(method, userProperties);
116 | }
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/java/lombok.config:
--------------------------------------------------------------------------------
1 | lombok.log.fieldName = logger
2 | lombok.nonNull.exceptionType = IllegalArgumentException
3 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.kakawait.spring.boot.security.cas.autoconfigure.CasSecurityAutoConfiguration
2 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/test/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasAuthenticationFilterConfigurerTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
4 | import org.junit.jupiter.api.Test;
5 | import org.mockito.Mockito;
6 | import org.springframework.security.cas.web.CasAuthenticationFilter;
7 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
8 | import org.springframework.security.web.authentication.AuthenticationFailureHandler;
9 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
10 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
11 | import org.springframework.security.web.util.matcher.RequestMatcher;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | /**
16 | * @author Thibaud Leprêtre
17 | */
18 | public class CasAuthenticationFilterConfigurerTest {
19 |
20 | @Test
21 | public void configure_WithAnyParameters_InjectInsideCasAuthenticationFilter() {
22 | ProxyGrantingTicketStorage proxyGrantingTicketStorage = Mockito.mock(ProxyGrantingTicketStorage.class);
23 |
24 | ServiceAuthenticationDetailsSource serviceAuthenticationDetailsSource =
25 | Mockito.mock(ServiceAuthenticationDetailsSource.class);
26 | String proxyReceptorUrl = "dummyProxyReceptorUrl";
27 | AuthenticationFailureHandler authenticationFailureHandler = Mockito.mock(AuthenticationFailureHandler.class);
28 | AuthenticationSuccessHandler authenticationSuccessHandler = Mockito.mock(AuthenticationSuccessHandler.class);
29 | AuthenticationFailureHandler proxyAuthenticationFailureHandler =
30 | Mockito.mock(AuthenticationFailureHandler.class);
31 | RequestMatcher requestMatcher = Mockito.mock(RequestMatcher.class);
32 |
33 | CasAuthenticationFilter filter = new CasAuthenticationFilter();
34 |
35 | CasAuthenticationFilterConfigurer configurer = new CasAuthenticationFilterConfigurer();
36 | configurer.proxyGrantingTicketStorage(proxyGrantingTicketStorage)
37 | .proxyReceptorUrl(proxyReceptorUrl)
38 | .serviceAuthenticationDetailsSource(serviceAuthenticationDetailsSource)
39 | .authenticationFailureHandler(authenticationFailureHandler)
40 | .authenticationSuccessHandler(authenticationSuccessHandler)
41 | .proxyAuthenticationFailureHandler(proxyAuthenticationFailureHandler)
42 | .requiresAuthenticationRequestMatcher(requestMatcher);
43 |
44 | configurer.configure(filter);
45 |
46 | assertThat(filter)
47 | .hasFieldOrPropertyWithValue("proxyGrantingTicketStorage", proxyGrantingTicketStorage)
48 | .hasFieldOrPropertyWithValue("authenticationDetailsSource", serviceAuthenticationDetailsSource)
49 | .hasFieldOrPropertyWithValue("successHandler", authenticationSuccessHandler)
50 | .hasFieldOrPropertyWithValue("proxyFailureHandler", proxyAuthenticationFailureHandler)
51 | .hasFieldOrPropertyWithValue("requiresAuthenticationRequestMatcher", requestMatcher)
52 | .hasFieldOrPropertyWithValue("proxyReceptorMatcher",
53 | new AntPathRequestMatcher("/**" + proxyReceptorUrl))
54 | .hasFieldOrPropertyWithValue("failureHandler.serviceTicketFailureHandler",
55 | authenticationFailureHandler);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/test/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasAuthenticationProviderSecurityBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import com.kakawait.spring.security.cas.authentication.DynamicProxyCallbackUrlCasAuthenticationProvider;
4 | import org.jasig.cas.client.validation.TicketValidator;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.mockito.Mockito;
8 | import org.springframework.context.MessageSource;
9 | import org.springframework.security.cas.authentication.CasAuthenticationProvider;
10 | import org.springframework.security.cas.authentication.StatelessTicketCache;
11 | import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
12 | import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
13 |
14 | import java.util.Comparator;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 |
18 | /**
19 | * @author Thibaud Leprêtre
20 | */
21 | public class CasAuthenticationProviderSecurityBuilderTest {
22 |
23 | private CasAuthenticationProviderSecurityBuilder builder;
24 |
25 | @BeforeEach
26 | public void setUp() {
27 | builder = new CasAuthenticationProviderSecurityBuilder();
28 | }
29 |
30 | @Test
31 | public void build_Default_ResolutionModeStatic() {
32 | assertThat(builder.build()).isExactlyInstanceOf(CasAuthenticationProvider.class);
33 | }
34 |
35 | @Test
36 | public void build_WithDynamicResolutionMode_InstanceOfDynamicProxyCallbackUrlCasAuthenticationProvider() {
37 | builder.serviceResolutionMode(CasSecurityProperties.ServiceResolutionMode.DYNAMIC);
38 |
39 | assertThat(builder.build()).isExactlyInstanceOf(DynamicProxyCallbackUrlCasAuthenticationProvider.class);
40 | }
41 |
42 | @Test
43 | public void build_AnyParameters_InjectInsideCasAuthenticationProvider() {
44 | String key = "key";
45 | AuthenticationUserDetailsService userDetailsService = Mockito.mock(AuthenticationUserDetailsService.class);
46 | GrantedAuthoritiesMapper grantedAuthoritiesMapper = Mockito.mock(GrantedAuthoritiesMapper.class);
47 | MessageSource messageSource = Mockito.mock(MessageSource.class);
48 | StatelessTicketCache statelessTicketCache = Mockito.mock(StatelessTicketCache.class);
49 | TicketValidator ticketValidator = Mockito.mock(TicketValidator.class);
50 |
51 | builder.key(key)
52 | .authenticationUserDetailsService(userDetailsService)
53 | .grantedAuthoritiesMapper(grantedAuthoritiesMapper)
54 | .messageSource(messageSource)
55 | .statelessTicketCache(statelessTicketCache)
56 | .ticketValidator(ticketValidator);
57 |
58 | assertThat(builder.build())
59 | .extracting("key", "authenticationUserDetailsService", "authoritiesMapper", "statelessTicketCache",
60 | "ticketValidator")
61 | .usingElementComparator((Comparator) (o1, o2) -> (o1 == o2) ? 0 : -1)
62 | .containsOnly(key, userDetailsService, grantedAuthoritiesMapper, statelessTicketCache, ticketValidator);
63 |
64 | assertThat(builder.build()).hasFieldOrPropertyWithValue("messages.messageSource", messageSource);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/test/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSecurityAutoConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 |
4 | import com.kakawait.spring.security.cas.LaxServiceProperties;
5 | import com.kakawait.spring.security.cas.client.ticket.AttributePrincipalProxyTicketProvider;
6 | import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
7 | import com.kakawait.spring.security.cas.client.validation.AssertionProvider;
8 | import com.kakawait.spring.security.cas.client.validation.SecurityContextHolderAssertionProvider;
9 | import com.kakawait.spring.security.cas.web.authentication.CasLogoutSuccessHandler;
10 | import com.kakawait.spring.security.cas.web.authentication.ProxyCallbackAndServiceAuthenticationDetailsSource;
11 | import com.kakawait.spring.security.cas.web.authentication.RequestAwareCasLogoutSuccessHandler;
12 | import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
13 | import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
14 | import org.jasig.cas.client.validation.TicketValidator;
15 | import org.junit.jupiter.api.AfterEach;
16 | import org.junit.jupiter.api.Test;
17 | import org.springframework.beans.factory.BeanCreationException;
18 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
19 | import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
20 | import org.springframework.context.ConfigurableApplicationContext;
21 | import org.springframework.context.annotation.Bean;
22 | import org.springframework.context.annotation.Configuration;
23 | import org.springframework.core.env.PropertiesPropertySource;
24 | import org.springframework.mock.web.MockHttpServletRequest;
25 | import org.springframework.mock.web.MockServletContext;
26 | import org.springframework.security.authentication.AuthenticationManager;
27 | import org.springframework.security.authentication.TestingAuthenticationToken;
28 | import org.springframework.security.cas.ServiceProperties;
29 | import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
30 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
31 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
32 | import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
33 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
34 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
35 |
36 | import java.net.URI;
37 | import java.util.Properties;
38 |
39 | import static org.assertj.core.api.Assertions.assertThat;
40 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
41 | import static org.junit.jupiter.api.Assertions.assertThrows;
42 |
43 | /**
44 | * @author Thibaud Leprêtre
45 | */
46 | public class CasSecurityAutoConfigurationTest {
47 |
48 | private static final String CAS_SERVER_BASE_URL = "http://localhost:8443/cas";
49 |
50 | private static final String CAS_SERVICE_BASE_URL = "http://localhost:8080";
51 |
52 | private static final String CAS_SERVICE_LOGIN_URL = CAS_SERVICE_BASE_URL + "/login";
53 |
54 | private ConfigurableApplicationContext context;
55 |
56 | @AfterEach
57 | public void tearDown() {
58 | if (context != null) {
59 | context.close();
60 | }
61 | }
62 |
63 | @Test
64 | public void autoConfiguration_MissingCasServerBaseUrl_SkipAutoConfiguration() {
65 | load(new Properties(), EmptyConfiguration.class);
66 |
67 | assertThrows(NoSuchBeanDefinitionException.class,
68 | () -> context.getBean(CasSecurityAutoConfiguration.class)
69 | );
70 | }
71 |
72 | @Test
73 | public void autoConfiguration_DisableProperty_SkipAutoConfiguration() {
74 | Properties properties = new Properties();
75 | properties.put("security.cas.server.base-url", CAS_SERVER_BASE_URL);
76 | properties.put("security.cas.enabled", "false");
77 | load(properties, EmptyConfiguration.class);
78 |
79 | assertThrows(NoSuchBeanDefinitionException.class,
80 | () -> context.getBean(CasSecurityAutoConfiguration.class)
81 | );
82 | }
83 |
84 | @Test
85 | public void autoConfigure_WithoutServiceBaseUrl_Exception() {
86 | Properties properties = getDefaultProperties();
87 | properties.remove("security.cas.service.base-url");
88 |
89 | assertThatThrownBy(() -> load(properties, EmptyConfiguration.class))
90 | .isInstanceOf(BeanCreationException.class)
91 | .hasRootCauseExactlyInstanceOf(IllegalArgumentException.class)
92 | .hasMessageEndingWith("java.lang.IllegalArgumentException: Cas service base url must not be null " +
93 | "(ref property security.cas.service.base-url)");
94 | }
95 |
96 | @Test
97 | public void autoConfigure_DynamicModeWithoutServiceBaseUrl_NoException() {
98 | load(getDynamicModeProperties(), EmptyConfiguration.class);
99 | }
100 |
101 | @Test
102 | public void autoConfigure_StaticMode_DefaultBeans() {
103 | load(EmptyConfiguration.class);
104 |
105 | assertThat(context.getBean(ServiceProperties.class))
106 | .isExactlyInstanceOf(ServiceProperties.class)
107 | .hasFieldOrPropertyWithValue("service", CAS_SERVICE_LOGIN_URL);
108 | assertThat(context.getBean(CasAuthenticationEntryPoint.class))
109 | .isExactlyInstanceOf(CasAuthenticationEntryPoint.class);
110 | assertThat(context.getBean(ServiceAuthenticationDetailsSource.class))
111 | .isExactlyInstanceOf(ServiceAuthenticationDetailsSource.class);
112 | assertThat(context.getBean(CasLogoutSuccessHandler.class)).isExactlyInstanceOf(CasLogoutSuccessHandler.class);
113 | }
114 |
115 | @Test
116 | public void autoConfigure_DynamicMode_SpecificBeans() {
117 | load(getDynamicModeProperties(), EmptyConfiguration.class);
118 |
119 | assertThat(context.getBean(ServiceProperties.class))
120 | .isExactlyInstanceOf(LaxServiceProperties.class)
121 | .hasFieldOrPropertyWithValue("service", null);
122 | assertThat(context.getBean(CasLogoutSuccessHandler.class))
123 | .isExactlyInstanceOf(RequestAwareCasLogoutSuccessHandler.class);
124 | assertThat(context.getBean(ServiceAuthenticationDetailsSource.class))
125 | .isExactlyInstanceOf(ProxyCallbackAndServiceAuthenticationDetailsSource.class);
126 | assertThat(context.getBean(CasLogoutSuccessHandler.class))
127 | .isExactlyInstanceOf(RequestAwareCasLogoutSuccessHandler.class);
128 | }
129 |
130 | @Test
131 | public void autoConfigure_EmptyConfiguration_ProxyGrantingTicketStorageImplBean() {
132 | load(EmptyConfiguration.class);
133 |
134 | ProxyGrantingTicketStorage proxyGrantingTicketStorage = context.getBean(ProxyGrantingTicketStorage.class);
135 | assertThat(proxyGrantingTicketStorage).isExactlyInstanceOf(ProxyGrantingTicketStorageImpl.class);
136 |
137 | assertThat(context.getBean(TicketValidator.class))
138 | .hasFieldOrPropertyWithValue("proxyGrantingTicketStorage", proxyGrantingTicketStorage);
139 | }
140 |
141 | @Test
142 | public void autoConfiguration_EmptyConfiguration_SecurityContextHolderAssertionProviderBean() {
143 | load(EmptyConfiguration.class);
144 |
145 | assertThat(context.getBean(AssertionProvider.class)).isInstanceOf(SecurityContextHolderAssertionProvider.class);
146 | }
147 |
148 | @Test
149 | public void autoConfiguration_EmptyConfiguration_AttributePrincipalProxyTicketProviderBean() {
150 | load(EmptyConfiguration.class);
151 |
152 | assertThat(context.getBean(ProxyTicketProvider.class))
153 | .isInstanceOf(AttributePrincipalProxyTicketProvider.class);
154 | }
155 |
156 | @Test
157 | public void autoConfigure_WithProxyCallbackPathAndCallbackUrl_AbsoluteProxyCallbackUri() {
158 | Properties properties = getDynamicModeProperties();
159 | properties.put("security.cas.service.paths.proxy-callback", "/cas/proxy-callback");
160 | properties.put("security.cas.service.callback-base-url", "http://app:8081/test/");
161 |
162 | load(properties, EmptyConfiguration.class);
163 |
164 | ProxyCallbackAndServiceAuthenticationDetailsSource serviceAuthenticationDetailsSource =
165 | context.getBean(ProxyCallbackAndServiceAuthenticationDetailsSource.class);
166 | assertThat(serviceAuthenticationDetailsSource.buildDetails(new MockHttpServletRequest()))
167 | .hasFieldOrPropertyWithValue("proxyCallbackUri", URI.create("http://app:8081/test/cas/proxy-callback"));
168 | }
169 |
170 | @Test
171 | public void autoConfigure_WithProxyCallbackPathButWithoutCallbackUrl_RelativeProxyCallbackUri() {
172 | Properties properties = getDynamicModeProperties();
173 | properties.put("security.cas.service.paths.proxy-callback", "/cas/proxy-callback");
174 |
175 | load(properties, EmptyConfiguration.class);
176 |
177 | ProxyCallbackAndServiceAuthenticationDetailsSource serviceAuthenticationDetailsSource =
178 | context.getBean(ProxyCallbackAndServiceAuthenticationDetailsSource.class);
179 | assertThat(serviceAuthenticationDetailsSource.buildDetails(new MockHttpServletRequest()))
180 | .hasFieldOrPropertyWithValue("proxyCallbackUri", URI.create("/cas/proxy-callback"));
181 | }
182 |
183 | @Test
184 | public void autoConfigure_EmptyConfiguration_DefaultCasSecurityConfigurerAdapterBean() {
185 | load(EmptyConfiguration.class);
186 |
187 | String beanName ="com.kakawait.spring.boot.security.cas.autoconfigure.CasSecurityAutoConfiguration$" +
188 | "DefaultCasSecurityConfigurerAdapter";
189 | context.getBean(beanName);
190 | }
191 |
192 | @Test
193 | public void autoConfigure_WithCustomSecurityPath_NoIllegalArgumentException() {
194 | Properties properties = getDefaultProperties();
195 | properties.put("security.cas.paths", "/secured");
196 |
197 | load(properties, EmptyConfiguration.class);
198 | }
199 |
200 | private Properties getDefaultProperties() {
201 | Properties properties = new Properties();
202 | properties.put("security.cas.server.base-url", CAS_SERVER_BASE_URL);
203 | properties.put("security.cas.service.base-url", CAS_SERVICE_BASE_URL);
204 |
205 | return properties;
206 | }
207 |
208 | private Properties getDynamicModeProperties() {
209 | Properties properties = new Properties();
210 | properties.put("security.cas.server.base-url", CAS_SERVER_BASE_URL);
211 | properties.put("security.cas.service.resolution-mode", "dynamic");
212 |
213 | return properties;
214 | }
215 |
216 | private void load(Class>... configs) {
217 | load(getDefaultProperties(), configs);
218 | }
219 |
220 | private void load(Properties properties, Class>... configs) {
221 | Class[] securityConfigurationsClasses = {SecurityFilterAutoConfiguration.class,
222 | ObjectPostProcessorConfiguration.class, AuthenticationConfiguration.class,
223 | WebSecurityConfiguration.class};
224 |
225 | AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
226 | context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("test", properties));
227 | context.register(configs);
228 | context.register(DummyAuthenticationManagerConfiguration.class);
229 | context.register(securityConfigurationsClasses);
230 | context.register(CasSecurityAutoConfiguration.class);
231 | context.setServletContext(new MockServletContext());
232 | context.refresh();
233 | this.context = context;
234 | }
235 |
236 | @Configuration
237 | static class EmptyConfiguration {}
238 |
239 | private static class DummyAuthenticationManagerConfiguration {
240 | @Bean
241 | AuthenticationManager authenticationManager() {
242 | return authentication -> new TestingAuthenticationToken("kakawait", "secret");
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/test/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasSingleSignOutFilterConfigurerTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.jasig.cas.client.session.SessionMappingStorage;
4 | import org.jasig.cas.client.session.SingleSignOutFilter;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mockito;
7 | import org.springframework.test.util.ReflectionTestUtils;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | /**
12 | * @author Thibaud Leprêtre
13 | */
14 | public class CasSingleSignOutFilterConfigurerTest {
15 |
16 | @Test
17 | public void configure_WithAnyParameters_InjectInsideSingleSignOutHandler() {
18 | SessionMappingStorage sessionMappingStorage = Mockito.mock(SessionMappingStorage.class);
19 |
20 | SingleSignOutFilter filter = new SingleSignOutFilter();
21 |
22 | CasSingleSignOutFilterConfigurer configurer = new CasSingleSignOutFilterConfigurer();
23 | configurer.artifactParameterName("dummyArtifactParameterName")
24 | .logoutParameterName("dummyLogoutParameterName")
25 | .relayStateParameterName("dummyRelayStateParameterName")
26 | .sessionMappingStorage(sessionMappingStorage)
27 | .configure(filter);
28 |
29 | assertThat(ReflectionTestUtils.getField(filter, "HANDLER"))
30 | .hasFieldOrPropertyWithValue("artifactParameterName", "dummyArtifactParameterName")
31 | .hasFieldOrPropertyWithValue("logoutParameterName", "dummyLogoutParameterName")
32 | .hasFieldOrPropertyWithValue("relayStateParameterName", "dummyRelayStateParameterName")
33 | .hasFieldOrPropertyWithValue("sessionMappingStorage", sessionMappingStorage);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-autoconfigure/src/test/java/com/kakawait/spring/boot/security/cas/autoconfigure/CasTicketValidatorBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.boot.security.cas.autoconfigure;
2 |
3 | import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
4 | import org.jasig.cas.client.proxy.ProxyRetriever;
5 | import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
6 | import org.jasig.cas.client.validation.Cas10TicketValidator;
7 | import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
8 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
9 | import org.jasig.cas.client.validation.Cas30ProxyTicketValidator;
10 | import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
11 | import org.jasig.cas.client.validation.ProxyList;
12 | import org.jasig.cas.client.validation.TicketValidator;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 | import org.junit.jupiter.api.extension.ExtendWith;
16 | import org.mockito.Mockito;
17 | import org.springframework.boot.test.system.CapturedOutput;
18 | import org.springframework.boot.test.system.OutputCaptureExtension;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.Comparator;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | import static org.assertj.core.api.Assertions.assertThat;
27 |
28 | /**
29 | * @author Thibaud Leprêtre
30 | */
31 | @ExtendWith(OutputCaptureExtension.class)
32 | public class CasTicketValidatorBuilderTest {
33 |
34 | private static final String CAS_SERVER_URL_PREFIX = "http://my.cas.server.base.url/";
35 |
36 | private static final String V1_WARN_MESSAGE_TEMPLATE =
37 | "WARN com.kakawait.spring.boot.security.cas.autoconfigure.CasTicketValidatorBuilder - " +
38 | "Configuration \"%s\" isn't possible using protocol version 1, will be omitted!";
39 |
40 | private static final String SERVICE_VALIDATOR_WARN_MESSAGE_TEMPLATE =
41 | "WARN com.kakawait.spring.boot.security.cas.autoconfigure.CasTicketValidatorBuilder - " +
42 | "Configuration \"%s\" isn't possible using service ticket validator " +
43 | "(please consider proxy ticket validator), will be omitted!";
44 |
45 | private CasTicketValidatorBuilder builder;
46 |
47 | @BeforeEach
48 | public void setUp() {
49 | builder = new CasTicketValidatorBuilder(CAS_SERVER_URL_PREFIX);
50 | }
51 |
52 | @Test
53 | public void build_DefaultTicketValidator_Cas30ProxyTicketValidator() {
54 | assertThat(builder.build()).isInstanceOf(Cas30ProxyTicketValidator.class);
55 | }
56 |
57 | @Test
58 | public void build_UnsupportedProtocolVersion_FallbackToProtocolVersion3() {
59 | builder.protocolVersion(42);
60 |
61 | assertThat(builder.build()).isInstanceOf(Cas30ProxyTicketValidator.class);
62 | }
63 |
64 | @Test
65 | public void build_WithProtocolVersion1_Cas10TicketValidator() {
66 | builder.protocolVersion(1);
67 |
68 | assertThat(builder.build()).isInstanceOf(Cas10TicketValidator.class);
69 | }
70 |
71 | @Test
72 | public void build_WithProtocolVersion2_Cas20ProxyTicketValidator() {
73 | builder.protocolVersion(2);
74 |
75 | assertThat(builder.build()).isInstanceOf(Cas20ProxyTicketValidator.class);
76 | }
77 |
78 | @Test
79 | public void build_WithoutProxyTicketValidator_CasX0ServiceTicketValidator() {
80 | builder.protocolVersion(3).proxyTicketValidator(false);
81 |
82 | assertThat(builder.build()).isInstanceOf(Cas30ServiceTicketValidator.class);
83 |
84 | builder.protocolVersion(2).proxyTicketValidator(false);
85 |
86 | assertThat(builder.build()).isInstanceOf(Cas20ServiceTicketValidator.class);
87 | }
88 |
89 | @Test
90 | public void build_WithProxyTicketValidator_IgnoredWithProtocolVersion1() {
91 | builder.protocolVersion(1).proxyTicketValidator(true);
92 |
93 | assertThat(builder.build()).isInstanceOf(Cas10TicketValidator.class);
94 |
95 | builder.protocolVersion(1).proxyTicketValidator(false);
96 |
97 | assertThat(builder.build()).isInstanceOf(Cas10TicketValidator.class);
98 | }
99 |
100 | @Test
101 | public void build_UsingProtocolVersion3AndAnyParameters_InjectInsideTicketValidator() {
102 | testBuilder(3);
103 | }
104 |
105 | @Test
106 | public void build_UsingProtocolVersion2AndAnyParameters_InjectInsideTicketValidator() {
107 | testBuilder(2);
108 | }
109 |
110 | @Test
111 | public void build_UsingProtocolVersion1AndAnyParameters_InjectInsideTicketValidator() {
112 | testBuilder(1);
113 | }
114 |
115 | @Test
116 | public void build_ProtocolVersion1WithIncompatibleParameter_LogWarnMessage(CapturedOutput output) {
117 | int protocolVersion = 1;
118 |
119 | CasTicketValidatorBuilder builder = fulfilledBuilder();
120 | builder.protocolVersion(protocolVersion);
121 |
122 | builder.build();
123 |
124 | List warns = new ArrayList<>();
125 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "proxyCallbackUrl"));
126 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "proxyGrantingTicketStorage"));
127 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "proxyRetriever"));
128 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "proxyChainsValidation"));
129 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "proxyChains"));
130 | warns.add(String.format(V1_WARN_MESSAGE_TEMPLATE, "allowEmptyProxyChain"));
131 |
132 | for (String warn : warns) {
133 | assertThat(output).contains(warn);
134 | }
135 | }
136 |
137 | @Test
138 | public void build_ServiceValidatorProtocolWithIncompatibleParameter_LogWarnMessage(CapturedOutput output) {
139 | int protocolVersion = 2;
140 |
141 | CasTicketValidatorBuilder builder = fulfilledBuilder();
142 | builder.protocolVersion(protocolVersion).proxyTicketValidator(false);
143 |
144 | builder.build();
145 |
146 | List warns = new ArrayList<>();
147 | warns.add(String.format(SERVICE_VALIDATOR_WARN_MESSAGE_TEMPLATE, "proxyChainsValidation"));
148 | warns.add(String.format(SERVICE_VALIDATOR_WARN_MESSAGE_TEMPLATE, "proxyChains"));
149 | warns.add(String.format(SERVICE_VALIDATOR_WARN_MESSAGE_TEMPLATE, "allowEmptyProxyChain"));
150 |
151 | for (String warn : warns) {
152 | assertThat(output).contains(warn);
153 | }
154 |
155 | protocolVersion = 3;
156 |
157 | builder.protocolVersion(protocolVersion).proxyTicketValidator(false);
158 | builder.build();
159 |
160 | for (String warn : warns) {
161 | assertThat(output).contains(warn);
162 | }
163 | }
164 |
165 | private CasTicketValidatorBuilder fulfilledBuilder() {
166 | HttpURLConnectionFactory urlConnectionFactory = Mockito.mock(HttpURLConnectionFactory.class);
167 | ProxyList proxyList = new ProxyList();
168 | boolean proxyChainsValidation = true;
169 | ProxyGrantingTicketStorage proxyGrantingTicketStorage = Mockito.mock(ProxyGrantingTicketStorage.class);
170 | String proxyCallbackUrl = "http://my.client/proxy/callback";
171 | boolean allowEmptyProxyChain = false;
172 | Map customParameters = Collections.singletonMap("test", "value");
173 | ProxyRetriever proxyRetriever = Mockito.mock(ProxyRetriever.class);
174 | boolean renew = false;
175 |
176 | builder.urlConnectionFactory(urlConnectionFactory)
177 | .proxyChains(proxyList)
178 | .proxyChainsValidation(proxyChainsValidation)
179 | .proxyGrantingTicketStorage(proxyGrantingTicketStorage)
180 | .proxyCallbackUrl(proxyCallbackUrl)
181 | .allowEmptyProxyChain(allowEmptyProxyChain)
182 | .customParameters(customParameters)
183 | .proxyRetriever(proxyRetriever)
184 | .renew(renew);
185 | return builder;
186 | }
187 |
188 | private void testBuilder(int protocolVersion) {
189 | HttpURLConnectionFactory urlConnectionFactory = Mockito.mock(HttpURLConnectionFactory.class);
190 | ProxyList proxyList = new ProxyList();
191 | boolean proxyChainsValidation = true;
192 | ProxyGrantingTicketStorage proxyGrantingTicketStorage = Mockito.mock(ProxyGrantingTicketStorage.class);
193 | String proxyCallbackUrl = "http://my.client/proxy/callback";
194 | boolean allowEmptyProxyChain = false;
195 | Map customParameters = Collections.singletonMap("test", "value");
196 | ProxyRetriever proxyRetriever = Mockito.mock(ProxyRetriever.class);
197 | boolean renew = false;
198 |
199 |
200 | builder.protocolVersion(protocolVersion)
201 | .urlConnectionFactory(urlConnectionFactory)
202 | .proxyChains(proxyList)
203 | .proxyChainsValidation(proxyChainsValidation)
204 | .proxyGrantingTicketStorage(proxyGrantingTicketStorage)
205 | .proxyCallbackUrl(proxyCallbackUrl)
206 | .allowEmptyProxyChain(allowEmptyProxyChain)
207 | .customParameters(customParameters)
208 | .proxyRetriever(proxyRetriever)
209 | .renew(renew);
210 |
211 | List fields = new ArrayList<>();
212 | fields.add("urlConnectionFactory");
213 | fields.add("allowedProxyChains");
214 | fields.add("proxyGrantingTicketStorage");
215 | fields.add("proxyCallbackUrl");
216 | fields.add("customParameters");
217 | fields.add("proxyRetriever");
218 | List values = new ArrayList<>();
219 | values.add(urlConnectionFactory);
220 | values.add(proxyList);
221 | values.add(proxyGrantingTicketStorage);
222 | values.add(proxyCallbackUrl);
223 | values.add(customParameters);
224 | values.add(proxyRetriever);
225 |
226 | if (protocolVersion == 1) {
227 | fields.remove("allowedProxyChains");
228 | values.remove(proxyList);
229 | fields.remove("proxyCallbackUrl");
230 | values.remove(proxyCallbackUrl);
231 | fields.remove("proxyGrantingTicketStorage");
232 | values.remove(proxyGrantingTicketStorage);
233 | fields.remove("proxyRetriever");
234 | values.remove(proxyRetriever);
235 | }
236 |
237 | TicketValidator ticketValidator = builder.build();
238 | assertThat(ticketValidator)
239 | .extracting(fields.toArray(new String[0]))
240 | .usingElementComparator((Comparator) (o1, o2) -> (o1 == o2) ? 0 : -1)
241 | .containsExactly(values.toArray());
242 |
243 | if (protocolVersion > 1) {
244 | assertThat(ticketValidator).hasFieldOrPropertyWithValue("allowEmptyProxyChain", allowEmptyProxyChain);
245 | assertThat(ticketValidator).hasFieldOrPropertyWithValue("acceptAnyProxy", !proxyChainsValidation);
246 | }
247 | assertThat(ticketValidator).hasFieldOrPropertyWithValue("renew", renew);
248 | }
249 |
250 | }
251 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:3-eclipse-temurin-11 as build
2 | WORKDIR /src
3 | COPY . /src
4 | RUN mvn clean install && mvn -f cas-security-spring-boot-sample/pom.xml clean install
5 |
6 | FROM eclipse-temurin:11-jre
7 | WORKDIR /app
8 | COPY --from=build /src/cas-security-spring-boot-sample/target/cas-security-spring-boot-sample-1.1.0.jar /app
9 | ENV JAVA_OPTS=""
10 | CMD [ "sh", "-c", "java $JAVA_OPTS -jar /app/cas-security-spring-boot-sample-1.1.0.jar" ]
11 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/README.md:
--------------------------------------------------------------------------------
1 | # Cas security spring boot sample
2 |
3 | [](https://asciinema.org/a/eNoup6KEIfd2TbmSwnpQxdjCT)
4 |
5 | ## Run using docker
6 |
7 | ```bash
8 | docker-compose build --no-cache
9 | docker-compose up -d
10 | ```
11 |
12 | ## Usage
13 |
14 | Go to http://localhost:8081 and you should be redirected to http://localhost:8082 (CAS Server).
15 |
16 | Use casuser/Mellon as login/password.
17 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.1'
2 | services:
3 | cas:
4 | image: apereo/cas:6.6.1
5 | ports:
6 | - "8082:8080"
7 | volumes:
8 | - ./docker/cas.properties:/etc/cas/config/cas.properties:ro
9 | - ./docker/All-10000005.json:/etc/cas/services/All-10000005.json:ro
10 | healthcheck:
11 | test: ["CMD", "curl", "-f", "http://localhost:8080/cas"]
12 | interval: 1m30s
13 | timeout: 30s
14 | retries: 5
15 | app:
16 | build:
17 | context: ../
18 | dockerfile: cas-security-spring-boot-sample/Dockerfile
19 | image: cas-security-spring-boot-sample:1.1.0
20 | ports:
21 | - "8081:8081"
22 | - "5005"
23 | environment:
24 | - SPRING_PROFILES_ACTIVE=docker
25 | - JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
26 | depends_on:
27 | cas:
28 | condition: service_healthy
29 |
30 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/docker/All-10000005.json:
--------------------------------------------------------------------------------
1 | {
2 | "@class" : "org.apereo.cas.services.CasRegisteredService",
3 | "serviceId" : "^(https?|imaps?)://.*",
4 | "name" : "All",
5 | "id" : 10000005,
6 | "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
7 | "evaluationOrder" : 10000,
8 | "proxyPolicy" : {
9 | "@class" : "org.apereo.cas.services.RegexMatchingRegisteredServiceProxyPolicy",
10 | "pattern" : "^(https?|imaps?)://.*"
11 | }
12 | }
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/docker/cas.properties:
--------------------------------------------------------------------------------
1 | server.port=8080
2 | server.ssl.enabled=false
3 | cas.service-registry.core.init-from-json=true
4 | cas.service-registry.json.location=file:/etc/cas/services/
5 | cas.ticket.st.timeToKillInSeconds=60
6 | spring.cloud.config.enabled=false
7 | cas.events.core.enabled=false
8 | cas.events.core.track-configuration-modifications=false
9 | CasFeatureModule.FeatureCatalog.CasConfiguration.enabled=false
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 |
9 |
10 | org.springframework.boot
11 | spring-boot-starter-parent
12 | 2.6.9
13 |
14 |
15 |
16 | com.kakawait
17 | cas-security-spring-boot-sample
18 | 1.1.0
19 |
20 | Cas security spring boot sample
21 |
22 |
23 | UTF-8
24 | UTF-8
25 |
26 | 11
27 | 11
28 | 11
29 |
30 |
31 |
32 |
33 |
34 | com.kakawait
35 | cas-security-spring-boot-starter
36 | ${project.version}
37 |
38 |
39 |
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-web
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-security
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-actuator
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-starter-thymeleaf
56 |
57 |
58 | org.springframework.boot
59 | spring-boot-starter-test
60 | test
61 |
62 |
63 |
64 | com.kakawait
65 | cas-security-spring-boot-starter
66 |
67 |
68 |
69 |
70 |
71 |
72 | org.springframework.boot
73 | spring-boot-maven-plugin
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8081
3 |
4 | security:
5 | cas:
6 | authorization:
7 | mode: role
8 | roles: USER
9 | user:
10 | default-roles: USER
11 | proxy-validation:
12 | chains:
13 | - http://localhost:8180, http://localhost:8181
14 | - - http://localhost:8280
15 | - http://localhost:8281
16 | server:
17 | base-url: http://localhost:8080/cas/
18 | protocol-version: 3
19 | service:
20 | resolution-mode: dynamic
21 | ---
22 |
23 | spring:
24 | config:
25 | activate:
26 | on-profile: docker
27 |
28 | security:
29 | cas:
30 | server:
31 | # Browser/client base url, that uses external exposed port that why is 8082
32 | base-url: http://localhost:8082/cas/
33 | # Server-to-server base url, that uses internal docker network so with port mapping that why is 8080
34 | validation-base-url: http://cas:8080/cas/
35 | service:
36 | callback-base-url: http://app:8081/cas/
37 | paths:
38 | proxy-callback: /cas/proxy-callback
39 |
40 | logging:
41 | level:
42 | org.jasig.cas.client.validation: debug
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Index page
6 |
7 |
8 | Hello world!
9 |
10 | Principal object
11 | Proxy granting ticket object
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/src/main/resources/templates/logout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Logout page
6 |
7 |
8 | Do you want to log out of CAS?
9 | You have logged out of this application, but may still have an active single-sign on session with CAS.
10 | Logout of CAS
11 |
12 |
13 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/src/test/java/com/kakawait/sample/CasSecuritySpringBootSampleApplicationIT.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.sample;
2 |
3 | import com.kakawait.spring.security.cas.client.CasAuthorizationInterceptor;
4 | import org.junit.jupiter.api.DisplayName;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.ValueSource;
8 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
12 | import org.springframework.context.ApplicationContext;
13 | import org.springframework.core.Ordered;
14 | import org.springframework.web.client.RestTemplate;
15 | import org.springframework.web.filter.ForwardedHeaderFilter;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static com.kakawait.sample.CasSecuritySpringBootSampleApplication.*;
19 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
20 |
21 | @SpringBootTest
22 | abstract class CasSecuritySpringBootSampleApplicationIT {
23 |
24 | @Autowired
25 | protected ApplicationContext applicationContext;
26 |
27 | @Test
28 | void contextLoads(ApplicationContext context) {
29 | assertThat(context).isNotNull();
30 | }
31 |
32 | @Test
33 | void hasForwardedHeaderFilterBeanConfigured(ApplicationContext context) {
34 | FilterRegistrationBean forwardedHeaderFilter = (FilterRegistrationBean) context.getBean("forwardedHeaderFilter");
35 | assertThat(forwardedHeaderFilter).isNotNull();
36 |
37 | assertThat(forwardedHeaderFilter.getFilter()).isInstanceOf(ForwardedHeaderFilter.class);
38 | assertThat(forwardedHeaderFilter.getOrder()).isEqualTo(Ordered.HIGHEST_PRECEDENCE);
39 | }
40 |
41 | @Test
42 | void hasCasRestTemplateBeanConfigured(ApplicationContext context) {
43 | RestTemplate casRestTemplate = (RestTemplate) context.getBean("casRestTemplate");
44 | assertThat(casRestTemplate).isNotNull();
45 |
46 | assertThat(casRestTemplate.getInterceptors()).hasSize(1);
47 | assertThat(casRestTemplate.getInterceptors().get(0)).isInstanceOf(CasAuthorizationInterceptor.class);
48 | }
49 |
50 | @DisplayName("has static bean configured")
51 | @ParameterizedTest
52 | @ValueSource(classes = {SecurityConfiguration.class
53 | , OverrideDefaultCasSecurity.class
54 | , LogoutConfiguration.class
55 | , ApiSecurityConfiguration.class
56 | //, CustomLogoutConfiguration.class // In comment as custom-logout profile is NOT active
57 | //, WebMvcConfiguration.class // // In comment as custom-logout profile NOT active
58 | , BackwardSpringBoot1CasSecurityConfiguration.class
59 | , IndexController.class
60 | , HelloWorldController.class
61 | })
62 | void hasStaticBeanConfigured(Class clazz) {
63 | var staticBean = applicationContext.getBean(clazz);
64 | assertThat(staticBean).isNotNull();
65 | }
66 |
67 | @DisplayName("has noSuchBeanDefinitionException thrown")
68 | @ParameterizedTest
69 | @ValueSource(classes = {CustomLogoutConfiguration.class
70 | , WebMvcConfiguration.class
71 | })
72 | void noSuchBeanDefinitionExceptionIsThrown(Class clazz) {
73 | assertThatThrownBy(() -> applicationContext.getBean(clazz))
74 | .isInstanceOf(NoSuchBeanDefinitionException.class);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-sample/src/test/java/com/kakawait/sample/CasSecuritySpringBootSampleApplicationWithCustomLogoutProfileIT.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.sample;
2 |
3 | import org.junit.jupiter.api.DisplayName;
4 | import org.junit.jupiter.params.ParameterizedTest;
5 | import org.junit.jupiter.params.provider.ValueSource;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.test.context.ActiveProfiles;
8 |
9 | import static com.kakawait.sample.CasSecuritySpringBootSampleApplication.*;
10 |
11 | @SpringBootTest
12 | @ActiveProfiles("custom-logout")
13 | class CasSecuritySpringBootSampleApplicationWithCustomLogoutProfileIT extends CasSecuritySpringBootSampleApplicationIT {
14 |
15 | @DisplayName("has static bean for custom-logout profile configured")
16 | @ParameterizedTest
17 | @ValueSource(classes = {SecurityConfiguration.class
18 | , OverrideDefaultCasSecurity.class
19 | //, LogoutConfiguration.class // In comment as custom-login profile is NOT active
20 | , ApiSecurityConfiguration.class
21 | , CustomLogoutConfiguration.class
22 | , WebMvcConfiguration.class
23 | , BackwardSpringBoot1CasSecurityConfiguration.class
24 | , IndexController.class
25 | , HelloWorldController.class
26 | })
27 | void hasStaticBeanConfigured(Class clazz) {
28 | super.hasStaticBeanConfigured(clazz);
29 | }
30 |
31 | @DisplayName("has noSuchBeanDefinitionException thrown")
32 | @ParameterizedTest
33 | @ValueSource(classes = {LogoutConfiguration.class})
34 | void noSuchBeanDefinitionExceptionIsThrown(Class clazz) {
35 | super.noSuchBeanDefinitionExceptionIsThrown(clazz);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-starter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 |
9 |
10 | com.kakawait
11 | cas-security-spring-boot-parent
12 | 1.1.0
13 |
14 |
15 | cas-security-spring-boot-starter
16 | jar
17 |
18 | Cas security spring boot starter
19 | Spring boot starter for Apereo CAS client fully integrated with Spring security
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-autoconfigure
25 |
26 |
27 |
28 | com.kakawait
29 | cas-security-spring-boot-autoconfigure
30 |
31 |
32 | com.kakawait
33 | spring-security-cas-extension
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/cas-security-spring-boot-starter/src/main/resources/META-INF/spring.provides:
--------------------------------------------------------------------------------
1 | provides: cas-security
2 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | cas-security-spring-boot-autoconfigure:
5 | paths:
6 | - "cas-security-spring-boot-autoconfigure/"
7 | target: auto
8 | spring-security-cas-extension:
9 | paths:
10 | - "spring-security-cas-extension"
11 | target: auto
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Migwn, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 |
9 | com.kakawait
10 | cas-security-spring-boot-parent
11 | pom
12 | 1.1.0
13 |
14 | Cas security spring boot parent
15 | Spring boot starter for Apereo CAS client fully integrated with Spring security
16 | https://github.com/kakawait/cas-security-spring-boot-starter
17 |
18 |
19 | MIT License
20 | http://www.opensource.org/licenses/mit-license.php
21 | repo
22 |
23 |
24 |
25 |
26 |
27 | Thibaud Leprêtre
28 | thibaud.lepretre@gmail.com
29 |
30 |
31 |
32 |
33 | spring-security-cas-extension
34 | cas-security-spring-boot-starter
35 | cas-security-spring-boot-autoconfigure
36 |
37 |
38 |
39 | https://github.com/kakawait/cas-security-spring-boot-starter
40 | scm:git:git@github.com:kakawait/cas-security-spring-boot-starter.git
41 | scm:git:git@github.com:kakawait/cas-security-spring-boot-starter.git
42 |
43 |
44 |
45 | oss.sonatype.org
46 | Sonatype OSS Staging
47 | https://oss.sonatype.org/service/local/staging/deploy/maven2
48 | default
49 |
50 |
51 | oss.sonatype.org
52 | Sonatype OSS Snapshots
53 | https://oss.sonatype.org/content/repositories/snapshots
54 | default
55 |
56 |
57 |
58 |
59 | UTF-8
60 | UTF-8
61 |
62 | 11
63 | 11
64 | 11
65 |
66 |
67 | 2.6.9
68 |
69 |
70 | 1.18.24
71 |
72 |
73 | 3.15.0
74 | 3.3.3
75 |
76 |
77 | 0.8.5
78 | 3.2.1
79 | 3.2.0
80 | 1.6
81 | 1.6.13
82 |
83 |
84 |
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-dependencies
89 | ${spring-boot-dependencies.version}
90 | pom
91 | import
92 |
93 |
94 |
95 | com.kakawait
96 | cas-security-spring-boot-autoconfigure
97 | ${project.version}
98 |
99 |
100 |
101 | com.kakawait
102 | spring-security-cas-extension
103 | ${project.version}
104 |
105 |
106 |
107 | org.projectlombok
108 | lombok
109 | ${lombok.version}
110 |
111 |
112 | org.assertj
113 | assertj-core
114 | ${assertj-core.version}
115 |
116 |
117 | org.mockito
118 | mockito-core
119 | ${mockito-core.version}
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | org.jacoco
129 | jacoco-maven-plugin
130 | ${jacoco-maven-plugin.version}
131 |
132 |
133 |
134 | prepare-agent
135 |
136 |
137 |
138 | report
139 | test
140 |
141 | report
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | release
153 |
154 |
155 |
156 | org.apache.maven.plugins
157 | maven-source-plugin
158 | ${maven-source-plugin.version}
159 |
160 |
161 | attach-sources
162 | verify
163 |
164 | jar-no-fork
165 |
166 |
167 |
168 |
169 |
170 | org.apache.maven.plugins
171 | maven-javadoc-plugin
172 | ${maven-javadoc-plugin.version}
173 |
174 |
175 | attach-javadocs
176 | verify
177 |
178 | jar
179 |
180 |
181 |
182 |
183 |
184 | org.apache.maven.plugins
185 | maven-gpg-plugin
186 | ${maven-gpg-plugin.version}
187 |
188 |
189 | sign-artifacts
190 | verify
191 |
192 | sign
193 |
194 |
195 |
196 |
197 |
198 | org.sonatype.plugins
199 | nexus-staging-maven-plugin
200 | ${nexus-staging-maven-plugin.version}
201 | true
202 |
203 | oss.sonatype.org
204 | https://oss.sonatype.org/
205 | true
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [[ "$OSTYPE" == "darwin"* ]]; then
4 | sed() {
5 | gsed "$@"
6 | }
7 | date() {
8 | gdate "$@"
9 | }
10 | fi
11 |
12 | new_version="${1}"
13 |
14 | if [[ -z "${new_version}" ]]; then
15 | echo "Missing argument: version"
16 | exit 1
17 | fi
18 |
19 | current_version="$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)"
20 | echo "Current version is: ${current_version}"
21 | echo "New version will be: ${new_version}"
22 | echo
23 | read -p "Are you sure? " -r
24 | if [[ $REPLY =~ ^[Yy]$ ]]; then
25 | mvn versions:set "-DnewVersion=${new_version}" -DoldVersion=* -DgroupId=* -DartifactId=* -q -DforceStdout
26 | mvn -f cas-security-spring-boot-sample/pom.xml versions:set "-DnewVersion=${new_version}" -DoldVersion=* -DgroupId=* -DartifactId=* -q -DforceStdout
27 | mvn -f cas-security-spring-boot-sample/pom.xml versions:update-parent "-DparentVersion=${new_version}" -q -DforceStdout
28 | mvn -f spring-security-cas-extension/pom.xml versions:update-parent "-DparentVersion=${new_version}" -q -DforceStdout
29 | sed -i 's/\(cas-security-spring-boot-sample-\).*\.jar/\1'"${new_version}"'.jar/g' ./cas-security-spring-boot-sample/Dockerfile
30 | sed -i 's/\(image: cas-security-spring-boot-sample:\).*/\1'"${new_version}"'/g' ./cas-security-spring-boot-sample/docker-compose.yml
31 | sed -i 's/\(cas-security-spring-boot-starter%7C\).*%7Cjar/\1'"${new_version}"'%7Cjar/g' README.md
32 | sed -i 's/\(\).*\(<\/version>\)/\1'"${new_version}"'\2/g' README.md
33 | fi
34 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 |
9 |
10 | com.kakawait
11 | cas-security-spring-boot-parent
12 | 1.1.0
13 |
14 |
15 | spring-security-cas-extension
16 | jar
17 |
18 | Spring security cas extension
19 | Spring security cas extension and additional implementation used by the starter
20 |
21 |
22 | 3.1.0
23 | 3.0.2
24 |
25 |
26 |
27 |
28 |
29 | javax.servlet
30 | javax.servlet-api
31 | ${javax.servlet-api.version}
32 |
33 |
34 | com.google.code.findbugs
35 | jsr305
36 | ${jsr305.version}
37 |
38 |
39 |
40 |
41 |
42 | org.springframework.security
43 | spring-security-config
44 |
45 |
46 | org.springframework.security
47 | spring-security-cas
48 |
49 |
50 | org.springframework
51 | spring-webmvc
52 |
53 |
54 | javax.servlet
55 | javax.servlet-api
56 |
57 |
58 | com.google.code.findbugs
59 | jsr305
60 |
61 |
62 |
63 | org.springframework.boot
64 | spring-boot-starter-test
65 | test
66 |
67 |
68 |
69 | org.springframework.boot
70 | spring-boot-starter-logging
71 | test
72 |
73 |
74 | org.assertj
75 | assertj-core
76 | test
77 |
78 |
79 | org.mockito
80 | mockito-core
81 | test
82 |
83 |
84 |
85 |
86 |
87 |
88 | org.jacoco
89 | jacoco-maven-plugin
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/LaxServiceProperties.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas;
2 |
3 | import org.springframework.security.cas.ServiceProperties;
4 | import org.springframework.util.Assert;
5 |
6 | /**
7 | * @author Thibaud Leprêtre
8 | */
9 | public class LaxServiceProperties extends ServiceProperties {
10 |
11 | private final boolean dynamicServiceResolution;
12 |
13 | public LaxServiceProperties() {
14 | this(true);
15 | }
16 |
17 | public LaxServiceProperties(boolean dynamicServiceResolution) {
18 | this.dynamicServiceResolution = dynamicServiceResolution;
19 | }
20 |
21 | @Override
22 | public void afterPropertiesSet() {
23 | if (!dynamicServiceResolution) {
24 | try {
25 | super.afterPropertiesSet();
26 | } catch (RuntimeException e) {
27 | throw e;
28 | } catch (Exception e) {
29 | // Old version of spring security throw Exception for afterPropertiesSet()
30 | throw new RuntimeException(e);
31 | }
32 | } else {
33 | Assert.hasLength(getArtifactParameter(), "artifactParameter cannot be empty.");
34 | Assert.hasLength(getServiceParameter(), "serviceParameter cannot be empty.");
35 | }
36 | }
37 |
38 | public boolean isDynamicServiceResolution() {
39 | return dynamicServiceResolution;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/authentication/DynamicProxyCallbackUrlCasAuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.authentication;
2 |
3 | import com.kakawait.spring.security.cas.client.validation.ProxyCallbackUrlAwareTicketValidator;
4 | import com.kakawait.spring.security.cas.web.authentication.ProxyCallbackAndServiceAuthenticationDetails;
5 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
6 | import org.springframework.security.cas.authentication.CasAuthenticationProvider;
7 | import org.springframework.security.core.Authentication;
8 |
9 | /**
10 | * @author Thibaud Leprêtre
11 | */
12 | public class DynamicProxyCallbackUrlCasAuthenticationProvider extends CasAuthenticationProvider {
13 | @Override
14 | public Authentication authenticate(Authentication authentication) {
15 | if (authentication.getDetails() instanceof ProxyCallbackAndServiceAuthenticationDetails) {
16 | if (getTicketValidator() instanceof Cas20ServiceTicketValidator) {
17 | String proxyCallbackUrl =
18 | ((ProxyCallbackAndServiceAuthenticationDetails) authentication.getDetails()).getProxyCallbackUrl();
19 | ((Cas20ServiceTicketValidator) getTicketValidator()).setProxyCallbackUrl(proxyCallbackUrl);
20 | } else if (getTicketValidator() instanceof ProxyCallbackUrlAwareTicketValidator) {
21 | String proxyCallbackUrl =
22 | ((ProxyCallbackAndServiceAuthenticationDetails) authentication.getDetails()).getProxyCallbackUrl();
23 | ((ProxyCallbackUrlAwareTicketValidator) getTicketValidator()).setProxyCallbackUrl(proxyCallbackUrl);
24 | }
25 | }
26 | return super.authenticate(authentication);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/CasAuthorizationInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client;
2 |
3 | import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
4 | import org.springframework.http.HttpRequest;
5 | import org.springframework.http.client.ClientHttpRequestExecution;
6 | import org.springframework.http.client.ClientHttpRequestInterceptor;
7 | import org.springframework.http.client.ClientHttpResponse;
8 | import org.springframework.http.client.support.HttpRequestWrapper;
9 | import org.springframework.security.cas.ServiceProperties;
10 | import org.springframework.util.StringUtils;
11 | import org.springframework.web.util.UriComponentsBuilder;
12 |
13 | import java.io.IOException;
14 | import java.net.URI;
15 |
16 | /**
17 | * Implementation of {@link ClientHttpRequestInterceptor} to apply Proxy ticket query parameter.
18 | *
19 | * @see ProxyTicketProvider
20 | * @author Jonathan Coueraud
21 | * @author Thibaud Leprêtre
22 | * @since 0.7.0
23 | */
24 | public class CasAuthorizationInterceptor implements ClientHttpRequestInterceptor {
25 |
26 | private final ServiceProperties serviceProperties;
27 |
28 | private final ProxyTicketProvider proxyTicketProvider;
29 |
30 | public CasAuthorizationInterceptor(ServiceProperties serviceProperties,
31 | ProxyTicketProvider proxyTicketProvider) {
32 | this.serviceProperties = serviceProperties;
33 | this.proxyTicketProvider = proxyTicketProvider;
34 | }
35 |
36 | /**
37 | * {@inheritDoc}
38 | *
39 | * @throws IllegalStateException if proxy ticket retrieves from {@link ProxyTicketProvider#getProxyTicket(String)}
40 | * is null or blank
41 | */
42 | @Override
43 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
44 | throws IOException {
45 | String service = request.getURI().toASCIIString();
46 | String proxyTicket = proxyTicketProvider.getProxyTicket(service);
47 | if (!StringUtils.hasText(proxyTicket)) {
48 | throw new IllegalStateException(
49 | String.format("Proxy ticket provider returned a null proxy ticket for service %s.", service));
50 | }
51 | URI uri = UriComponentsBuilder
52 | .fromUri(request.getURI())
53 | .replaceQueryParam(serviceProperties.getArtifactParameter(), proxyTicket)
54 | .build(true).toUri();
55 | return execution.execute(new HttpRequestWrapper(request) {
56 | @Override
57 | public URI getURI() {
58 | return uri;
59 | }
60 | }, body);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/ticket/AttributePrincipalProxyTicketProvider.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.ticket;
2 |
3 | import com.kakawait.spring.security.cas.client.validation.AssertionProvider;
4 | import org.jasig.cas.client.authentication.AttributePrincipal;
5 | import org.jasig.cas.client.validation.Assertion;
6 | import org.springframework.util.Assert;
7 |
8 | /**
9 | * A standard implementation of {@link ProxyTicketProvider} that rely on
10 | * {@link AttributePrincipal#getProxyTicketFor(String)}.
11 | *
12 | * @see AssertionProvider
13 | * @author Jonathan Coueraud
14 | * @author Thibaud Leprêtre
15 | * @since 0.7.0
16 | */
17 | public class AttributePrincipalProxyTicketProvider implements ProxyTicketProvider {
18 | private static final String EXCEPTION_MESSAGE = "Unable to provide a proxy ticket with null %s";
19 |
20 | private final AssertionProvider assertionProvider;
21 |
22 | public AttributePrincipalProxyTicketProvider(AssertionProvider assertionProvider) {
23 | this.assertionProvider = assertionProvider;
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | *
29 | * @throws IllegalArgumentException if {@code service} is null or blank
30 | * @throws IllegalStateException if {@link Assertion} from {@link AssertionProvider#getAssertion()} is
31 | * {@code null} or {@link AttributePrincipal} from previous
32 | * {@link Assertion#getPrincipal()} is {@code null}.
33 | */
34 | @Override
35 | public String getProxyTicket(String service) {
36 | Assert.hasText(service, "service cannot not be null or blank");
37 | Assertion assertion = assertionProvider.getAssertion();
38 |
39 | AttributePrincipal principal = assertion.getPrincipal();
40 | if (principal == null) {
41 | throw new IllegalStateException(String.format(EXCEPTION_MESSAGE, AttributePrincipal.class.getSimpleName()));
42 | }
43 |
44 | return principal.getProxyTicketFor(service);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/ticket/ProxyTicketProvider.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.ticket;
2 |
3 | /**
4 | * Proxy ticker provider is simple interface that provides a way to get a CAS proxy ticket for a given service
5 | * and current bounded user.
6 | *
7 | * @author Jonathan Coueraud
8 | * @author Thibaud Leprêtre
9 | * @since 0.7.0
10 | */
11 | public interface ProxyTicketProvider {
12 |
13 | /**
14 | * Ask proxy ticket for a given service to CAS server.
15 | *
16 | * @param service service name or (mostly) service URL
17 | * @return the proxy ticket or {@code null} if CAS server won't be able to return us a proxy ticket for given
18 | * {@code service}
19 | */
20 | String getProxyTicket(String service);
21 | }
22 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/validation/AssertionProvider.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.validation;
2 |
3 | import org.jasig.cas.client.validation.Assertion;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | /**
8 | * Assertion provider is simple interface that provides a way to get the current (user bounded) {@link Assertion}.
9 | *
10 | * @author Jonathan Coueraud
11 | * @author Thibaud Leprêtre
12 | * @since 0.7.0
13 | */
14 | public interface AssertionProvider {
15 |
16 | /**
17 | * Retrieve current request {@link Assertion}.
18 | * @return the current request {@link Assertion}.
19 | */
20 | @Nonnull
21 | Assertion getAssertion();
22 | }
23 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/validation/ProxyCallbackUrlAwareTicketValidator.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.validation;
2 |
3 | /**
4 | * @author Thibaud Leprêtre
5 | */
6 | public interface ProxyCallbackUrlAwareTicketValidator {
7 | void setProxyCallbackUrl(String proxyCallbackUrl);
8 | }
9 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/client/validation/SecurityContextHolderAssertionProvider.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.validation;
2 |
3 | import org.jasig.cas.client.validation.Assertion;
4 | import org.springframework.security.cas.authentication.CasAuthenticationToken;
5 | import org.springframework.security.core.context.SecurityContext;
6 | import org.springframework.security.core.context.SecurityContextHolder;
7 |
8 | import java.util.Optional;
9 |
10 | import javax.annotation.Nonnull;
11 |
12 | /**
13 | * A standard implementation that rely on {@link SecurityContextHolder} to retrieve current request
14 | * {@link CasAuthenticationToken} that contains {@link Assertion} through {@link CasAuthenticationToken#getAssertion()}.
15 | *
16 | * @author Jonathan Coueraud
17 | * @author Thibaud Leprêtre
18 | * @see SecurityContextHolder
19 | * @see CasAuthenticationToken
20 | * @since 0.7.0
21 | */
22 | public class SecurityContextHolderAssertionProvider implements AssertionProvider {
23 |
24 | /**
25 | * {@inheritDoc}
26 | *
27 | * @throws IllegalStateException if current {@link org.springframework.security.core.Authentication} retrieve from
28 | * {@link SecurityContext#getAuthentication()} is not instance of
29 | * {@link CasAuthenticationToken}.
30 | */
31 | @Nonnull
32 | @Override
33 | public Assertion getAssertion() {
34 | // @formatter:off
35 | return ((CasAuthenticationToken) Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
36 | .filter(a -> a instanceof CasAuthenticationToken)
37 | .orElseThrow(() -> new IllegalStateException(
38 | String.format("Unable to provide an %s with null or non %s authentication",
39 | Assertion.class.getSimpleName(),
40 | CasAuthenticationToken.class.getCanonicalName()))))
41 | .getAssertion();
42 | // @formatter:on
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/userdetails/GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.userdetails;
2 |
3 | import org.jasig.cas.client.validation.Assertion;
4 | import org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService;
5 | import org.springframework.security.core.GrantedAuthority;
6 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
7 | import org.springframework.security.core.userdetails.User;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
10 | import org.springframework.util.StringUtils;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.Collection;
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.Objects;
18 | import java.util.stream.Collectors;
19 | import java.util.stream.Stream;
20 |
21 | /**
22 | * @author Thibaud Leprêtre
23 | */
24 | public class GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService
25 | extends AbstractCasAssertionUserDetailsService {
26 |
27 | private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
28 |
29 | private final String[] attributes;
30 |
31 | private final Collection extends GrantedAuthority> defaultGrantedAuthorities;
32 |
33 | private boolean toUppercase = true;
34 |
35 | public GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService(String[] attributes,
36 | Collection extends GrantedAuthority> defaultGrantedAuthorities) {
37 | this.attributes = (attributes == null) ? new String[0] : attributes;
38 | this.defaultGrantedAuthorities = (defaultGrantedAuthorities == null) ? new ArrayList<>()
39 | : defaultGrantedAuthorities;
40 | }
41 |
42 | protected UserDetails loadUserDetails(Assertion assertion) {
43 | String username = assertion.getPrincipal().getName();
44 | if (!StringUtils.hasText(username)) {
45 | throw new UsernameNotFoundException("Unable to retrieve username from CAS assertion");
46 | }
47 |
48 | Map principalAttributes = assertion.getPrincipal().getAttributes();
49 | List authorities = Arrays
50 | .stream(attributes)
51 | .map(principalAttributes::get)
52 | .filter(Objects::nonNull)
53 | .flatMap(v -> (v instanceof Collection) ? ((Collection>) v).stream() : Stream.of(v))
54 | .map(v -> toUppercase ? v.toString().toUpperCase() : v.toString())
55 | .map(r -> r.replaceFirst("^ROLE_", ""))
56 | .map(r -> new SimpleGrantedAuthority("ROLE_" + r))
57 | .collect(Collectors.toList());
58 |
59 | authorities.addAll(defaultGrantedAuthorities);
60 |
61 | return new User(username, NON_EXISTENT_PASSWORD_VALUE, authorities);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/RequestAwareCasAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web;
2 |
3 | import org.jasig.cas.client.util.CommonUtils;
4 | import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
5 | import org.springframework.util.Assert;
6 |
7 | import java.net.URI;
8 | import java.util.Optional;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 |
13 | import static org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromContextPath;
14 |
15 | /**
16 | * @author Thibaud Leprêtre
17 | */
18 | public class RequestAwareCasAuthenticationEntryPoint extends CasAuthenticationEntryPoint {
19 |
20 | @SuppressWarnings("WeakerAccess")
21 | protected final URI loginPath;
22 |
23 | public RequestAwareCasAuthenticationEntryPoint(URI loginPath) {
24 | Assert.notNull(loginPath, "login path is required, it must not be null");
25 | this.loginPath = loginPath;
26 | }
27 |
28 | @Override
29 | public void afterPropertiesSet() {
30 | Assert.hasLength(getLoginUrl(), "loginUrl must be specified");
31 | Assert.notNull(getServiceProperties(), "serviceProperties must be specified");
32 | }
33 |
34 | @Override
35 | protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) {
36 | String serviceUrl = buildUrl(request, loginPath).orElse(loginPath.toASCIIString());
37 | return CommonUtils.constructServiceUrl(null, response, serviceUrl, null,
38 | getServiceProperties().getServiceParameter(), getServiceProperties().getArtifactParameter(), true);
39 | }
40 |
41 | @SuppressWarnings("WeakerAccess")
42 | protected static Optional buildUrl(HttpServletRequest request, URI path) {
43 | Assert.notNull(request, "request is required; it must not be null");
44 | if (!path.isAbsolute()) {
45 | return Optional.of(fromContextPath(request).path(path.toASCIIString()).toUriString());
46 | }
47 | return Optional.empty();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/authentication/CasLogoutSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.security.cas.ServiceProperties;
6 | import org.springframework.security.core.Authentication;
7 | import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
8 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
9 | import org.springframework.web.util.UriComponentsBuilder;
10 |
11 | import java.io.IOException;
12 | import java.io.UnsupportedEncodingException;
13 | import java.net.URI;
14 | import java.net.URLEncoder;
15 |
16 | import javax.servlet.ServletException;
17 | import javax.servlet.http.HttpServletRequest;
18 | import javax.servlet.http.HttpServletResponse;
19 |
20 | import static java.net.URLEncoder.encode;
21 | import static java.nio.charset.StandardCharsets.UTF_8;
22 |
23 | /**
24 | * @author Thibaud Leprêtre
25 | */
26 | public class CasLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
27 | implements LogoutSuccessHandler {
28 |
29 | private static final Logger logger = LoggerFactory.getLogger(CasLogoutSuccessHandler.class);
30 |
31 | protected final URI casLogout;
32 |
33 | protected final ServiceProperties serviceProperties;
34 |
35 | public CasLogoutSuccessHandler(URI casLogout, ServiceProperties serviceProperties) {
36 | this.casLogout = casLogout;
37 | this.serviceProperties = serviceProperties;
38 | }
39 |
40 | @Override
41 | public void onLogoutSuccess(HttpServletRequest httpServletRequest,
42 | HttpServletResponse httpServletResponse, Authentication authentication)
43 | throws IOException, ServletException {
44 | super.handle(httpServletRequest, httpServletResponse, authentication);
45 | }
46 |
47 | @Override
48 | protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
49 | UriComponentsBuilder builder = UriComponentsBuilder.fromUri(casLogout);
50 | addLogoutServiceParameter(builder, serviceProperties.getService());
51 | return builder.build().toUriString();
52 | }
53 |
54 | protected void addLogoutServiceParameter(UriComponentsBuilder builder, String service) {
55 | if (service != null) {
56 | try {
57 | builder.replaceQueryParam(serviceProperties.getServiceParameter(), encode(service, UTF_8.toString()));
58 | } catch (UnsupportedEncodingException e) {
59 | logger.error("Unable to encode service url {}", service, e);
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/authentication/DefaultProxyCallbackAndServiceAuthenticationDetails.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import org.springframework.security.cas.ServiceProperties;
4 | import org.springframework.security.web.util.UrlUtils;
5 | import org.springframework.util.StringUtils;
6 | import org.springframework.web.util.UriComponentsBuilder;
7 |
8 | import java.net.URI;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 |
12 | import static org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromContextPath;
13 |
14 | /**
15 | * @author Thibaud Leprêtre
16 | */
17 | public class DefaultProxyCallbackAndServiceAuthenticationDetails
18 | implements ProxyCallbackAndServiceAuthenticationDetails {
19 |
20 | private static final long serialVersionUID = -88469969834244098L;
21 |
22 | private final transient ServiceProperties serviceProperties;
23 |
24 | private final URI proxyCallbackUri;
25 |
26 | protected transient HttpServletRequest context;
27 |
28 | public DefaultProxyCallbackAndServiceAuthenticationDetails(ServiceProperties serviceProperties, URI proxyCallbackUri) {
29 | this.serviceProperties = serviceProperties;
30 | this.proxyCallbackUri = proxyCallbackUri;
31 | }
32 |
33 | @Override
34 | public void setContext(HttpServletRequest context) {
35 | this.context = context;
36 | }
37 |
38 | @Override
39 | public String getProxyCallbackUrl() {
40 | if (proxyCallbackUri == null) {
41 | return null;
42 | }
43 | if (proxyCallbackUri.isAbsolute()) {
44 | return proxyCallbackUri.toASCIIString();
45 | }
46 | return fromContextPath(context).path(proxyCallbackUri.toASCIIString()).toUriString();
47 | }
48 |
49 | @Override
50 | public String getServiceUrl() {
51 | String query = removeArtifactParameterFromQueryString(context.getQueryString());
52 | return UrlUtils.buildFullRequestUrl(context.getScheme(), context.getServerName(),
53 | context.getServerPort(), context.getRequestURI(), StringUtils.hasText(query) ? query : null);
54 | }
55 |
56 | private String removeArtifactParameterFromQueryString(String queryString) {
57 | return UriComponentsBuilder
58 | .newInstance()
59 | .query(queryString)
60 | .replaceQueryParam(serviceProperties.getArtifactParameter(), new Object[0])
61 | .build()
62 | .toString()
63 | .replaceFirst("^\\?", "");
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/authentication/ProxyCallbackAndServiceAuthenticationDetails.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 |
7 | /**
8 | * @author Thibaud Leprêtre
9 | */
10 | public interface ProxyCallbackAndServiceAuthenticationDetails extends ServiceAuthenticationDetails {
11 | String getProxyCallbackUrl();
12 |
13 | void setContext(HttpServletRequest context);
14 | }
15 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/authentication/ProxyCallbackAndServiceAuthenticationDetailsSource.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import org.springframework.security.cas.ServiceProperties;
4 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
5 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
6 |
7 | import java.net.URI;
8 |
9 | import javax.servlet.http.HttpServletRequest;
10 |
11 | /**
12 | * @author Thibaud Leprêtre
13 | */
14 | public class ProxyCallbackAndServiceAuthenticationDetailsSource extends ServiceAuthenticationDetailsSource {
15 |
16 | private final ProxyCallbackAndServiceAuthenticationDetails serviceAuthenticationDetails;
17 |
18 | public ProxyCallbackAndServiceAuthenticationDetailsSource(ServiceProperties serviceProperties,
19 | ProxyCallbackAndServiceAuthenticationDetails serviceAuthenticationDetails) {
20 | super(serviceProperties);
21 | this.serviceAuthenticationDetails = serviceAuthenticationDetails;
22 | }
23 |
24 | public ProxyCallbackAndServiceAuthenticationDetailsSource(ServiceProperties serviceProperties,
25 | URI proxyCallbackUri) {
26 | super(serviceProperties);
27 | serviceAuthenticationDetails =
28 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties, proxyCallbackUri);
29 | }
30 |
31 | @Override
32 | public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
33 | serviceAuthenticationDetails.setContext(context);
34 | return serviceAuthenticationDetails;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/main/java/com/kakawait/spring/security/cas/web/authentication/RequestAwareCasLogoutSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import org.springframework.security.cas.ServiceProperties;
4 | import org.springframework.web.util.UriComponentsBuilder;
5 |
6 | import java.net.URI;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 |
11 | import static org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromContextPath;
12 |
13 | /**
14 | * @author Thibaud Leprêtre
15 | */
16 | public class RequestAwareCasLogoutSuccessHandler extends CasLogoutSuccessHandler {
17 |
18 | public RequestAwareCasLogoutSuccessHandler(URI casLogout, ServiceProperties serviceProperties) {
19 | super(casLogout, serviceProperties);
20 | }
21 |
22 | @Override
23 | protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
24 | UriComponentsBuilder builder = UriComponentsBuilder.fromUri(casLogout);
25 | addLogoutServiceParameter(builder, fromContextPath(request).build().toUriString());
26 | return builder.build().toUriString();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/LaxServicePropertiesTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas;
2 |
3 |
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.security.cas.ServiceProperties;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
9 | import static org.junit.jupiter.api.Assertions.assertThrows;
10 |
11 | /**
12 | * @author Thibaud Leprêtre
13 | */
14 | public class LaxServicePropertiesTest {
15 |
16 | @Test
17 | public void afterPropertiesSet_WithDynamicServiceResolutionAndNullOrEmptyService_Ok() {
18 | LaxServiceProperties serviceProperties = new LaxServiceProperties(true);
19 | serviceProperties.afterPropertiesSet();
20 | assertThat(serviceProperties.isDynamicServiceResolution()).isTrue();
21 | }
22 |
23 | @Test
24 | public void afterPropertiesSet_WithoutDynamicServiceResolutionValueAndNullOrEmptyService_IllegalArgumentException() {
25 | LaxServiceProperties serviceProperties = new LaxServiceProperties(false);
26 | assertThrows(IllegalArgumentException.class, serviceProperties::afterPropertiesSet);
27 | }
28 |
29 | @Test
30 | public void afterPropertiesSet_WithNullService_NoException() {
31 | ServiceProperties serviceProperties = new LaxServiceProperties();
32 | serviceProperties.setService(null);
33 | serviceProperties.afterPropertiesSet();
34 |
35 | assertThat(serviceProperties.getService()).isNull();
36 | }
37 |
38 | @Test
39 | public void afterPropertiesSet_WithNullOrEmptyArtifactParameter_IllegalArgumentException() {
40 | LaxServiceProperties serviceProperties = new LaxServiceProperties();
41 | serviceProperties.setArtifactParameter(null);
42 |
43 | assertThatThrownBy(serviceProperties::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
44 |
45 | serviceProperties.setArtifactParameter("");
46 | assertThatThrownBy(serviceProperties::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
47 | }
48 |
49 | @Test
50 | public void afterPropertiesSet_WithNullOrEmptyServiceParameter_IllegalArgumentException() {
51 | LaxServiceProperties serviceProperties = new LaxServiceProperties();
52 | serviceProperties.setServiceParameter(null);
53 |
54 | assertThatThrownBy(serviceProperties::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
55 |
56 | serviceProperties.setServiceParameter("");
57 | assertThatThrownBy(serviceProperties::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/authentication/DynamicProxyCallbackUrlCasAuthenticationProviderTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.authentication;
2 |
3 | import com.kakawait.spring.security.cas.web.authentication.ProxyCallbackAndServiceAuthenticationDetails;
4 | import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
5 | import org.jasig.cas.client.validation.TicketValidator;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.Mock;
9 | import org.mockito.junit.jupiter.MockitoExtension;
10 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
11 | import org.springframework.security.core.Authentication;
12 |
13 | import javax.servlet.http.HttpServletRequest;
14 |
15 | import static org.mockito.ArgumentMatchers.anyString;
16 | import static org.mockito.ArgumentMatchers.eq;
17 | import static org.mockito.Mockito.doNothing;
18 | import static org.mockito.Mockito.mock;
19 | import static org.mockito.Mockito.times;
20 | import static org.mockito.Mockito.verify;
21 | import static org.mockito.Mockito.verifyNoInteractions;
22 | import static org.mockito.Mockito.when;
23 |
24 | /**
25 | * @author Thibaud Leprêtre
26 | */
27 | @ExtendWith(MockitoExtension.class)
28 | public class DynamicProxyCallbackUrlCasAuthenticationProviderTest {
29 |
30 | @Mock
31 | private Authentication authentication;
32 |
33 | @Test
34 | public void authenticate_WithNullDetails_DoNothing() {
35 | TicketValidator ticketValidator = mock(TicketValidator.class);
36 |
37 | DynamicProxyCallbackUrlCasAuthenticationProvider authenticationProvider =
38 | new DynamicProxyCallbackUrlCasAuthenticationProvider();
39 | authenticationProvider.setTicketValidator(ticketValidator);
40 |
41 | when(authentication.getDetails()).thenReturn(null);
42 |
43 | authenticationProvider.authenticate(authentication);
44 |
45 | verify(authentication, times(1)).getDetails();
46 | verifyNoInteractions(ticketValidator);
47 | }
48 |
49 | @Test
50 | public void authenticate_WithDetailsNotInstanceOfProxyCallbackAndServiceAuthenticationDetails_DoNothing() {
51 | TicketValidator ticketValidator = mock(TicketValidator.class);
52 |
53 | DynamicProxyCallbackUrlCasAuthenticationProvider authenticationProvider =
54 | new DynamicProxyCallbackUrlCasAuthenticationProvider();
55 | authenticationProvider.setTicketValidator(ticketValidator);
56 |
57 | when(authentication.getDetails()).thenReturn((ServiceAuthenticationDetails) () -> "http://localhost");
58 |
59 | authenticationProvider.authenticate(authentication);
60 |
61 | verify(authentication, times(1)).getDetails();
62 | verifyNoInteractions(ticketValidator);
63 | }
64 |
65 | @Test
66 | public void authenticate_WithNullTicketValidator_DoNothing() {
67 | DynamicProxyCallbackUrlCasAuthenticationProvider authenticationProvider =
68 | new DynamicProxyCallbackUrlCasAuthenticationProvider();
69 | authenticationProvider.setTicketValidator(null);
70 |
71 | when(authentication.getDetails()).thenReturn(new ProxyCallbackAndServiceAuthenticationDetails() {
72 | private static final long serialVersionUID = 2171667909966987793L;
73 |
74 | @Override
75 | public String getServiceUrl() {
76 | return "http://localhost";
77 | }
78 |
79 | @Override
80 | public String getProxyCallbackUrl() {
81 | return "http://localhost/cas/callback";
82 | }
83 |
84 | @Override
85 | public void setContext(HttpServletRequest context) {
86 | // do nothing
87 | }
88 | });
89 |
90 | authenticationProvider.authenticate(authentication);
91 |
92 | verify(authentication, times(1)).getDetails();
93 | }
94 |
95 | @Test
96 | public void authenticate_WithTicketValidatorNotInstanceOfCas20ServiceTicketValidator_DoNothing() {
97 | TicketValidator ticketValidator = mock(TicketValidator.class);
98 |
99 | DynamicProxyCallbackUrlCasAuthenticationProvider authenticationProvider =
100 | new DynamicProxyCallbackUrlCasAuthenticationProvider();
101 | authenticationProvider.setTicketValidator(ticketValidator);
102 |
103 | when(authentication.getDetails()).thenReturn(new ProxyCallbackAndServiceAuthenticationDetails() {
104 | private static final long serialVersionUID = 700055871632490815L;
105 |
106 | @Override
107 | public String getServiceUrl() {
108 | return "http://localhost";
109 | }
110 |
111 | @Override
112 | public String getProxyCallbackUrl() {
113 | return "http://localhost/cas/callback";
114 | }
115 |
116 | @Override
117 | public void setContext(HttpServletRequest context) {
118 | // do nothing
119 | }
120 | });
121 |
122 | authenticationProvider.authenticate(authentication);
123 |
124 | verify(authentication, times(1)).getDetails();
125 | verifyNoInteractions(ticketValidator);
126 | }
127 |
128 | @Test
129 | public void authenticate_EveryRequirements_TicketValidatorProxyCallbackUrlUpdated() {
130 | Cas20ServiceTicketValidator ticketValidator = mock(Cas20ServiceTicketValidator.class);
131 |
132 | DynamicProxyCallbackUrlCasAuthenticationProvider authenticationProvider =
133 | new DynamicProxyCallbackUrlCasAuthenticationProvider();
134 | authenticationProvider.setTicketValidator(ticketValidator);
135 |
136 | when(authentication.getDetails()).thenReturn(new ProxyCallbackAndServiceAuthenticationDetails() {
137 | private static final long serialVersionUID = -541835714542292545L;
138 |
139 | @Override
140 | public String getServiceUrl() {
141 | return "http://localhost";
142 | }
143 |
144 | @Override
145 | public String getProxyCallbackUrl() {
146 | return "http://localhost/cas/callback";
147 | }
148 |
149 | @Override
150 | public void setContext(HttpServletRequest context) {
151 | // do nothing
152 | }
153 | });
154 | doNothing().when(ticketValidator).setProxyCallbackUrl(anyString());
155 |
156 | authenticationProvider.authenticate(authentication);
157 |
158 | verify(authentication, times(2)).getDetails();
159 | verify(ticketValidator, times(1)).setProxyCallbackUrl(eq("http://localhost/cas/callback"));
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/client/CasAuthorizationInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client;
2 |
3 | import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.api.extension.ExtendWith;
7 | import org.mockito.ArgumentCaptor;
8 | import org.mockito.Captor;
9 | import org.mockito.Mock;
10 | import org.mockito.junit.jupiter.MockitoExtension;
11 | import org.springframework.http.HttpMethod;
12 | import org.springframework.http.HttpRequest;
13 | import org.springframework.http.client.ClientHttpRequest;
14 | import org.springframework.http.client.ClientHttpRequestExecution;
15 | import org.springframework.http.client.ClientHttpRequestInterceptor;
16 | import org.springframework.mock.http.client.MockClientHttpRequest;
17 | import org.springframework.security.cas.ServiceProperties;
18 |
19 | import java.io.IOException;
20 | import java.net.URI;
21 |
22 | import static java.lang.String.format;
23 | import static org.assertj.core.api.Assertions.assertThat;
24 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
25 | import static org.mockito.ArgumentMatchers.isNull;
26 | import static org.mockito.Mockito.mock;
27 | import static org.mockito.Mockito.never;
28 | import static org.mockito.Mockito.times;
29 | import static org.mockito.Mockito.verify;
30 | import static org.mockito.Mockito.when;
31 |
32 | /**
33 | * @author Thibaud Leprêtre
34 | */
35 | @ExtendWith(MockitoExtension.class)
36 | public class CasAuthorizationInterceptorTest {
37 |
38 | @Mock
39 | private ProxyTicketProvider proxyTicketProvider;
40 |
41 | private ClientHttpRequestInterceptor casAuthorizationInterceptor;
42 |
43 | @Captor
44 | private ArgumentCaptor httpRequestArgumentCaptor;
45 |
46 | @BeforeEach
47 | public void setUp() {
48 | ServiceProperties serviceProperties = new ServiceProperties();
49 | serviceProperties.setService("http://localhost:8080/");
50 | casAuthorizationInterceptor = new CasAuthorizationInterceptor(serviceProperties, proxyTicketProvider);
51 | }
52 |
53 | @Test
54 | public void intercept_NullProxyTicket_IllegalStateException() throws IOException {
55 | String service = "http://httpbin.org/get";
56 |
57 | ClientHttpRequestExecution clientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
58 | ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create(service));
59 |
60 | when(proxyTicketProvider.getProxyTicket(service)).thenReturn(null);
61 |
62 | assertThatThrownBy(() -> casAuthorizationInterceptor.intercept(request, null, clientHttpRequestExecution))
63 | .isInstanceOf(IllegalStateException.class)
64 | .hasMessage(format("Proxy ticket provider returned a null proxy ticket for service %s.", service));
65 |
66 | verify(proxyTicketProvider, times(1)).getProxyTicket(service);
67 | verify(clientHttpRequestExecution, never()).execute(request, null);
68 | }
69 |
70 | @Test
71 | public void intercept_BasicService_ProxyTicketAsQueryParameter() throws IOException {
72 | String service = "http://httpbin.org/get";
73 |
74 | ClientHttpRequestExecution clientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
75 | ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create(service));
76 |
77 | String proxyTicket = "PT-21-c1gk6jBcfYnatLbNExfx-0623277bc36a";
78 | when(proxyTicketProvider.getProxyTicket(service)).thenReturn(proxyTicket);
79 |
80 | casAuthorizationInterceptor.intercept(request, null, clientHttpRequestExecution);
81 |
82 | verify(proxyTicketProvider, times(1)).getProxyTicket(service);
83 | verify(clientHttpRequestExecution, times(1)).execute(httpRequestArgumentCaptor.capture(), isNull());
84 |
85 | assertThat(httpRequestArgumentCaptor.getValue().getURI().toASCIIString())
86 | .isEqualTo(service + "?ticket=" + proxyTicket);
87 | }
88 |
89 | @Test
90 | public void intercept_ServiceWithExistingQueryParameters_ProxyTicketAsQueryParameter() throws IOException {
91 | String service = "http://httpbin.org/get?a=test&x=test2";
92 |
93 | ClientHttpRequestExecution clientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
94 | ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create(service));
95 |
96 | String proxyTicket = "PT-21-c1gk6jBcfYnatLbNExfx-0623277bc36a";
97 | when(proxyTicketProvider.getProxyTicket(service)).thenReturn(proxyTicket);
98 |
99 | casAuthorizationInterceptor.intercept(request, null, clientHttpRequestExecution);
100 |
101 | verify(proxyTicketProvider, times(1)).getProxyTicket(service);
102 | verify(clientHttpRequestExecution, times(1)).execute(httpRequestArgumentCaptor.capture(), isNull());
103 |
104 | assertThat(httpRequestArgumentCaptor.getValue().getURI().toASCIIString())
105 | .isEqualTo(service + "&ticket=" + proxyTicket);
106 | }
107 |
108 | @Test
109 | public void intercept_ServiceWithExistingQueryParameters_ProxyTicketAsQueryEscapedParameter() throws IOException {
110 | String service = "http://httpbin.org/get?a=test&x=test2&c=%22test3%22";
111 |
112 | ClientHttpRequestExecution clientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
113 | ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create(service));
114 |
115 | String proxyTicket = "PT-21-c1gk6jBcfYnatLbNExfx-0623277bc36a";
116 | when(proxyTicketProvider.getProxyTicket(service)).thenReturn(proxyTicket);
117 |
118 | casAuthorizationInterceptor.intercept(request, null, clientHttpRequestExecution);
119 |
120 | verify(proxyTicketProvider, times(1)).getProxyTicket(service);
121 | verify(clientHttpRequestExecution, times(1)).execute(httpRequestArgumentCaptor.capture(), isNull());
122 |
123 | assertThat(httpRequestArgumentCaptor.getValue().getURI().toASCIIString())
124 | .isEqualTo(service + "&ticket=" + proxyTicket);
125 | }
126 |
127 | @Test
128 | public void intercept_ServiceWithConflictQueryParameter_QueryParameterOverride() throws IOException {
129 | String service = "http://httpbin.org/get?ticket=bob";
130 |
131 | ClientHttpRequestExecution clientHttpRequestExecution = mock(ClientHttpRequestExecution.class);
132 | ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create(service));
133 |
134 | String proxyTicket = "PT-21-c1gk6jBcfYnatLbNExfx-0623277bc36a";
135 | when(proxyTicketProvider.getProxyTicket(service)).thenReturn(proxyTicket);
136 |
137 | casAuthorizationInterceptor.intercept(request, null, clientHttpRequestExecution);
138 |
139 | verify(proxyTicketProvider, times(1)).getProxyTicket(service);
140 | verify(clientHttpRequestExecution, times(1)).execute(httpRequestArgumentCaptor.capture(), isNull());
141 |
142 | assertThat(httpRequestArgumentCaptor.getValue().getURI().toASCIIString())
143 | .isEqualTo("http://httpbin.org/get?ticket=" + proxyTicket);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/client/ticket/AttributePrincipalProxyTicketProviderTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.ticket;
2 |
3 | import com.kakawait.spring.security.cas.client.validation.AssertionProvider;
4 | import org.jasig.cas.client.authentication.AttributePrincipal;
5 | import org.jasig.cas.client.validation.Assertion;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.Mock;
10 | import org.mockito.junit.jupiter.MockitoExtension;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
14 | import static org.mockito.Mockito.mock;
15 | import static org.mockito.Mockito.times;
16 | import static org.mockito.Mockito.verify;
17 | import static org.mockito.Mockito.when;
18 |
19 | /**
20 | * @author Thibaud Leprêtre
21 | */
22 | @ExtendWith(MockitoExtension.class)
23 | public class AttributePrincipalProxyTicketProviderTest {
24 |
25 | @Mock
26 | private AssertionProvider assertionProvider;
27 |
28 | private ProxyTicketProvider proxyTicketProvider;
29 |
30 | @BeforeEach
31 | public void setUp() {
32 | proxyTicketProvider = new AttributePrincipalProxyTicketProvider(assertionProvider);
33 | }
34 |
35 | @Test
36 | public void getProxyTicket_NullAttributePrincipal_IllegalStateException() {
37 | String service = "http://httpbin.org/get";
38 |
39 | Assertion assertion = mock(Assertion.class);
40 | when(assertionProvider.getAssertion()).thenReturn(assertion);
41 | when(assertion.getPrincipal()).thenReturn(null);
42 |
43 | assertThatThrownBy(() -> proxyTicketProvider.getProxyTicket(service))
44 | .isInstanceOf(IllegalStateException.class)
45 | .hasMessage("Unable to provide a proxy ticket with null %s", AttributePrincipal.class.getSimpleName());
46 |
47 | verify(assertionProvider, times(1)).getAssertion();
48 | verify(assertion, times(1)).getPrincipal();
49 | }
50 |
51 | @Test
52 | public void getProxyTicket_ValidService_ProxyTicketValue() {
53 | String service = "http://httpbin.org/get";
54 |
55 | Assertion assertion = mock(Assertion.class);
56 | AttributePrincipal attributePrincipal = mock(AttributePrincipal.class);
57 | when(assertionProvider.getAssertion()).thenReturn(assertion);
58 | when(assertion.getPrincipal()).thenReturn(attributePrincipal);
59 | when(attributePrincipal.getProxyTicketFor(service)).thenReturn("ST-21-c1gk6jBcfYnatLbNExfx-0623277bc36a");
60 |
61 | assertThat(proxyTicketProvider.getProxyTicket(service)).isEqualTo("ST-21-c1gk6jBcfYnatLbNExfx-0623277bc36a");
62 |
63 | verify(assertionProvider, times(1)).getAssertion();
64 | verify(assertion, times(1)).getPrincipal();
65 | verify(attributePrincipal, times(1)).getProxyTicketFor(service);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/client/validation/SecurityContextHolderAssertionProviderTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.client.validation;
2 |
3 | import org.jasig.cas.client.validation.AssertionImpl;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6 | import org.springframework.security.cas.authentication.CasAuthenticationToken;
7 | import org.springframework.security.core.Authentication;
8 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
9 | import org.springframework.security.core.context.SecurityContextHolder;
10 | import org.springframework.security.core.userdetails.User;
11 |
12 | import java.util.Collections;
13 | import java.util.Set;
14 | import java.util.UUID;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.junit.jupiter.api.Assertions.assertThrows;
18 |
19 | /**
20 | * @author Thibaud Leprêtre
21 | */
22 | public class SecurityContextHolderAssertionProviderTest {
23 |
24 | @Test
25 | public void getAssertion_NullAuthentication_IllegalStateException() {
26 | SecurityContextHolder.getContext().setAuthentication(null);
27 |
28 | AssertionProvider provider = new SecurityContextHolderAssertionProvider();
29 | assertThrows(IllegalStateException.class, provider::getAssertion);
30 | }
31 |
32 | @Test
33 | public void getAssertion_UnwantedAuthenticationType_IllegalStateException() {
34 | Authentication authentication = new UsernamePasswordAuthenticationToken("casuser", "Mellon");
35 | SecurityContextHolder.getContext().setAuthentication(authentication);
36 |
37 | AssertionProvider provider = new SecurityContextHolderAssertionProvider();
38 | assertThrows(IllegalStateException.class, provider::getAssertion);
39 | }
40 |
41 | @Test
42 | public void getAssertion_ValidSecurityContextHolder_Assertion() {
43 | String user = "casuser";
44 | String ticket = "ST-21-c1gk6jBcfYnatLbNExfx-0623277bc36a";
45 | Set roles = Collections.singleton(new SimpleGrantedAuthority("MEMBER"));
46 |
47 | CasAuthenticationToken authentication = new CasAuthenticationToken(UUID.randomUUID().toString(), user,
48 | ticket, roles, new User(user, ticket, roles), new AssertionImpl(user));
49 | SecurityContextHolder.getContext().setAuthentication(authentication);
50 |
51 | AssertionProvider provider = new SecurityContextHolderAssertionProvider();
52 | assertThat(provider.getAssertion()).isNotNull();
53 | assertThat(provider.getAssertion().getPrincipal().getName()).isEqualTo(user);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/userdetails/GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.userdetails;
2 |
3 | import org.jasig.cas.client.authentication.AttributePrincipal;
4 | import org.jasig.cas.client.validation.Assertion;
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.jupiter.api.extension.ExtendWith;
7 | import org.mockito.Mock;
8 | import org.mockito.junit.jupiter.MockitoExtension;
9 | import org.springframework.security.core.GrantedAuthority;
10 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
11 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collection;
15 | import java.util.Collections;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
21 | import static org.mockito.Mockito.reset;
22 | import static org.mockito.Mockito.times;
23 | import static org.mockito.Mockito.verify;
24 | import static org.mockito.Mockito.when;
25 |
26 | /**
27 | * @author Thibaud Leprêtre
28 | */
29 | @ExtendWith(MockitoExtension.class)
30 | public class GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsServiceTest {
31 |
32 | private static final Collection extends GrantedAuthority> DEFAULT_ROLES = Collections.unmodifiableCollection(
33 | new ArrayList() {{
34 | add(new SimpleGrantedAuthority("ROLE_USER"));
35 | }});
36 |
37 | private static final Map ATTRIBUTES = Collections.unmodifiableMap(new HashMap() {{
38 | put("foo", "bar");
39 | put("role", "role_member");
40 | put("group", "admin");
41 | }});
42 |
43 | @Mock
44 | private Assertion assertion;
45 |
46 | @Mock
47 | private AttributePrincipal principal;
48 |
49 | @Test
50 | public void loadUserDetails_NullOrBlankPrincipalName_UsernameNotFoundException() {
51 | // JDK10 var needed :)
52 | GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService userDetailsService =
53 | new GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService(new String[]{"role"},
54 | DEFAULT_ROLES);
55 |
56 | when(assertion.getPrincipal()).thenReturn(principal);
57 | when(principal.getName()).thenReturn(null);
58 | assertThatThrownBy(() -> userDetailsService.loadUserDetails(assertion))
59 | .isInstanceOf(UsernameNotFoundException.class);
60 | verify(assertion, times(1)).getPrincipal();
61 | verify(principal, times(1)).getName();
62 |
63 | reset(assertion, principal);
64 | when(assertion.getPrincipal()).thenReturn(principal);
65 | when(principal.getName()).thenReturn("");
66 | assertThatThrownBy(() -> userDetailsService.loadUserDetails(assertion))
67 | .isInstanceOf(UsernameNotFoundException.class);
68 | verify(assertion, times(1)).getPrincipal();
69 | verify(principal, times(1)).getName();
70 |
71 | reset(assertion, principal);
72 | when(assertion.getPrincipal()).thenReturn(principal);
73 | when(principal.getName()).thenReturn(" ");
74 | assertThatThrownBy(() -> userDetailsService.loadUserDetails(assertion))
75 | .isInstanceOf(UsernameNotFoundException.class);
76 | verify(assertion, times(1)).getPrincipal();
77 | verify(principal, times(1)).getName();
78 | }
79 |
80 | @Test
81 | public void loadUserDetails_WithoutAttributes_OnlyDefaultRoles() {
82 | // JDK10 var needed :)
83 | GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService userDetailsService =
84 | new GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService(null, DEFAULT_ROLES);
85 |
86 | when(assertion.getPrincipal()).thenReturn(principal);
87 | when(principal.getName()).thenReturn("JohnWick");
88 |
89 | assertThat(userDetailsService.loadUserDetails(assertion).getAuthorities())
90 | .extracting("authority", String.class)
91 | .containsExactly("ROLE_USER");
92 |
93 | verify(assertion, times(2)).getPrincipal();
94 | verify(principal, times(1)).getName();
95 | verify(principal, times(1)).getAttributes();
96 | }
97 |
98 | @Test
99 | public void loadUserDetails_WithNonMatchingAttributes_OnlyDefaultRoles() {
100 | // JDK10 var needed :)
101 | GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService userDetailsService =
102 | new GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService(
103 | new String[]{"doesNotExists"}, DEFAULT_ROLES);
104 |
105 | when(assertion.getPrincipal()).thenReturn(principal);
106 | when(principal.getName()).thenReturn("JohnWick");
107 | when(principal.getAttributes()).thenReturn(ATTRIBUTES);
108 |
109 | assertThat(userDetailsService.loadUserDetails(assertion).getAuthorities())
110 | .extracting("authority", String.class)
111 | .containsExactly("ROLE_USER");
112 |
113 | verify(assertion, times(2)).getPrincipal();
114 | verify(principal, times(1)).getName();
115 | verify(principal, times(1)).getAttributes();
116 | }
117 |
118 | @Test
119 | public void loadUserDetails_WithMatchingAttributes_MergedRoles() {
120 | // JDK10 var needed :)
121 | GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService userDetailsService =
122 | new GrantedAuthoritiesFromAssertionAttributesWithDefaultRolesUserDetailsService(
123 | new String[]{"role", "group"}, DEFAULT_ROLES);
124 |
125 | when(assertion.getPrincipal()).thenReturn(principal);
126 | when(principal.getName()).thenReturn("JohnWick");
127 | when(principal.getAttributes()).thenReturn(ATTRIBUTES);
128 |
129 | assertThat(userDetailsService.loadUserDetails(assertion).getAuthorities())
130 | .extracting("authority", String.class)
131 | .containsExactlyInAnyOrder("ROLE_USER", "ROLE_MEMBER", "ROLE_ADMIN");
132 |
133 | verify(assertion, times(2)).getPrincipal();
134 | verify(principal, times(1)).getName();
135 | verify(principal, times(1)).getAttributes();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/web/RequestAwareCasAuthenticationEntryPointTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web;
2 |
3 | import com.kakawait.spring.security.cas.LaxServiceProperties;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.http.HttpMethod;
6 | import org.springframework.mock.web.MockHttpServletRequest;
7 | import org.springframework.mock.web.MockHttpServletResponse;
8 |
9 | import java.net.URI;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
13 |
14 | /**
15 | * @author Thibaud Leprêtre
16 | */
17 | public class RequestAwareCasAuthenticationEntryPointTest {
18 |
19 | private static final String CAS_SERVER_LOGIN_URL = "http://cas.server.com/cas/login";
20 |
21 | @Test
22 | public void constructor_WithNullLoginPath_IllegalArgumentException() {
23 | assertThatThrownBy(() -> new RequestAwareCasAuthenticationEntryPoint(null))
24 | .isInstanceOf(IllegalArgumentException.class);
25 | }
26 |
27 | @Test
28 | public void afterPropertiesSet_WithNullOrEmptyLoginUrl_IllegalArgumentException() {
29 | RequestAwareCasAuthenticationEntryPoint entryPoint =
30 | new RequestAwareCasAuthenticationEntryPoint(URI.create("/"));
31 | entryPoint.setLoginUrl(null);
32 |
33 | assertThatThrownBy(entryPoint::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
34 |
35 | entryPoint.setLoginUrl("");
36 | assertThatThrownBy(entryPoint::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
37 | }
38 |
39 | @Test
40 | public void afterPropertiesSet_WithNullLoginUrl_IllegalArgumentException() {
41 | RequestAwareCasAuthenticationEntryPoint entryPoint =
42 | new RequestAwareCasAuthenticationEntryPoint(URI.create("/"));
43 | entryPoint.setServiceProperties(null);
44 |
45 | assertThatThrownBy(entryPoint::afterPropertiesSet).isInstanceOf(IllegalArgumentException.class);
46 | }
47 |
48 | @Test
49 | public void createServiceUrl_AbsoluteUrlAsLoginPath_NoTransformation() {
50 | String loginPath = "http://localhost/my/custom/login/path";
51 | RequestAwareCasAuthenticationEntryPoint entryPoint =
52 | new RequestAwareCasAuthenticationEntryPoint(URI.create(loginPath));
53 | entryPoint.setLoginUrl(CAS_SERVER_LOGIN_URL);
54 | entryPoint.setServiceProperties(new LaxServiceProperties());
55 | entryPoint.afterPropertiesSet();
56 |
57 | MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/john/wick");
58 |
59 | String serviceUrl = entryPoint.createServiceUrl(request, new MockHttpServletResponse());
60 |
61 | assertThat(serviceUrl).isNotBlank().isEqualTo(loginPath);
62 | }
63 |
64 | @Test
65 | public void createServiceUrl_WithoutContextPath_AppendToBaseUrl() {
66 | String loginPath = "/my/custom/login/path";
67 | RequestAwareCasAuthenticationEntryPoint entryPoint =
68 | new RequestAwareCasAuthenticationEntryPoint(URI.create(loginPath));
69 | entryPoint.setLoginUrl(CAS_SERVER_LOGIN_URL);
70 | entryPoint.setServiceProperties(new LaxServiceProperties());
71 | entryPoint.afterPropertiesSet();
72 |
73 | MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/john/wick");
74 |
75 | String serviceUrl = entryPoint.createServiceUrl(request, new MockHttpServletResponse());
76 |
77 | assertThat(serviceUrl).isNotBlank().isEqualTo("http://localhost" + loginPath);
78 | }
79 |
80 | @Test
81 | public void createServiceUrl_WithContextPath_AppendToBaseUrl() {
82 | String loginPath = "/my/custom/login/path";
83 | RequestAwareCasAuthenticationEntryPoint entryPoint =
84 | new RequestAwareCasAuthenticationEntryPoint(URI.create(loginPath));
85 | entryPoint.setLoginUrl(CAS_SERVER_LOGIN_URL);
86 | entryPoint.setServiceProperties(new LaxServiceProperties());
87 | entryPoint.afterPropertiesSet();
88 |
89 | MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/john/wick");
90 | request.setContextPath("/john");
91 |
92 | String serviceUrl = entryPoint.createServiceUrl(request, new MockHttpServletResponse());
93 |
94 | assertThat(serviceUrl).isNotBlank().isEqualTo("http://localhost/john" + loginPath);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/web/authentication/CasLogoutSuccessHandlerTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import com.kakawait.spring.security.cas.LaxServiceProperties;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.mock.web.MockHttpServletRequest;
7 | import org.springframework.mock.web.MockHttpServletResponse;
8 | import org.springframework.security.cas.ServiceProperties;
9 |
10 | import java.io.IOException;
11 | import java.net.URI;
12 | import java.net.URLEncoder;
13 | import java.nio.charset.StandardCharsets;
14 |
15 | import javax.servlet.ServletException;
16 |
17 | import static java.net.URLEncoder.*;
18 | import static java.nio.charset.StandardCharsets.*;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | /**
22 | * @author Thibaud Leprêtre
23 | */
24 | public class CasLogoutSuccessHandlerTest {
25 |
26 | private static final URI casLogout = URI.create("http://cas.server/cas/logout");
27 |
28 | private MockHttpServletRequest request;
29 |
30 | private MockHttpServletResponse response;
31 |
32 | @BeforeEach
33 | public void setUp() {
34 | request = new MockHttpServletRequest();
35 | response = new MockHttpServletResponse();
36 | }
37 |
38 | @Test
39 | public void onLogoutSuccess_WithService_ServiceAsQueryParameterValue()
40 | throws IOException, ServletException {
41 | ServiceProperties serviceProperties = new ServiceProperties();
42 | serviceProperties.setService("http://localhost/john/wick?foo=a&bar=b");
43 | CasLogoutSuccessHandler logoutSuccessHandler = new CasLogoutSuccessHandler(casLogout, serviceProperties);
44 | logoutSuccessHandler.onLogoutSuccess(request, response, null);
45 |
46 | String service = encode(serviceProperties.getService(), UTF_8.toString());
47 | assertThat(response.getRedirectedUrl())
48 | .isEqualTo(casLogout.toASCIIString() + "?service=" + service);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/web/authentication/DefaultProxyCallbackAndServiceAuthenticationDetailsTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import com.kakawait.spring.security.cas.LaxServiceProperties;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.mock.web.MockHttpServletRequest;
7 | import org.springframework.security.cas.ServiceProperties;
8 |
9 | import java.net.URI;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | /**
14 | * @author Thibaud Leprêtre
15 | */
16 | public class DefaultProxyCallbackAndServiceAuthenticationDetailsTest {
17 |
18 | private ServiceProperties serviceProperties;
19 |
20 | private MockHttpServletRequest request;
21 |
22 | @BeforeEach
23 | public void setUp() {
24 | serviceProperties = new LaxServiceProperties();
25 | request = new MockHttpServletRequest();
26 | }
27 |
28 | @Test
29 | public void getProxyCallbackUrl_NullProxyCallbackUri_Null() {
30 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
31 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties, null);
32 | authenticationDetails.setContext(request);
33 |
34 | assertThat(authenticationDetails.getProxyCallbackUrl()).isNull();
35 | }
36 |
37 | @Test
38 | public void getProxyCallbackUrl_AbsoluteProxyCallbackUri_NoTransformation() {
39 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
40 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties,
41 | URI.create("http://localhost/cas/callback"));
42 | authenticationDetails.setContext(request);
43 |
44 | assertThat(authenticationDetails.getProxyCallbackUrl()).isEqualTo("http://localhost/cas/callback");
45 | }
46 |
47 | @Test
48 | public void getProxyCallbackUrl_ProxyCallbackUriAndWithoutContextPath_AppendToBaseUrl() {
49 | request.setRequestURI("/john/wick");
50 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
51 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties,
52 | URI.create("/cas/callback"));
53 | authenticationDetails.setContext(request);
54 |
55 | assertThat(authenticationDetails.getProxyCallbackUrl()).isEqualTo("http://localhost/cas/callback");
56 | }
57 |
58 | @Test
59 | public void getProxyCallbackUrl_ProxyCallbackUriAndWithContextPath_AppendToBaseUrl() {
60 | request.setContextPath("/john");
61 | request.setRequestURI("/john/wick");
62 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
63 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties,
64 | URI.create("/cas/callback"));
65 | authenticationDetails.setContext(request);
66 |
67 | assertThat(authenticationDetails.getProxyCallbackUrl()).isEqualTo("http://localhost/john/cas/callback");
68 | }
69 |
70 | @Test
71 | public void getServiceUrl_WithoutArtifactParameterQueryString_HttpServletRequestUrl() {
72 | request.setQueryString("foo=a&bar=b");
73 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
74 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties,
75 | URI.create("/cas/callback"));
76 | authenticationDetails.setContext(request);
77 |
78 | assertThat(authenticationDetails.getServiceUrl()).isEqualTo("http://localhost?foo=a&bar=b");
79 | }
80 |
81 | @Test
82 | public void getServiceUrl_WithArtifactParameterQueryString_CleanQueryString() {
83 | request.setQueryString("foo=a&bar=b&"
84 | + serviceProperties.getArtifactParameter() + "=ST-21-c1gk6jBcfYnatLbNExfx-0623277bc36a");
85 | ProxyCallbackAndServiceAuthenticationDetails authenticationDetails =
86 | new DefaultProxyCallbackAndServiceAuthenticationDetails(serviceProperties,
87 | URI.create("/cas/callback"));
88 | authenticationDetails.setContext(request);
89 |
90 | assertThat(authenticationDetails.getServiceUrl()).isEqualTo("http://localhost?foo=a&bar=b");
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/web/authentication/ProxyCallbackAndServiceAuthenticationDetailsSourceTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import com.kakawait.spring.security.cas.LaxServiceProperties;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.mock.web.MockHttpServletRequest;
6 | import org.springframework.security.cas.ServiceProperties;
7 | import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
8 |
9 | import java.net.URI;
10 |
11 | import javax.servlet.http.HttpServletRequest;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | /**
16 | * @author Thibaud Leprêtre
17 | */
18 | public class ProxyCallbackAndServiceAuthenticationDetailsSourceTest {
19 |
20 | @Test
21 | public void buildDetails_WithUri_DefaultProxyCallbackAndServiceAuthenticationDetails() {
22 | ServiceProperties serviceProperties = new LaxServiceProperties();
23 | ProxyCallbackAndServiceAuthenticationDetailsSource authenticationDetailsSource =
24 | new ProxyCallbackAndServiceAuthenticationDetailsSource(serviceProperties, URI.create("/cas/callback"));
25 |
26 | MockHttpServletRequest context = new MockHttpServletRequest();
27 |
28 | ServiceAuthenticationDetails serviceAuthenticationDetails = authenticationDetailsSource.buildDetails(context);
29 | assertThat(serviceAuthenticationDetails)
30 | .isInstanceOf(DefaultProxyCallbackAndServiceAuthenticationDetails.class);
31 | assertThat(serviceAuthenticationDetails.getServiceUrl()).isEqualTo("http://localhost");
32 | assertThat(((ProxyCallbackAndServiceAuthenticationDetails) serviceAuthenticationDetails).getProxyCallbackUrl())
33 | .isEqualTo("http://localhost/cas/callback");
34 | }
35 |
36 | @Test
37 | public void buildDetails_WithCustomProxyCallbackAndServiceAuthenticationDetails_DelegateTo() {
38 | ServiceProperties serviceProperties = new LaxServiceProperties();
39 | ProxyCallbackAndServiceAuthenticationDetailsSource authenticationDetailsSource =
40 | new ProxyCallbackAndServiceAuthenticationDetailsSource(serviceProperties,
41 | new ProxyCallbackAndServiceAuthenticationDetails() {
42 | private static final long serialVersionUID = 197549373788141292L;
43 |
44 | @Override
45 | public String getProxyCallbackUrl() {
46 | return "http://bat.man/callback";
47 | }
48 |
49 | @Override
50 | public void setContext(HttpServletRequest context) {
51 | // do nothing
52 | }
53 |
54 | @Override
55 | public String getServiceUrl() {
56 | return "http://bat.man/";
57 | }
58 | });
59 |
60 | ServiceAuthenticationDetails serviceAuthenticationDetails =
61 | authenticationDetailsSource.buildDetails(new MockHttpServletRequest());
62 | assertThat(serviceAuthenticationDetails).isInstanceOf(ProxyCallbackAndServiceAuthenticationDetails.class);
63 | assertThat(serviceAuthenticationDetails.getServiceUrl()).isEqualTo("http://bat.man/");
64 | assertThat(((ProxyCallbackAndServiceAuthenticationDetails) serviceAuthenticationDetails).getProxyCallbackUrl())
65 | .isEqualTo("http://bat.man/callback");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/java/com/kakawait/spring/security/cas/web/authentication/RequestAwareCasLogoutSuccessHandlerTest.java:
--------------------------------------------------------------------------------
1 | package com.kakawait.spring.security.cas.web.authentication;
2 |
3 | import com.kakawait.spring.security.cas.LaxServiceProperties;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.mock.web.MockHttpServletRequest;
7 | import org.springframework.mock.web.MockHttpServletResponse;
8 |
9 | import java.io.IOException;
10 | import java.net.URI;
11 |
12 | import javax.servlet.ServletException;
13 |
14 | import static java.net.URLEncoder.encode;
15 | import static java.nio.charset.StandardCharsets.UTF_8;
16 | import static org.assertj.core.api.Assertions.assertThat;
17 |
18 | /**
19 | * @author Thibaud Leprêtre
20 | */
21 | public class RequestAwareCasLogoutSuccessHandlerTest {
22 |
23 | private static final URI casLogout = URI.create("http://cas.server/cas/logout");
24 |
25 | private MockHttpServletRequest request;
26 |
27 | private MockHttpServletResponse response;
28 |
29 | @BeforeEach
30 | public void setUp() {
31 | request = new MockHttpServletRequest();
32 | response = new MockHttpServletResponse();
33 | }
34 |
35 | @Test
36 | public void onLogoutSuccess_WithService_UseHttpServletRequestAsService()
37 | throws IOException, ServletException {
38 | LaxServiceProperties serviceProperties = new LaxServiceProperties();
39 | CasLogoutSuccessHandler logoutSuccessHandler = new RequestAwareCasLogoutSuccessHandler(casLogout,
40 | serviceProperties);
41 | logoutSuccessHandler.onLogoutSuccess(request, response, null);
42 |
43 | String service = encode(request.getRequestURL().toString(), UTF_8.toString());
44 | assertThat(response.getRedirectedUrl())
45 | .isEqualTo(casLogout.toASCIIString() + "?service=" + service);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/spring-security-cas-extension/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
2 |
--------------------------------------------------------------------------------