How to use CAS SSO with JHipster - java

I genereted an application using JHipster and I would like to use my instance of jasig CAS as SSO with my application instead of default form login that comes with the app. Ultimately I would like to use custom CAS parameters to assign authorities.
I followed this example. Now I have the app generated with JHipster which I successfully connected to my CAS. When I type localhost:8080/app/login it redirects me to my CAS instance, authenticates me and redirects back to JHipster app but JHipster doesn't let me browse the secured part of my the app and still requires authentication. Below is my SecurityConfiguration.java. Can somone provide any insight as to how proceed from here?
package com.my.company.application.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import pl.edu.uw.dsk.konferator.security.*;
import pl.edu.uw.dsk.konferator.web.filter.CsrfCookieGeneratorFilter;
import javax.inject.Inject;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String CAS_URL_LOGIN = "cas.url.login";
private static final String CAS_URL_LOGOUT = "cas.url.logout";
private static final String CAS_URL_PREFIX = "cas.url.prefix";
private static final String CAS_SERVICE_URL = "app.service.security";
private static final String CAS_CALLBACK = "/auth/cas";
#Inject
private Environment env;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
/*#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;*/
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties sp = new ServiceProperties();
sp.setService(env.getRequiredProperty(CAS_SERVICE_URL));
sp.setSendRenew(false);
return sp;
}
#Bean
public SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler = new SimpleUrlAuthenticationSuccessHandler();
authenticationSuccessHandler.setDefaultTargetUrl("/");
authenticationSuccessHandler.setTargetUrlParameter("spring-security-redirect");
return authenticationSuccessHandler;
}
#Bean
public RememberCasAuthenticationProvider casAuthenticationProvider() {
RememberCasAuthenticationProvider casAuthenticationProvider = new RememberCasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(userDetailsService);
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("CAS_AUTHENTICATION_PROVIDER");
return casAuthenticationProvider;
}
#Bean
public SessionAuthenticationStrategy sessionStrategy() {
SessionFixationProtectionStrategy sessionStrategy = new SessionFixationProtectionStrategy();
sessionStrategy.setMigrateSessionAttributes(false);
//sessionStrategy.setRetainedAttributes(Arrays.asList("CALLBACKURL"));
return sessionStrategy;
}
/*
#Bean
public Saml11TicketValidator casSamlServiceTicketValidator() {
return new Saml11TicketValidator(env.getRequiredProperty(CAS_URL_PREFIX));
}
*/
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_PREFIX));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setAuthenticationDetailsSource(new RememberWebAuthenticationDetailsSource());
casAuthenticationFilter.setSessionAuthenticationStrategy(sessionStrategy());
casAuthenticationFilter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler);
casAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
casAuthenticationFilter.setFilterProcessesUrl(CAS_CALLBACK);
// casAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new
// AntPathRequestMatcher("/login", "GET"));
return casAuthenticationFilter;
}
#Bean
public RememberCasAuthenticationEntryPoint casAuthenticationEntryPoint() {
RememberCasAuthenticationEntryPoint casAuthenticationEntryPoint = new RememberCasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
//move to /app/login due to cachebuster instead of api/authenticate
casAuthenticationEntryPoint.setPathLogin("/app/login");
return casAuthenticationEntryPoint;
}
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(env.getRequiredProperty(CAS_URL_PREFIX));
return singleSignOutFilter;
}
#Bean
public LogoutFilter requestCasGlobalLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(env.getRequiredProperty(CAS_URL_LOGOUT) + "?service="
+ env.getRequiredProperty(CAS_SERVICE_URL), new SecurityContextLogoutHandler());
// logoutFilter.setFilterProcessesUrl("/logout");
// logoutFilter.setFilterProcessesUrl("/j_spring_cas_security_logout");
logoutFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/api/logout", "POST"));
return logoutFilter;
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.addFilterBefore(casAuthenticationFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilterBefore(requestCasGlobalLogoutFilter(), LogoutFilter.class)
.exceptionHandling()
.authenticationEntryPoint(casAuthenticationEntryPoint())
// .and()
// .rememberMe()
// .rememberMeServices(rememberMeServices)
// .key(env.getProperty("jhipster.security.rememberme.key"))
// .and()
// .formLogin()
// .loginProcessingUrl("/api/authentication")
// .successHandler(ajaxAuthenticationSuccessHandler)
// .failureHandler(ajaxAuthenticationFailureHandler)
// .usernameParameter("j_username")
// .passwordParameter("j_password")
// .permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/app/**").authenticated()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset_password/init").permitAll()
.antMatchers("/api/account/reset_password/finish").permitAll()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/audits/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/mappings/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/liquibase/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/configuration/security").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
public GlobalSecurityConfiguration() {
super();
}
}
}

