I am working on a Spring-MVC application in which we are using Spring-Security for authentication and authorization. In our application we send out emails with URL's. Many times the user is not logged in, but after login, we would want to redirect to the original link after login. Thank you. Tried XML configuration, but it is not working.
Xml config :
<!-- Global Security settings -->
<security:http pattern="/resources/**" security="none"/>
<security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
<security:form-login login-page="/login" username-parameter="j_username" password-parameter="j_password"
login-processing-url="/j_spring_security_check" default-target-url="/canvaslisting"
always-use-default-target="true" authentication-failure-url="/denied"/>
<security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
token-validity-seconds="1209600" data-source-ref="dataSource"/>
<security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
<security:intercept-url pattern="/**" requires-channel="https"/>
<security:port-mappings>
<security:port-mapping http="8080" https="8443"/>
</security:port-mappings>
<security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>
<security:session-management session-fixation-protection="newSession">
<security:concurrency-control session-registry-ref="sessionReg" max-sessions="5" expired-url="/login"/>
</security:session-management>
</security:http>
<beans:bean id="sessionReg" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<beans:bean id="rememberMeAuthenticationProvider"
class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
<beans:constructor-arg index="0" value="_spring_security_remember_me"/>
<beans:constructor-arg index="1" ref="userDetailsService"/>
<beans:constructor-arg index="2" ref="jdbcTokenRepository"/>
<property name="alwaysRemember" value="true"/>
</beans:bean>
<beans:bean id="jdbcTokenRepository"
class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<beans:property name="createTableOnStartup" value="false"/>
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>
<!-- Remember me ends here -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="LoginServiceImpl">
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11"/>
</beans:bean>
<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="LoginServiceImpl"/>
<beans:property name="passwordEncoder" ref="encoder"/>
</beans:bean>
<beans:bean id="authenticationFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="filterProcessesUrl" value="/login" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler">
<beans:bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="useReferer" value="true"/>
</beans:bean>
</beans:property>
<beans:property name="authenticationFailureHandler">
<beans:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login?login_error=t" />
</beans:bean>
</beans:property>
</beans:bean>
</beans>
Thank you.
Although you have not shown how you have configured your tag, so I am assuming that, you already have, that see bellow code.
<security:http auto-config="true" use-expressions="false">
<security:form-login login-page="/login" login-processing-url="/login"
username-parameter="custom_username"
password-parameter="custom_password"
default-target-url="/appointments/"
always-use-default-target="true"
authentication-failure-url="/login?error=true"
/>
<security:intercept-url pattern="/appointments/*" access="ROLE_USER"/>
<security:intercept-url pattern="/schedule/*" access="ROLE_FOO"/>
<security:intercept-url pattern="/**" access="ROLE_ANONYMOUS, ROLE_USER"/>
</security:http>
so, if you are not mentioning these two lines
default-target-url="/appointments/"
always-use-default-target="true"
, then by default spring security will redirect you user to the same page (which he requested), and got authentication prompt.
If this still does not full fills your requirement then you may need to implement your own filter or take a look at FilterSecurityInterceptor or MethodSecurityInterceptor .
Bellow is an Example, which will help you to achieve your goal:
To understand this problem better, take a look on below example:
- user receives a newsletter mail where he's invited to vote for more beautiful holidays picture. The URL contains a hash parameter used to authenticate user in the page.
- when user clicks on given URL, it will arrive to voting page as already authenticated user.
So My Custom Filter Will be like bellow:
package com.osigu.ehr.config;
public class OneShotActionFilter extends GenericFilterBean {
private static final Logger LOGGER =
LoggerFactory.getLogger(OneShotActionFilter.class);
private static Map<String, String> users = new HashMap<String, String>();
static {
users.put("0000000000001", "bartosz");
users.put("0000000000002", "admin");
users.put("0000000000003", "mod");
}
private static final String PARAM_NAME = "uio";
private AuthenticationManager authenticationManager;
private UserDetailsService userDetailsService;
private final RedirectStrategy redirectStrategy = new
DefaultRedirectStrategy();
private enum AuthenticationStates {
REDIRECT, CONTINUE;
}
public void setAuthenticationManager(AuthenticationManager
authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException,
ServletException {
LOGGER.debug("One shot filter invoked");
if (attemptAuthentication(request) == AuthenticationStates.REDIRECT) {
// Note that we should handle that dynamically but for learning purposes
//we'll consider that one-shot
// authentication works only for this URL
this.redirectStrategy.sendRedirect((HttpServletRequest) request,
(HttpServletResponse) response,
"/secret/one-shot-action");
} else {
LOGGER.debug("User was not correctly authenticated, continue filter chain");
// continue execution of all other filters
// You can test the code without this fragment in the pages without ?uio parameter. You should see blank page because of
// security filter chain interruption.
filterChain.doFilter(request, response);
}
}
private AuthenticationStates attemptAuthentication(ServletRequest request) {
AuthenticationStates state = AuthenticationStates.CONTINUE;
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
String code = request.getParameter(PARAM_NAME);
if ((authentication == null || !authentication.isAuthenticated()) && code !=
null &&
users.containsKey(code)) {
LOGGER.debug("Checking user for code " + code);
UserDetails user = userDetailsService.loadUserByUsername(users.get(code));
LOGGER.debug("Found user from code (" + users.get(code) + "). User found is " + user);
if (user != null) {
users.remove(code);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword());
authentication = this.authenticationManager.authenticate(authRequest);
if (authentication != null && authentication.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authentication);
state = AuthenticationStates.REDIRECT;
}
}
}
return state;
}
}
And Finally Configure it in your xml, like bellow
<security:http authentication-manager-ref="frontend" auto-config="true" use-
expressions="true" access-denied-page="/access-denied">
<-- other filters are defined here -->
<security:custom-filter ref="oneShootAuthFilter"
after="CONCURRENT_SESSION_FILTER"/>
</security:http>
<bean id="oneShootAuthFilter"
class="com.waitingforcode.security.filter.OneShotActionFilter">
<property name="authenticationManager" ref="frontend" />
<property name="userDetailsService" ref="inMemoryUserService" />
</bean>
To test the filter, we can try to access to http://localhost:8080/?uio=0000000000001. You should be redirected (but only once) to http://localhost:8080/secret/one-shot-action page
I wrote this example for better clarity and as reference for future questions.
Do up vote if you think it helped you to clear your doubt.
you can also visit this link
I am working on a Java application which connects to a Spring-MVC server, using Spring-Security for authentication/authorization. The login part works, and I get a JSESSIONID back in the Java application, but when I make request to secured resource, it fails, Spring-Security is unable to find any logged in user. What am I doing wrong here?
security-applicationContext.xml :
<security:http pattern="/resources/**" security="none"/>
<security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
<security:form-login login-page="/login" login-processing-url="/j_spring_security_check"
default-target-url="/dashboard" always-use-default-target="false"
authentication-failure-url="/denied"/>
<security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
token-validity-seconds="1209600" data-source-ref="dataSource"/>
<security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
<!--<security:intercept-url pattern="/**" requires-channel="https"/>-->
<security:port-mappings>
<security:port-mapping http="8080" https="8443"/>
</security:port-mappings>
<security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>
<security:session-management session-fixation-protection="migrateSession">
<security:concurrency-control session-registry-ref="sessionRegistry" max-sessions="5" expired-url="/login"/>
</security:session-management>
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="restaurantauthenticationprovider"/>
<security:authentication-provider ref="userauthenticationprovider"/>
</security:authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11"/>
</beans:bean>
<beans:bean id="restaurantauthenticationprovider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="LoginServiceImpl"/>
<beans:property name="passwordEncoder" ref="encoder"/>
</beans:bean>
<beans:bean id="userauthenticationprovider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="UserLoginServiceImpl"/>
<beans:property name="passwordEncoder" ref="encoder"/>
</beans:bean>
As I have 2 tables to check from which to login, I have 2 DAOAuthenticationProviders.
UserLoginServiceImpl :
#Transactional
#Service("loginuserDetailsService")
public class UserLoginServiceImpl implements UserDetailsService {
#Autowired
private PersonDAO personDAO;
#Autowired
private UserAssembler userAssembler;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,DataAccessException {
System.out.println("Username is "+username);
Person person = this.personDAO.findPersonByUserName(username.toLowerCase());
if(person == null) { throw new UsernameNotFoundException("Wrong username or password");}
return userAssembler.buildUserFromUserEntity(person);
}
}
Assembler :
#Service("userassembler")
#Transactional
public class UserAssembler {
#Transactional
User buildUserFromUserEntity(Person userEntity){
System.out.println("We are in Userassembler"+userEntity.getEmail());
String username = userEntity.getUsername().toLowerCase();
String password = userEntity.getPassword();
boolean enabled = userEntity.isEnabled();
boolean accountNonExpired = userEntity.isAccountNonExpired();
boolean credentialsNonExpired = userEntity.isCredentialsNonExpired();
boolean accountNonLocked = userEntity.isAccountNonLocked();
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username,password,enabled,accountNonExpired,credentialsNonExpired,accountNonLocked,authorities);
}
}
The above is the config, now I will put the rest code which is failing :
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Log.d("Username is ", username);
String jsessionid = rest.execute("http://192.168.178.60:8080/j_spring_security_check", HttpMethod.POST,
new RequestCallback() {
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
request.getBody().write(("j_username=" + username + "&j_password=" + password).getBytes());
}
}, new ResponseExtractor<String>() {
#Override
public String extractData(ClientHttpResponse response) throws IOException {
List<String> cookies = response.getHeaders().get("Cookie");
if (cookies == null) {
cookies = response.getHeaders().get("Set-Cookie");
}
String cookie = cookies.get(cookies.size() - 1);
System.out.println("Cookie is " + cookie);
// The method below gets me which user is logged in, and I always get null for Controller method.
reply = rest.getForObject(
"http://192.168.178.60:8080/dashboard", String.class);
int start = cookie.indexOf('=');
int end = cookie.indexOf(';');
return cookie.substring(start + 1, end);
}
});
}
});
thread.start();
Update
Finally, the code which worked :
// I am getting the cookie from the server, which I am setting manually for every request, cookie is a static volatile string.
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", "JSESSIONID=" + StaticRestTemplate.jsessionid);
HttpEntity requestEntity = new HttpEntity(null, requestHeaders);
ResponseEntity rssResponse = rest.exchange(
"http://192.168.178.60:8080/dashboard",
HttpMethod.GET,
requestEntity,
String.class);
String abc = (String) rssResponse.getBody();
Spring's RestTemplate does not keep track of cookies by default. This ensures you don't accidentally pass a cookie (i.e. JSESSIONID) from one user on behalf of another user (i.e. think of using the RestTemplate on a server where many users are leveraging the same RestTemplate).
If you want to do this you can configure it using something like this:
RestTemplate rest = new RestTemplate();
// initialize the RequestFactory to allow cookies
HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
rest.setRequestFactory(factory);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", "user");
map.add("password", "password");
String result = rest.postForObject("http://localhost:8080/login", map, String.class);
String hello = rest.postForObject("http://localhost:8080/", map, String.class);
assertThat(hello).isEqualTo("Hello");
To use this code you will need to ensure you have httpclient on your classpath. For example, the following might be in your pom.xml if you are using Maven:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
Obviously you will need to ensure you include the version of httpclient that works for your dependencies.
I have configured CAS 4 / Spring Security / Active Directory and able to authenticate successfully.
But I have difficulty in to retrieve roles and later use that for authorisation.
I have the roles available after the authentication in CAS but I want to pass this to the service (web app) so that it can be used to check the authorisation (for eg. hasRole('ROLE_EDITOR') )
I think I am making some configuration mistake in the below beans which I investigating further. I have shown these configuration fragments.
bean 'casAuthenticationProvider' and the property 'authenticationUserDetailsService'
bean 'ldapAuthenticationHandler' -- 'principalAttributeMap' property
bean 'attributeRepository'
Server logs
2014-09-25 16:59:45,516 DEBUG [org.jasig.cas.authentication.LdapAuthenticationHandler] - <Found principal attribute: [displayName[James TAYLOR]]>
2014-09-25 16:59:45,516 DEBUG [org.jasig.cas.authentication.LdapAuthenticationHandler] - <Found principal attribute: [memberOf[CN=USERTOKEN,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_NOTIFICA
TION,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_CIR_AUTHORISER,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_SANCTIONS_DB,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_COLLEGES,OU=GRO
UPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_APP_CIR,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL, CN=ROLE_CIR_EDITOR,OU=GROUPS,OU=EGATE,DC=EGATE-T,DC=LOCAL]]>
2014-09-25 16:59:45,519 INFO [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <LdapAuthenticationHandler successfully authenticated taylorj+password>
2014-09-25 16:59:45,519 DEBUG [org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver] - <Attempting to resolve a principal...>
2014-09-25 16:59:45,521 DEBUG [org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver] - <Creating SimplePrincipal for [taylorj]>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Created seed map='{username=[taylorj]}' for uid='taylorj'>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Adding attribute 'uid' with value '[taylorj]' to query builder 'null'>
2014-09-25 16:59:45,522 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Constructed LDAP search query [sAMAccountName=taylorj]>
2014-09-25 16:59:45,524 DEBUG [org.jasig.cas.persondir.LdapPersonAttributeDao] - <Generated query builder '[org.ldaptive.SearchFilter#-1419023406::filter=sAMAccountName={0}, parameters={0=taylorj}]' f
rom query Map {username=[taylorj]}.>
2014-09-25 16:59:45,527 DEBUG [org.ldaptive.SearchOperation] - <execute request=[org.ldaptive.SearchRequest#1241774557::baseDn=dc=egate-t,dc=local, searchFilter=[org.ldaptive.SearchFilter#-1419023406:
:filter=sAMAccountName={0}, parameters={0=taylorj}], returnAttributes=[], searchScope=null, timeLimit=0, sizeLimit=10, derefAliases=null, typesOnly=false, binaryAttributes=null, sortBehavior=UNORDERED
, searchEntryHandlers=null, searchReferenceHandlers=null, controls=null, followReferrals=false, intermediateResponseHandlers=null] with connection=[org.ldaptive.DefaultConnectionFactory$DefaultConnect
ion#511019109::config=[org.ldaptive.ConnectionConfig#1652971138::ldapUrl=ldap://eb2ts-app14, connectTimeout=3000, responseTimeout=-1, sslConfig=[org.ldaptive.ssl.SslConfig#1637458774::credentialConfig
=[org.ldaptive.ssl.X509CredentialConfig#-421683437::trustCertificates=classpath:root_CA_base64.cer, authenticationCertificate=null, authenticationKey=null], trustManagers=null, enabledCipherSuites=nul
l, enabledProtocols=null, handshakeCompletedListeners=null], useSSL=false, useStartTLS=false, connectionInitializer=null], providerConnectionFactory=[org.ldaptive.provider.jndi.JndiConnectionFactory#3
99139047::connectionCount=1, environment={java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, com.sun.jndi.ldap.connect.timeout=3000, java.naming.ldap.version=3}, providerConfig=[org.ldaptiv
e.provider.jndi.JndiProviderConfig#1738533348::operationExceptionResultCodes=[PROTOCOL_ERROR, SERVER_DOWN], properties={}, connectionStrategy=DEFAULT, environment=null, tracePackets=null, removeDnUrls
=true, searchIgnoreResultCodes=[TIME_LIMIT_EXCEEDED, SIZE_LIMIT_EXCEEDED, PARTIAL_RESULTS], sslSocketFactory=null, hostnameVerifier=null, controlProcessor=org.ldaptive.provider.ControlProcessor#33b4ac
e2]], providerConnection=org.ldaptive.provider.jndi.JndiConnection#1ac243f3]>
2014-09-25 16:59:45,540 DEBUG [org.ldaptive.SearchOperation] - <execute response=[org.ldaptive.Response#370759675::result=[[]], resultCode=SUCCESS, message=null, matchedDn=null, responseControls=null,
referralURLs=[ldap://ForestDnsZones.EGATE-T.LOCAL/DC=ForestDnsZones,DC=EGATE-T,DC=LOCAL??base], messageId=-1] for request=[org.ldaptive.SearchRequest#1241774557::baseDn=dc=egate-t,dc=local, searchFil
ter=[org.ldaptive.SearchFilter#-1419023406::filter=sAMAccountName={0}, parameters={0=taylorj}], returnAttributes=[], searchScope=null, timeLimit=0, sizeLimit=10, derefAliases=null, typesOnly=false, bi
naryAttributes=null, sortBehavior=UNORDERED, searchEntryHandlers=null, searchReferenceHandlers=null, controls=null, followReferrals=false, intermediateResponseHandlers=null] with connection=[org.ldapt
ive.DefaultConnectionFactory$DefaultConnection#511019109::config=[org.ldaptive.ConnectionConfig#1652971138::ldapUrl=ldap://eb2ts-app14, connectTimeout=3000, responseTimeout=-1, sslConfig=[org.ldaptive
.ssl.SslConfig#1637458774::credentialConfig=[org.ldaptive.ssl.X509CredentialConfig#-421683437::trustCertificates=classpath:root_CA_base64.cer, authenticationCertificate=null, authenticationKey=null],
trustManagers=null, enabledCipherSuites=null, enabledProtocols=null, handshakeCompletedListeners=null], useSSL=false, useStartTLS=false, connectionInitializer=null], providerConnectionFactory=[org.lda
ptive.provider.jndi.JndiConnectionFactory#399139047::connectionCount=1, environment={java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, com.sun.jndi.ldap.connect.timeout=3000, java.naming.l
dap.version=3}, providerConfig=[org.ldaptive.provider.jndi.JndiProviderConfig#1738533348::operationExceptionResultCodes=[PROTOCOL_ERROR, SERVER_DOWN], properties={}, connectionStrategy=DEFAULT, enviro
nment=null, tracePackets=null, removeDnUrls=true, searchIgnoreResultCodes=[TIME_LIMIT_EXCEEDED, SIZE_LIMIT_EXCEEDED, PARTIAL_RESULTS], sslSocketFactory=null, hostnameVerifier=null, controlProcessor=or
g.ldaptive.provider.ControlProcessor#33b4ace2]], providerConnection=org.ldaptive.provider.jndi.JndiConnection#1ac243f3]>
2014-09-25 16:59:45,546 DEBUG [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver#bf07ee0 resolved taylorj from t
aylorj+password>
2014-09-25 16:59:45,548 INFO [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <Authenticated taylorj with credentials [taylorj+password].>
2014-09-25 16:59:45,549 DEBUG [org.jasig.cas.authentication.PolicyBasedAuthenticationManager] - <Attribute map for taylorj: {}>
2014-09-25 16:59:45,549 INFO [org.perf4j.TimingLogger] - <start[1411660785397] time[151] tag[AUTHENTICATE]>
2014-09-25 16:59:45,556 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
Spring Security config
<beans:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<beans:property name="serviceProperties" ref="serviceProperties" />
<beans:property name="ticketValidator" ref="ticketValidator" />
<beans:property name="authenticationUserDetailsService" ref="ldapUserDetailsService" />
<beans:property name="key" value="cas_auth_provider_ldap" />
</beans:bean>
<beans:bean id="ldapUserDetailsService"
class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService" >
<beans:constructor-arg >
<beans:list>
<beans:value>authorities</beans:value>
</beans:list>
</beans:constructor-arg>
</beans:bean>
CAS deployerConfigContext.xml
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<entry key-ref="ldapAuthenticationHandler" value-ref="primaryPrincipalResolver" />
</map>
</constructor-arg>
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
</property>
</bean>
<bean id="ldapAuthenticationHandler"
class="org.jasig.cas.authentication.LdapAuthenticationHandler"
p:principalIdAttribute="sAMAccountName"
c:authenticator-ref="authenticator">
<property name="principalAttributeMap">
<map>
<entry key="displayName" value="displayName" />
<entry key="mail" value="mail" />
<entry key="memberOf" value="memberOf" />
</map>
</property>
</bean>
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<bean id="attributeRepository"
class="org.jasig.cas.persondir.LdapPersonAttributeDao"
p:connectionFactory-ref="pooledLdapConnectionFactory"
p:baseDN="${ldap.baseDn}" p:searchControls-ref="searchControls" p:searchFilter="sAMAccountName={0}">
<property name="queryAttributeMapping">
<map>
<entry key="username" value="uid" />
</map>
</property>
<property name="resultAttributeMapping">
<map>
<entry key="uid" value="username" />
<entry key="givenname" value="first_name" />
<entry key="sn" value="last_name" />
<entry key="mail" value="email" />
</map>
</property>
</bean>
<bean id="searchControls"
class="javax.naming.directory.SearchControls"
p:searchScope="2"
p:countLimit="10" />
After spending too much time to use the CAS feature to retrieve the roles and it did nt work...
So I decided to retrieve the needed LDAP attributes myself after successful login.
In my case I used Java, Spring framework, etc...
I have my class MyCasAuthenticationUserDetailsService that would looks like below and extends org.springframework.security.core.userdetails.AuthenticationUserDetailsService
And in initialiseAdditionalUserDetails() method I retrieve the needed attributes and set in my own CasUser class which extends org.springframework.security.core.userdetails.User
public class MyCasAuthenticationUserDetailsService implements AuthenticationUserDetailsService<Authentication> {
#Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
sAMAccountName = token.getName();
init();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
CasUser user = new CasUser(sAMAccountName, NON_EXISTENT_PASSWORD_VALUE, authorities);
// Sets additional user details
user = initialiseAdditionalUserDetails(sAMAccountName, user);
return user;
}
/**
* Retrieves the user roles from LDAP
*
* #return
*/
private List<String> getRoles() {
List<String> result = new ArrayList<String>();
try {
SearchResult searchResult = searchExecutor.search(connectionFactory, "(sAMAccountname=" + sAMAccountName + ")", "memberOf").getResult();
LdapEntry entry = searchResult.getEntry();
if (entry != null) {
Collection<String> roles = entry.getAttribute().getStringValues();
StringBuffer rolesCSV = new StringBuffer();
for (String role : roles) {
int start = role.indexOf("=");
int end = role.indexOf(",");
rolesCSV.append(role.substring(start + 1, end));
rolesCSV.append(",");
}
String role = rolesCSV.deleteCharAt(rolesCSV.length() - 1).toString();
String rolesArr[] = role.split(",");
result = Arrays.asList(rolesArr);
}
} catch (LdapException e) {
LOG.error(e);
}
return result;
}
/**
* Initialise additional user details like country, ISO country code, email,
* etc
*
*/
private CasUser initialiseAdditionalUserDetails(String sAMAccountName, CasUser user) {
try {
SearchResult searchResult = searchExecutor.search(connectionFactory, "(sAMAccountname=" + sAMAccountName + ")", "c", "co", "mail",
"givenName", "sn", "displayName").getResult();
LdapEntry entry = searchResult.getEntry();
if (entry != null) {
if (entry.getAttribute("givenName") != null) {
String firstName = entry.getAttribute("givenName").getStringValue();
user.setFirstName(firstName);
}
if (entry.getAttribute("sn") != null) {
String lastName = entry.getAttribute("sn").getStringValue();
user.setLastName(lastName);
}
if (entry.getAttribute("displayName") != null) {
String fullName = entry.getAttribute("displayName").getStringValue();
user.setFullName(fullName);
}
if (entry.getAttribute("c") != null) {
String isoCountryCode = entry.getAttribute("c").getStringValue();
user.setIsoCountryCode(isoCountryCode);
}
if (entry.getAttribute("co") != null) {
String country = entry.getAttribute("co").getStringValue();
user.setCountry(country);
}
if (entry.getAttribute("mail") != null) {
String email = entry.getAttribute("mail").getStringValue();
user.setEmail(email);
}
}
} catch (LdapException e) {
LOG.error(e);
}
return user;
}
}
And finally on the CAS Java clients .... I configure myCasAuthenticationUserDetailsService as below.....
<beans:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<beans:property name="serviceProperties" ref="serviceProperties" />
<beans:property name="ticketValidator" ref="ticketValidator" />
<beans:property name="authenticationUserDetailsService"
ref="myCasAuthenticationUserDetailsService" />
<beans:property name="key" value="notification" />
</beans:bean>
<beans:bean id="myCasAuthenticationUserDetailsService"
class="com.jai.cas.MyCasAuthenticationUserDetailsService">
<beans:property name="ldapUrl" value="${ldapUrl}" />
<beans:property name="ldapAdminDn" value="${ldapAdminDn}" />
<beans:property name="ldapAdminPwd" value="${ldapAdminPwd}" />
<beans:property name="ldapUserBaseDn" value="${ldapUserBaseDn}" />
</beans:bean>
This would retrieve all the needed attributes....
I'm running in my web app filter, which recieves from external source info about user, if he's logged in or not. Heres my filter:
#Override
public void doFilter( ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException, ServletException
{
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String loginBean = httpRequest.getHeader( CommonVariables.LOGIN_BEAN );
if ( loginBean == null )
{
System.out.println( "FILTER-----------" );
try
{
String login;
String domain;
//Here i'm getting login and domain string
loginBean = domain + "\\" + login;
httpResponse.addHeader( "LoginBean", loginBean );
System.out.println( login + " " + domain );
} catch ( Exception e )
{
e.printStackTrace();
//redirect to login page
httpResponse.sendRedirect( "..." );
return;
}
}
chain.doFilter( request, response );
}
Not I though that those header will be passed into next filters. Therefore I implemented Spring Security PRE_AUTH_FILTER:
Spring security context
<http use-expressions="true" auto-config="false" entry-point-ref="http403EntryPoint">
<!-- Additional http configuration omitted -->
<custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</http>
<beans:bean id="siteminderFilter" class=
"org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<beans:property name="principalRequestHeader" value="LoginBean"/>
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="preauthAuthProvider"
class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService">
<beans:bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<beans:property name="userDetailsService" ref="userDetailsService"/>
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<beans:bean id="userDetailsService" class="com.execon.security.CustomUserDetailsService"/>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
Then I tried to parse loginBean String in my CustoUserDetailsService and receive actuall user object. But It is not fired, and app fails with this:
org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException: LoginBean header not found in request.
So that means the header is set wrong? Or not set at all? What might be wrong?
Filter setting LoginBean is first one fired, then goes Spring SEcurity. Standard output works ok as I have:
17:12:15,669 INFO [stdout] (http--127.0.0.1-8080-2) FILTER-----------
17:12:15,669 INFO [stdout] (http--127.0.0.1-8080-2) LOGIN DOMAIN
You are setting something in the response and the Spring's class is looking for the same in the request.
The only way you can modify an incoming HttpServletRequest is to decorate it. You should define a class as follows first:
public class AuthHttpServletRequest extends HttpServletRequestWrapper
{
private String loginBean;
public AuthHttpServletRequest(HttpServletRequest aRequest, String loginBean)
{
super(aRequest);
this.loginBean = loginBean;
}
#Override
public String getHeader(String headerName)
{
if(CommonVariables.LOGIN_BEAN.equals(headerName)) {
return this.loginBean;
}
return super.getHeader(headerName);
}
}
Then, replace the following line in your filter:
httpResponse.addHeader( "LoginBean", loginBean );
with this:
request = new AuthHttpServletequest(httpRequest, loginBean);
Then your chain.doFilter gets the request that can return the loginBean as you intended it to, to the Spring's authentication filter class, down in the filter chain.
Can't get Spring Security to work with DB authentication provider.
In-memory authentication provider works OK.
Step to reproduce:
when I logged with credentials sb,sb,login() method of AuthenticationService returned false.
There are no related log in Tomcat.
applicationContext.xml:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/chirokDB?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
service layer:
#Service("authenticationService")
public class AuthenticationServiceImpl implements AuthenticationService {
#Resource(name = "authenticationManager")
private AuthenticationManager authenticationManager;
public boolean login(String username, String password) {
try {
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
username, password));
if (authenticate.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authenticate);
return true;
}
} catch (AuthenticationException e) {
}
return false;
}
managed bean level:
public String doLogin() {
boolean isLoggedIn = authenticationService.login(name, password);
if (isLoggedIn) {
return "index";
}
FacesContext.getCurrentInstance().addMessage("login failure", new FacesMessage());
return "failureLogin";
}
applicationContext-security.xml:
<global-method-security pre-post-annotations="enabled"/>
<http auto-config="true">
<form-login login-page="/login.xhtml" default-target-url="/index.xhtml"/>
<intercept-url pattern="/contacts.xhtml" access="ROLE_ANONYMOUS,ROLE_USER"/>
<intercept-url pattern="/delivery.xhtml" access="ROLE_USER"/>
<logout invalidate-session="true"/>
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>
</session-management>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
persistence level:
MySql DB has following standard tables(required by Spring):
1. users
2. authorities
users table has record with username='sb' and password='sb'
authorities table has record with username='sb' and authority='ROLE_USER'
note
with user-in memory all works OK with following config:
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="sb" password="sb" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
assumption:
dataSource injected into org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
As far Hibernate ORM used, perhaps some other than JdbcDaoImpl should be used?
Check if you're getting an Exception in your empty catch block (which always is a bad idea).