Jhipste doesn't know that you are authenticated, thats the reason it does not let you browse secure pages. If you look closely in your principal.service.js and auth.service.js, they are calling the api/account rest service in AccountResource, and based on this angular determins if you are authenticated or not. So that means we need to return an User from this rest service.
Once you logged in with cas the current user info can be obtained from the AccounResource Rest service. Below is my AccountResource
/**
* GET /account -> get the current user.
*/
#RequestMapping(value = "/account",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<UserDTO> getAccount() {
UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<String> roles = new ArrayList<>();
for (GrantedAuthority authority : userDetails.getGrantedAuthorities()) {
roles.add(authority.getAuthorigy());
}
return new ResponseEntity<>(
new UserDTO(
user.getLogin(),
null,
null,
null,
null,
null,
roles),
HttpStatus.OK);
}
As you can see above, i am getting authentication(casauthentication) token from security context. I have refactored my AccountResource to remove all the reference to user repository as i am no longer using the database to store user info.
Hope this helps.

Related

Is there a way to configure password encoder for default password?

I use overridden values for spring security username and password. Following properties are in my application.properties.
spring.security.user.name=myUser
spring.security.user.password=myPassword
spring.security.user.roles=admin
I would like to encrypt the password value as follows:
spring.security.user.name=myUser
spring.security.user.password={bcrypt}hashedpassword valuevalue
spring.security.user.roles=admin
I added PasswordEncoder in my SpringConfig:
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
In some example I noticed that there is for AuthenitcationManagerBuilder but I do not know what datasource should be used. What else do I need to use encrypted password for default user?
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
Adding my Spring Security config as a reference:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/custom").hasRole("admin")
.anyRequest().authenticated()
.and()
.httpBasic();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
Grabbing:
$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
for input:
hello
... from (e.g. thx): https://bcrypt-generator.com/
With:
spring-starter (web, security),
application.properties:
spring.security.user.password={bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
and any (rest)controller:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// #Bean
// public PasswordEncoder encoder() {
// return new BCryptPasswordEncoder();
// }
#GetMapping("secured")
public String secured() {
return "Hello Admin";
}
}
and without additional config,
we can login with user=user and password=hello.
Adding:
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
breaks this behavior!! To "fix it", we can (application.properties):
spring.security.user.password=$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
.. omit the "tag" {bcrypt}! ...
Responsible for "tags"/capable of several algorithms is the DelegatingPasswordEncoder, which is auto configured (via [AuthenticationConfiguration|HttpSecurityConfiguration].LazyPasswordEncoder ..if no other PasswordEncoder bean found) and uses
PasswordEncoderFactories behind the scenes, like (currently) this:
#SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2#SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt#SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256",
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2#SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
EDIT:
No, also introducing:
#Configuration
class WebSecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests().requestMatchers("/secured").hasRole("admin")
.anyRequest()
.authenticated()
.and()
.httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
}
Plus:
spring.security.user.roles=admin
Doesn't change my observations:
When "custom password encoder"
and $2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
can login with user:hello (and view /secured response)
with default/-out password encoder
and {bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
can login with user:hello (and view /secured response)
(it only switches from form to basic login:)
Test (good/app config):
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WebMvcTest
class WithAppConfigTest {
#Autowired
MockMvc mockMvc;
#Test
void expectAccess() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "hello"))
).andExpectAll(
authenticated(),
status().isOk(),
content().string("Hello Admin"));
}
#Test
void expectUnauthorized() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "wrong"))
).andExpectAll(
unauthenticated(),
status().isUnauthorized(),
content().bytes(new byte[0]));
}
}
Test forbidden:
#WebMvcTest(properties = "spring.security.user.roles=foo,bar,baz")
class WithFakeConfigTest {
#Autowired
MockMvc mockMvc;
#Test
void expectForbidden() throws Exception {
mockMvc.perform(
get("/secured")
.with(httpBasic("user", "hello"))
).andExpectAll(
authenticated(), // !, but:
status().isForbidden(),
content().bytes(new byte[0]));
}
}

How to make Spring security to redirect user to the original requested page after successfully authenticated by the CAS server

I have a spring boot RESTFul web application, which uses CAS server for Enterprise Single Sign-On. If a user, who is not logged-in, tries to access a secure page, that user is redirected to the CAS server for authentication. On successful authentication, the user is redirected to the home page of spring boot RESTFul web application - not the secured page, the user tries to access. How can we directly redirect the user to the secure page, which the user wants to access after successful login?
The spring-security-cas-client is used to implement CAS authentication. An AuthenticationSuccessHandler is implemented to set UseReferer true. The spring security config class is as follows:
package com.example.app
import java.util.Arrays;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationProvider authenticationProvider;
private AuthenticationEntryPoint authenticationEntryPoint;
private SingleSignOutFilter singleSignOutFilter;
private LogoutFilter logoutFilter;
#Autowired
public SecurityConfig(CasAuthenticationProvider casAuthenticationProvider,
AuthenticationEntryPoint
eP,
LogoutFilter lF
, SingleSignOutFilter ssF
) {
this.authenticationProvider = casAuthenticationProvider;
this.authenticationEntryPoint = eP;
this.logoutFilter = lF;
this.singleSignOutFilter = ssF;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**", "/path-1", "/path-2")
.authenticated()
.and()
.authorizeRequests()
.regexMatchers("/")
.permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout().logoutSuccessUrl("/logout")
.and()
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
.addFilterBefore(logoutFilter, LogoutFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Arrays.asList(authenticationProvider));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter(ServiceProperties sP) throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setServiceProperties(sP);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Bean
public AuthenticationSuccessHandler successHandler() {
SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
handler.setUseReferer(true);
return handler;
}
}
Using of SavedRequestAwareAuthenticationSuccessHandler instead of SimpleUrlAuthenticationSuccessHandler might help you achieve the redirection.
#Bean
public AuthenticationSuccessHandler successHandler() {
SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
handler.setTargetUrlParameter("redirectTo");
handler.setDefaultTargetUrl("/");
return handler;
}

Spring boot OAuth2Client, user not authenticated after callback: SecurityContext is empty or contents are anonymous

I'm configuring a Spring Boot application that will authenticate the user using OAuth2 and OpenID Connect. For the implementation, I follow the reference here: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2
After the AuthorizationCode is done, the user is not authenticated. How do I authenticate the user ?
The OpenID server I rely on requires an extra parameter for the authorization endpoint (acr_values={value}).
I'm able to add the parameter using an AuthorizationRequestResolver and call the authorization endpoint, then the server redirects me on my callback but the user is not authenticated. From the trace, the SecurityContext is empty and not save in the httpsession.
I can see in the logs that the POST request to the token endpoint is sent and I get a response 200.
MySecurityConfig
test
package com.uta.security.edc.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import com.uta.security.edc.oauth2.MyAuthorizationRequestResolver;
import com.uta.security.edc.oauth2.MyTokenResponseConverter;
#Configuration
#EnableOAuth2Client
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientRegistrationRepository clientRegistrationRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/connect/**", "/test/**", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.loginPage("/connect/login")
.authorizationEndpoint()
.baseUri("/connect/authorization")
.authorizationRequestResolver(this.authorizationRequestResolver())
.and()
.redirectionEndpoint()
.baseUri("/connect/callback")
.and()
.tokenEndpoint()
.accessTokenResponseClient(this.accessTokenResponseClient())
.and()
.userInfoEndpoint()
.and()
.defaultSuccessUrl("/")
.failureUrl("/connect/loginFailure")
.and()
.oauth2Client();
}
#Bean
public MyAuthorizationRequestResolver authorizationRequestResolver()
{
return new MyAuthorizationRequestResolver(this.clientRegistrationRepository);
}
#Bean
public OAuth2RestTemplate oauth2RestTemplate()
{
return new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-connect");
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
List<String> scopes = new ArrayList<String>(1);
scopes.add("uta-poc-edc");
resource.setAccessTokenUri(clientRegistration.getProviderDetails().getTokenUri());
resource.setClientId(clientRegistration.getClientId());
resource.setClientSecret(clientRegistration.getClientSecret());
resource.setScope(scopes);
return resource;
}
#Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient()
{
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
tokenResponseHttpMessageConverter.setTokenResponseConverter(new MyTokenResponseConverter());
RestTemplate restTemplate = this.oauth2RestTemplate();
//RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
accessTokenResponseClient.setRestOperations(restTemplate);
return accessTokenResponseClient;
}
/*#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository)
{
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}*/
}
and also MyClientApplication
package com.uta.security.edc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextListener;
#SpringBootApplication
public class myClientApplication extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(myClientApplication.class, args);
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
I need the user to be authenticated and its authentication informations saved in its httpsession in order to request Resource servers.
Thank you for your help!

Spring Boot OAuth2 Resource Server - Difficulty Making Publicly Accessible Paths

I am experimenting with Spring Boot Security OAuth2. I would like to make some paths publicly accessible; that is, not requiring authentication. However, I am receiving the following error:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
PublicController
#RequestMapping("/public")
public class PublicController {
#RequestMapping(method= RequestMethod.GET)
public String test() {
return "Hello World!";
}
}
Now, normally this would be a good thing (since security is the goal), but, I have explicitly told the application to permitAll() on that path.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.midamcorp.resource_server.service.OAuthUserDetailsService;
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuthUserDetailsService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Hash password
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/public/**")
.permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName("test")
.and()
.csrf()
.disable();
}
}
I have tried various combinations, including using antMatches(/**) and removing the portions after the and(). However, I still receive the same error.
Thanks.
EDIT:
ResourceServerConfig
#EnableResourceServer
#Configuration
public class ResourceServerConifig extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
private final AppConfig appConfig;
private AuthenticationManager authenticationManager;
#Autowired
public ResourceServerConifig(AuthenticationManager authenticationManager, AppConfig appConfig) {
this.authenticationManager = authenticationManager;
this.appConfig = appConfig;
}
#Bean
#Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
ResourceServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices());
}
}
ApplicationConfig
#Configuration
#EnableResourceServer
public class AppConfig {
#Bean
public TokenStore tokenStore() {
// return new JwtTokenStore(accessTokenConverter());
return new JwtTokenStore((jwtTokenEnhancer()));
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.cert");
String publicKey = null;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
}
In your application.yml you could add this:
security:
ignored: /public,/public/**

Using CAS server with two different Spring Security authentication flows

I have a spring-boot backend application that authorizes users using our JASIG-CAS server and redirects them to frontend which can now access protected resources from backend. Now I need to add a mobile client. Up until now my configuration had SimpleUrlAuthenticationSuccessHandler with hardcoded url of my frontend in CasAuthenticationFilter like so:
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter(); authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(serviceProperties());
authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
But now my mobile client should open browser, show familiar CAS login page, authenticate user, redirect to backend which will issue a deep-link to mobile application. The problem is the hardcoded redirection target which points to frontend. The request from CAS looks the same regardles if it was triggerd from frontend or mobile because both use browsers, so I can't distinguish them using my own AuthenticationSuccessHandler. In a desperate act I tried constructing two different authentication flows using using the same CAS server but different callback endpoints. Here is this monster:
package com.my.company.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.NullStatelessTicketCache;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String CAS_URL_SERVER = "cas.url.server";
private static final String CAS_URL_LOGIN = "cas.url.login";
private static final String CAS_URL_LOGOUT = "cas.url.logout";
private static final String CAS_URL_SERVICE = "cas.url.service";
private static final String CAS_URL_CALLBACK = "cas.url.callback";
private static final String CAS_REDIRECT_TARGET = "cas.redirect.target";
#Inject
private Environment env;
#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
#Qualifier("casUserDetailsService")
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casAuthenticationUserDetailsService;
#Inject
#Qualifier("formUserDetailsService")
private UserDetailsService userDetailsService;
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_SERVER));
}
#Bean(name="webAuthProvider")
public CasAuthenticationProvider webCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_WEB_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(webServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean(name="mobileAuthProvider")
public CasAuthenticationProvider mobileCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_MOBILE_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(mobileServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
return filter;
}//SingleSignOutFilter
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(mobileCasAuthenticationProvider())
.authenticationProvider(webCasAuthenticationProvider());
}
#Bean(name = "webCasFilter")
public CasAuthenticationFilter webCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("webCasFilter");
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(webServiceProperties());
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas"));
//authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name = "mobileCasFilter")
public CasAuthenticationFilter mobileCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("mobileCasFilter");
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas/mobile"));
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(mobileServiceProperties());
//authenticationFilter.setFilterProcessesUrl("/auth/cas/mobile");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler("/mobile/deep-link");
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name="webCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint webCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(webServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Bean(name="mobileCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint mobileCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(mobileServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**")
.antMatchers("/console/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private class CasRedirectionFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletResponse res = (HttpServletResponse) response;
//CasAuthenticationEntryPoint caep = casAuthenticationEntryPoint();
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
HttpServletRequest req = (HttpServletRequest) request;;
String contextPath = req.getRequestURI();
if(contextPath.equals("/api/login/mobile")){
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas/mobile";
res.setHeader("Location", redirectUrl);
}else {
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas";
res.setHeader("Location", redirectUrl);
}
}
public void destroy() {
}
}
#Bean
public FilterChainProxy loginFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/cas"), new CasRedirectionFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/mobile"), new CasRedirectionFilter()));
log.debug("loginFilter {}", chains);
return new FilterChainProxy(chains);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.csrf()
.disable()
.addFilterBefore(mobileCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(webCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilter(loginFilter())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(env.getRequiredProperty(CAS_URL_LOGOUT))
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.formLogin()
//.defaultSuccessUrl(env.getRequiredProperty(CAS_REDIRECT_TARGET), true)
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.loginProcessingUrl("/api/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.authorizeRequests()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/app/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/login").authenticated()
.antMatchers("/api/login/mobile").authenticated()
.antMatchers("/api/login/cas").authenticated()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").authenticated()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/mobile/**").permitAll()
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Inject
ConferenceRepository conferenceRepository;
#Inject
UserRepository userRepository;
public GlobalSecurityConfiguration() {
super();
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionChecker permissionEvaluator = new PermissionChecker(conferenceRepository, userRepository);
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}
#Bean(name="webServiceProperties")
public ServiceProperties webServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
#Bean(name="mobileServiceProperties")
public ServiceProperties mobileServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas/mobile");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
}
This works to some degree. When mobile authentication flow is issued it works as intended but when frontend issues /api/login/cas the TicketGrantingTicket from CAS is first checked using mobile filter against service=/auth/cas/mobile but was issued for service=/auth/cas which invalidates TGT and subsequent validation using casWebAuthenticationFilter uses that invalidated ticket which of course.
So now I'm out of ideas how to force CasAuthenticationFilter to process only certain tickets? Perhaps I'm so tangled up in my idea that I can't see simpler solution? Maybe I should do two separate http security configs?
EDIT:
It seems that it all boils down to the order in which I put AuthenticationProvider:
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(webCasAuthenticationProvider())
.authenticationProvider(mobileCasAuthenticationProvider());
}
When mobileAuthenticationProider() goes first then the mobile login works and web one doesn't when I switch the order in which they are called then mobile authentication fails and the web one starts to work.
Ok so I got it working, it doesn't look like the best, most robust solution so it probably nees some more investigation and care. Nevertheless here it goes:
#Bean
public AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() {
#Override
public WebAuthenticationDetails buildDetails(
HttpServletRequest request) {
return new CustomAuthenticationDetails(request);
}
};
}
#Bean
public AuthenticationProvider customAuthenticationProvider() {
return new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String serviceUrl;
serviceUrl = ((CustomAuthenticationDetails) authentication.getDetails()).getURI();
if (serviceUrl.equals(env.getRequiredProperty(CAS_URL_CALLBACK_MOBILE))) {
return mobileCasAuthenticationProvider().authenticate(authentication);
} else {
return webCasAuthenticationProvider().authenticate(authentication);
}
}
public boolean supports(final Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication))
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAssertionAuthenticationToken.class
.isAssignableFrom(authentication));
}
};
}
I added my custom AuthenticationProvider which distinguishes between the two that I really needed. It does it using another custom class, namely CustomAuthenticationDetails which stores information about where the request came from.
public class CustomAuthenticationDetails extends WebAuthenticationDetails {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationDetails.class);
private final String URI;
private final String sessionId;
public CustomAuthenticationDetails(HttpServletRequest request) {
super(request);
this.URI = request.getRequestURI();
HttpSession session = request.getSession(false);
this.sessionId = (session != null) ? session.getId() : null;
}
public String getURI() {
return URI;
}
public String getSessionId() {
return sessionId;
}
}
And all this is wired together in AuthenticationFilter using authenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource());. Hope it can help someone in future problems or at least lead in the right direction.

Categories