Spring security only for authorization. External authentication - spring

As title says, i'm developing a web application that receives user authentication infos from an external application. A spring controller of my app gets user info and stores it in session. I want to authenticate this user inside Spring Security and then use his roles to grant/deny access to urls like
<intercept-url pattern="/myprotectedpage*" access="hasRole('rightrole')" />
I read some tutorials speaking about PRE_AUTH_FILTER and UserDetailsService but i can't get the point. What is the application lifecycle of Spring Security? Which classes are involved?
I need some full working samples.

There are lots of tuts out there for the same, just need to google properly.
Anyway the best i have found till date (for almost all spring tuts) is Krams and here's the one for basic spring security.
http://krams915.blogspot.com/2010/12/spring-security-mvc-integration_18.html
For Implementing UserDetailService here's the link
http://krams915.blogspot.in/2012/01/spring-security-31-implement_5023.html
Some others are :
Spring By Example
MK Young
And SpringSource Site itself
EDIT
This is how my own application does the authentication (Please note that i dont use external authentication, I simpply get details from DB but i guess it should not be much of an issue).
My security-context.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<global-method-security pre-post-annotations="enabled" jsr250-annotations="enabled" secured-annotations="enabled">
</global-method-security>
<http use-expressions="true">
<intercept-url pattern="/favicon.ico" access="permitAll" />
<intercept-url pattern="/static/**" access="permitAll"/>
<intercept-url pattern="/login.jsp*" access="permitAll"/>
<intercept-url pattern="/Admin/**" access="hasAnyRole('ROLE_SUPER_USER')"/>
<intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_SUPER_USER','ROLE_ADMIN'"/>
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" />
<http-basic/>
<logout logout-success-url="/login.jsp"/>
<remember-me user-service-ref="loginService" /
</http>
<authentication-manager>
<authentication-provider user-service-ref="loginService">
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="loginService" class="com.indyaah.service.LoginService">
</beans:bean>
<beans:bean id="authService" class="com.indyaah.service.AuthService" />
</beans:beans>
Now as you see i have specified a bean named loginService as my authentication provider which is a bean for class com.indyaah.service.LoginService.
The code for the same is :
Pl Note I have truncated unnecessary code
package com.indyaah.service;
..
#Service
public class LoginService implements UserDetailsService {
....
/**
* Implementation for custom spring security UserDetailsService
*/
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
logger.debug("Inside get member by username");
if (userName != null) {
Member memberVO = memberMapper.getMemberByUsername(userName);
if (memberVO != null) {
ArrayList<String> authList = memberRolesMapper.getMemberRoles(memberVO.getMemberId());
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : authList) {
System.out.println(role);
authorities.add(new GrantedAuthorityImpl(role.toString()));
}
if (memberVO.getEnabled()) {
User user = new User(memberVO.getUserName(), memberVO.getPassword(), true, true, true, true, authorities);
return user;
} else {
logger.error("User with login: " + userName + " not Enabled in database. Authentication failed for user ");
throw new UsernameNotFoundException("User Not Enabled");
}
} else {
logger.error("User with login: " + userName + " not found in database. Authentication failed for user ");
throw new UsernameNotFoundException("user not found in database");
}
} else {
logger.error("No User specified in the login ");
throw new UsernameNotFoundException("No username specified");
}
}
}
Note 2 things over here.
I get the user details (in my case from DB, yours may be diff.) and put it under a new org.springframework.security.core.userdetails.User object which is then returned by the method to spring security.
Also the authorities, (which I load separately from DB as per my DB architecture, again your scenario may vary) and pass it to spring security via same User object.

implement a service that holds the user information.
#Service
public class UserAuthenticationInfoService {
private final Map<String, UserInfo> infos = new HashMap<String, UserInfo>();
public void addUserInfo(UserInfo userInfo){
infos.put(userInfo.getUsername(), userInfo);
}
public UserInfo getUserInfo(String username) {
return infos.get(username);
}
}
your controllers populates the UserAuthenticationInfoService service with the user information which you receive from your external application.
then implement a custom UserDetailsService to acces these information.
public class CustomerUserDetailsService implements UserDetailsService {
#Autowired
private UserAuthenticationInfoService service;
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
UserInfo info = service.getUserInfo(userName);
return new User(info.getUsername(), info.getPassword(), info.getAuthorities());
}
}
and setup spring security context to use this UserDetailsService (you'll find it in the spring security documentation)

You can implement your own Custom AuthenticationManager and Custom UsernamePasswordAuthenticationFilter. This is simple example but it can give you an idea also for your information this is very sensitive part of security context:)
Simply create beans in your spring_security.xml:
<http entry-point-ref="authenticationProcessingFilterEntryPoint"
use-expressions="true">
<custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<custom-filter ref="customUsernamePasswordAuthenticationFilter"
position="FORM_LOGIN_FILTER" />
<session-management
session-authentication-strategy-ref="sas"></session-management>
</http>
<beans:bean id="authenticationProcessingFilterEntryPoint"
class="org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint">
<beans:property name="loginFormUrl" value="/login" />
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
<beans:bean id="customAuthenticationManager"
class="my.package.security.CustomAuthenticationManager" />
<beans:bean id="customUsernamePasswordAuthenticationFilter"
class="my.package.security.CustomUsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy"
ref="sas" />
<beans:property name="authenticationManager" ref="customAuthenticationManager" />
<beans:property name="authenticationSuccessHandler">
<beans:bean
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/main.xhtml" />
</beans:bean>
</beans:property>
<beans:property name="authenticationFailureHandler">
<beans:bean
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login.xhtml" />
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="sessionManagementFilter"
class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg name="securityContextRepository"
ref="httpSessionSecurityContextRepository" />
</beans:bean>
<beans:bean id="httpSessionSecurityContextRepository"
class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
When you implement CustomUsernamePasswordAuthenticationFilter override Authentication and add your external logic:
public final class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
CustomAuthentication auth = new CustomAuthentication();
// set details of current user
auth.setDetails(new WebAuthenticationDetails(request));
auth.setAuthenticated(true);
auth.setUserName(username);
// set authentication to current security session
LOGGER.info("Setting authentication into existing security context");
SecurityContextHolder.getContext().setAuthentication(auth);
// if validation done return generated authentication
return auth;
}
}
Then generated authentication object will be handled by authentication manager:
public final class CustomAuthenticationManager implements AuthenticationManager {
/*
* (non-Javadoc)
*
* #see org.springframework.security.authentication.AuthenticationManager#
* authenticate(org.springframework.security.core.Authentication)
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CustomUsernamePasswordAuthenticationFilter.class);
private final BadCredentialsException badCredentialsException = new BadCredentialsException("Invalid username/password");
#Override
public Authentication authenticate(Authentication authentication) {
//check if user has valid authentication
if (authentication == null) {
LOGGER.debug("Null authentication");
throw badCredentialsException;
}
//Check mandatory fields
if (!Validator.isValidString((String) authentication.getPrincipal()) || !Validator.isValidString((String) authentication.getCredentials())) {
LOGGER.debug("Null/blank username/credential");
throw badCredentialsException;
}
//Check if there is any role assigned into user
if (authentication.getAuthorities() != null && authentication.getAuthorities().size() < 1) {
LOGGER.debug("No authority found");
throw badCredentialsException;
}
//Validate role
//IF ROLE VALIDATION REQUIRED YOU CAN HANDLE IT HERE
boolean authorityValid = false;
LOGGER.info("Validating user authentication. Total grantedAuth size: " + authentication.getAuthorities().size());
for (GrantedAuthority g : authentication.getAuthorities()) {
if (!authorityValid) {
//Testing purpose one type role available, when exact roles prepared create enum types
authorityValid = g.getAuthority().equals("ROLE_LDAP_AUTHENTICATED");
}
}
//if none of role matching to required throw exception
if(!authorityValid){
LOGGER.debug("User has authority but none of them matching");
throw badCredentialsException;
}
LOGGER.info("Final validation done returning authentication");
return authentication;
}
}
Then if required you can override default authentication object too,if roles dynamically located here is where you handle:
public final class CustomAuthentication implements Authentication {
/**
*
*/
private static final long serialVersionUID = 1L;
private transient String userName;
private transient boolean authenticated;
private transient Object details;
private static final transient String ROLE = "ROLE_LDAP_AUTHENTICATED";
/*
* (non-Javadoc)
*
* #see java.security.Principal#getName()
*/
#Override
public String getName() {
return this.userName; //for dynamic username logic here
}
//IF ROLES DYNAMICALLY ALLOCATED ASSIGN IT HERE, HERE IS WHERE YOU READ FROM INTERCEPT URL
/*
* (non-Javadoc)
*
* #see org.springframework.security.core.Authentication#getAuthorities()
*/
#Override
public Collection<GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
auths.add(new GrantedAuthority() {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public String getAuthority() {
if (authenticated) {
return ROLE;
}
return null;
}
});
return auths;
}
/*
* (non-Javadoc)
*
* #see org.springframework.security.core.Authentication#getCredentials()
*/
#Override
public Object getCredentials() {
//TODO: a specific algorithm can be stored
return userName + " is ldap authenticated user";
}
/*
* (non-Javadoc)
*
* #see org.springframework.security.core.Authentication#getDetails()
*/
#Override
public Object getDetails() {
return this.details;
}
/*
* (non-Javadoc)
*
* #see org.springframework.security.core.Authentication#getPrincipal()
*/
#Override
public Object getPrincipal() {
return userName;
}
/*
* (non-Javadoc)
*
* #see org.springframework.security.core.Authentication#isAuthenticated()
*/
#Override
public boolean isAuthenticated() {
return this.authenticated;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.security.core.Authentication#setAuthenticated(boolean
* )
*/
#Override
public void setAuthenticated(boolean arg0) {
this.authenticated = arg0;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setDetails(Object details) {
this.details = details;
}
}

Related

Not getting login failure reason (only BadCredential Exception is popped)

Tried various ways to get custom message from spring, if user authentication fails.
Using
<spring.version>4.2.4.RELEASE</spring.version>
<spring.security.version>4.0.3.RELEASE</spring.security.version>
XML configuration
<http auto-config="true" use-expressions="false">
<intercept-url pattern="/**" access='ROLE_FUNCTION' />
<form-login login-page="/login"
default-target-url="/welcome"
username-parameter="j_username"
password-parameter="j_password"
login-processing-url="/j_spring_security_check"
always-use-default-target="true"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
/>
<logout logout-url="/j_spring_security_logout" logout-success-url="/login?logout" delete-cookies="JSESSIONID" />
<access-denied-handler error-page="/accessDenied" />
<csrf disabled="true"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="**userDetailsService**">
<password-encoder ref="bcryptEncoder"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="authenticationSuccessHandler" class="com.company.project.CustomAuthenticationSuccessHandler"/>
<beans:bean id="**authenticationFailureHandler**" class="com.company.project.CustomAuthenticationFailureHandler"/>
<beans:bean name="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
Bean definition excerpt is as below
Implementation
userDetailsService
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("Getting access details for user : {}", username);
UserDto userDto = null;
boolean accountNonExpired = true;
boolean accountNonLocked = true;
boolean credentialsNonExpired = true;
boolean enabled = true;
try {
userDto = userService.loginUser(username);
if (userDto == null) {
throw new UsernameNotFoundException("User not found");
}
if (Active.Y != userDto.getActive()) {
enabled = false;
throw new BadCredentialsException("User account is inactive");
}
} catch (BaseException be) {
throw new BadCredentialsException(be.getMessage().toLowerCase());
}
UserContext context = new UserContext();
context.setLoginId(username);
context.setName(userDto.getName());
context.setPrincipleId(userDto.getId());
List<GrantedAuthority> grantedAuthorities = getGrantedAuthorities(userDto);
String password = getActivePassword(userDto);
accountNonExpired = isAccountActive(userDto);
accountNonLocked = isAccountUnlocked(userDto);
credentialsNonExpired = isCredentialsActive(userDto);
return new UserLoginDetails(grantedAuthorities, password, username, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, context);
}
}
authenticationSuccessHandler works fine.
authenticationFailureHandler
#Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
UserService userService;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
try {
// execute it when user enters wrong password, i.e loginAttempt ...
} catch (Exception e) {
// TODO: something
}
// TODO: how do I send message, if authenticationException.
redirectStrategy.sendRedirect(request, response, "/login?error");
// clearAuthenticationAttributes(request);
}
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
To show error message I'm using following line
JSP
<c:set var="errorMessage" value="${sessionScope[\"SPRING_SECURITY_LAST_EXCEPTION\"].message}" />
Let me brief the expected messages.
If user enters wrong credentials he should get
"Invalid credentials"
If user account is inactive he should get
"Your account is not active"
If user exceeded permissible no. of
attempt his account will get locked and he will get "Your account is
locked"
If my implementation is not correct please let me know what changes should be done.
If you want to override the AuthenticationFailureHandler, you can extend the SimpleUrlAuthenticationFailureHandler, it already has a method to save exception.
protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
if (forwardToDestination) {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
} else {
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
}
}
}
When you save the exception to request or session, then you can get the message.
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}

authentication-provider is not called

Here is my security-context.xml file:
<http auto-config='false' authentication-manager-ref="authenticationManager" entry-point-ref="authenticationEntryPoint">
<intercept-url pattern="/"/>
<intercept-url pattern="/**"/>
<csrf disabled="true"/>
<custom-filter position="REMEMBER_ME_FILTER" ref="DashboardFilter"></custom-filter>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="DashboardAuthProvider"></authentication-provider>
</authentication-manager>
<beans:bean id="DashboardFilter" class="com.apple.store.dashboard.security.DashboardAuthFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
</beans:bean>
<beans:bean id="authenticationEntryPoint" class="com.apple.store.dashboard.security.DashboardAuthEntryPoint">
</beans:bean>
<beans:bean id="DashboardAuthProvider" class="com.apple.store.dashboard.security.DashboardAuthProvider">
I have defined DashboardAuthProvider as such:
public class DashboardAuthProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(DashboardAuthProvider.class);
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
logger.debug("Inside DashboardAuthProvider: authenticate method +authentication=" + authentication);
Authentication auth = null;
[...]
}
}
When I executed the code, I can hit the filter, but not provider. I read many spring security related document and couldn't find anything wrong with my configuration in xml. Could someone help?
Here is my filter:
public class DashboardAuthFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(DashboardAuthFilter.class);
public DashboardAuthFilter() {
super("/**");
}
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
throws org.springframework.security.core.AuthenticationException {
logger.debug("Inside DashboardAuthFilter:attemptAuthentication method:");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth!=null ){
if (auth.isAuthenticated()){
logger.debug("Previously authenticated.isAuthenticated=true::: Auth details:" +auth);
return auth;
}
}
//Validate the DS Auth Cookie
Cookie AOSCookie = WebUtils.getCookie(request, "myacinfo-uat");//
if ( AOSCookie == null )
return null;
Authentication authResult = null;
try {
if( org.apache.commons.lang.StringUtils.isEmpty(AOSCookie.toString())) {
throw new PreAuthenticatedCredentialsNotFoundException("DS Auth Cookie not found. Commence DS Authentication..");
}
String credentials = "NA";
String validateCookieDetails = correctAuthentication(AOSCookie, request);
logger.debug("validateCookieDetails ....." + validateCookieDetails);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(validateCookieDetails, credentials);
authResult = getAuthenticationManager().authenticate(authRequest);
logger.debug("Attempted authentication: authResult ::" + authResult.toString());
} catch (org.springframework.security.core.AuthenticationException e) {
logger.error("AttemptAuthentication: Not Authenticated : AuthenticationException ....." + e.getMessage());
} catch (Exception e) {
logger.error("Exception occured during authentication....." + e.getMessage());
}
return authResult;
}
}

DB and LDAP authentication by using Spring Security

I am working on one project where I am using Spring Security and it authenticates user nicely from DB.
Now my need is I want to authenticate user using LDAP and Keep existing DB authentication as well.
Also, I can't use standard LDAP authentication way by using Spring security because I only have one function call to authenticate whether user is present or not by method like,
com.company.ldap.Authentication auth = new com.company.ldap.Authentication();
int status = auth.authenticate(userName, userPassword);
if I receive the status as 1 then user is authenticated otherwise not.
So for this scenario, I have created seperate url "/j_spring_facebook_security_check" (here name is facebook but actually it is for LDAP)
My applicationContext-Security File
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<security:http auto-config="true" use-expressions="true" access-denied-page="/accessDenied.jsp">
<security:form-login login-page="/index.jsp"
default-target-url="/jsp/home.jsp"
authentication-failure-handler-ref="authenticationFailureHandler" />
<security:intercept-url pattern="/jsp/listInBetweenPlaces.jsp"
access="permitAll" />
<security:intercept-url pattern="/jsp/home.jsp"
access="permitAll" />
<security:intercept-url pattern="/jsp/*"
access="isAuthenticated()" />
<security:logout logout-url="/j_spring_security_logout" logout-success-url="/index.jsp?logout=success"
invalidate-session="true" />
<security:custom-filter before="FORM_LOGIN_FILTER"
ref="facebookAuthenticationFilter" />
</security:http>
<bean id="facebookAuthenticationFilter" class="org.springframework.security.facebook.FacebookAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name = "authenticationSuccessHandler">
<bean class = "org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
<property name = "defaultTargetUrl" value = "/jsp/home.jsp" />
<property name = "alwaysUseDefaultTargetUrl" value = "true" />
</bean>
</property>
<property name = "authenticationFailureHandler">
<bean class = "org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name = "defaultFailureUrl" value = "/fb/failure.jsp" />
</bean>
</property>
</bean>
<bean id="ldapAuthenticationProvider" class="org.springframework.security.facebook.FacebookAuthenticationProvider">
<property name="roles" value="ROLE_FACEBOOK_USER" />
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder hash="md5" />
</security:authentication-provider>
<security:authentication-provider ref="ldapAuthenticationProvider">
</security:authentication-provider>
</security:authentication-manager>
<bean id="customUserDetailsService" class="com.abc.carpool.authentication.CustomUserDetailsService">
</bean>
</beans>
FacebookAuthenticationFilter.java
public class FacebookAuthenticationFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
#Autowired
CarpoolService carpoolService=null;
public CarpoolService getCarpoolService() {
return carpoolService;
}
public void setCarpoolService(CarpoolService carpoolService) {
this.carpoolService = carpoolService;
}
public static final String DEFAULT_FILTER_PROCESS_URL = "/j_spring_facebook_security_check";
private ApplicationContext ctx;
protected FacebookAuthenticationFilter() {
super(DEFAULT_FILTER_PROCESS_URL);
}
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException,
IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String userName = request.getParameter("j_username");
String userPassword = request.getParameter("j_password");
System.out.println("Username and pswd is :"+userName + " " + userPassword);
System.out.println("SYS PATH :"+System.getenv("MC_ENV_PATH"));
User user = null;
try{
com.abc.ldap.Authentication auth = new com.abc.ldap.Authentication();
int status = auth.authenticate(userName, userPassword);
//int status=2;
if(status==1){
//CREATE NEW USER AND SAVE IN DB
}
}else{
throw new UsernameNotFoundException("Incorrect Email Id or Password.");
}
System.out.println("status is :"+status);
}catch (Exception e) {
System.out.println("Exception is "+e.getMessage());
e.printStackTrace();
return null;
}
System.out.println("FacebookAuthenticationFilter.attemptAuthentication() :"+userName + " " + userPassword);
UsernamePasswordAuthenticationToken upatToken = new UsernamePasswordAuthenticationToken(userName, userPassword);
AuthenticationManager authenticationManager = getAuthenticationManager();
Authentication auth = authenticationManager.authenticate(upatToken);
return auth;
}
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx = ctx;
}
}
FacebookAuthenticationProvider .java
public class FacebookAuthenticationProvider implements AuthenticationProvider {
private String[] roles;
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
System.out.println("FacebookAuthenticationProvider.authenticate()");
return authentication;
}
public boolean supports(Class<? extends Object> authentication) {
boolean supports = true;
return supports;
}
public void setRoles(String[] roles) {
this.roles = roles;
}
public String[] getRoles() {
return roles;
}
}
In the above case things are working fine, Control is going to Filter class and verifying for user using LDAP and if user is present then I am saving user one copy to my DB and want to go ahead with normal flow.
actually when I call
Authentication auth = authenticationManager.authenticate(upatToken);
from FacebookAuthenticationFilter, control, goes to CustomUserDetailsService class.
My need is CustomUserDetailsService is only for DB authentication, I don't want control to go there.
How to achieve this thing in a nice way.
AuthenticationProvider.supports(Class<? extends Object> authentication) can help you. At this moment you use UsernamePasswordAuthenticationToken for both cases (DB and LDAP). Both authentication providers supports this type so both are used. In a case of LDAP try to add your custom authentication object:
public class CustomLdapAuthenticationToken extends AbstractAuthenticationToken {
Then use it in your FacebookAuthenticationFilter instead of UsernamePasswordAuthenticationToken.
Change your FacebookAuthenticationProvider.supports(...) implementation:
public boolean supports(Class<? extends Object> authentication) {
boolean result = false;
if(authentication instanceof CustomLdapAuthenticationToken) {
result = true;
}
return result;
}
From this moment each authentication provider will process only corresponding authentication request.

Spring Security automatic login interception

I've already developed a register and login module with Spring Security. My concern now is about how can I intercept the automatic stored login to save info in a database. I mean, when user marks "remember me", if enters into my app, automatically goes to logged-homepage but I would like to register that access in a database.
Now it's easy to do when users goes explicitly through login page, but not in the above case.
Regards,
UPDATE: I put some extra info:
security.xml
<http auto-config="true">
<form-login login-page="/login" login-processing-url="/j_spring_security_check" default-target-url="/private/dashboard" />
<remember-me key="rememberMeKey" user-service-ref="userServiceImpl" />
</http>
<authentication-manager alias="authenticationManager" />
<authentication-manager>
<authentication-provider user-service-ref="userServiceImpl">
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
userServiceImpl
#Service
#Transactional
public class UserServiceImpl implements UserDetailsService {
#Resource
private UserDao userDao;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
String password = userDao.getUserPassword(username);
if (password!=null) {
userDao.registerAccess(username);
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_REGISTERED"));
return new User(username,password, AUTHORITIES);
} else {
throw new UsernameNotFoundException("User not found: " + username);
}
}
}
You have multiple options here:
Set up your org.springframework.security.web.authentication.AuthenticationSuccessHandler
Subscribe to org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent (see #Ionut answer)
AuthenticationSuccessHandler will work equally for both your cases (normal login and remember me):
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
// log authentication success here for both cases
super.onAuthenticationSuccess(request, response, authentication);
}
}
In your security.xml:
<bean id="customAuthenticationSuccessHandler" class="com.domain.security.CustomAuthenticationSuccessHandler"/>
<security:http ... >
...
<security:form-login login-page='/login.html' authentication-success-handler-ref="customAuthenticationSuccessHandler" />
<security:remember-me authentication-success-handler-ref="customAuthenticationSuccessHandler" />
</security:http>
You can do something like this
#Component
public class AppListener implements ApplicationListener {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof InteractiveAuthenticationSuccessEvent) {
handleLoginEvent();
} else if (event instanceof HttpSessionDestroyedEvent)
handleLogoutEvent((HttpSessionDestroyedEvent) event);
}
private void handleLoginEvent() {
// handle login event
}
private synchronized void handleLogoutEvent(HttpSessionDestroyedEvent event) {
// handle logout event
}
}
Regards,
EDIT
add this to web.xml
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

Handling roles when authenticated to active directory with spring security 3.1

I'm trying to use a authenticate with an Active directory using Spring Security 3.1.
I get authenticated and all is well.
<sec:ldap-server id="ldapServer" url="ldap://ldap/dc=sub,dc=domain,dc=com" port="389" />
<sec:authentication-manager erase-credentials="true" >
<sec:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</sec:authentication-manager>
<bean id="ldapActiveDirectoryAuthProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="domain" />
<constructor-arg value="ldap://server:389/"/>
</bean>
Now to the question. How do I handle roles for the user so that I can setup my filters?
eg.
<sec:intercept-url pattern="/**" access="ROLE_USER"/>
Solution
I found out how to do this by using the UserDetailContextMapper and map my AD groups to ROLE_USER,ROLE_ADMIN etc.
<bean id="ldapActiveDirectoryAuthProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="domain" />
<constructor-arg value="ldap://host:389/"/>
<property name="userDetailsContextMapper" ref="tdrUserDetailsContextMapper"/>
<property name="useAuthenticationRequestCredentials" value="true"/>
</bean>
<bean id="tdrUserDetailsContextMapper" class="com.bla.bla.UserDetailsContextMapperImpl"/>
Mapper class:
public class UserDetailsContextMapperImpl implements UserDetailsContextMapper, Serializable{
private static final long serialVersionUID = 3962976258168853954L;
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authority) {
List<GrantedAuthority> mappedAuthorities = new ArrayList<GrantedAuthority>();
for (GrantedAuthority granted : authority) {
if (granted.getAuthority().equalsIgnoreCase("MY USER GROUP")) {
mappedAuthorities.add(new GrantedAuthority(){
private static final long serialVersionUID = 4356967414267942910L;
#Override
public String getAuthority() {
return "ROLE_USER";
}
});
} else if(granted.getAuthority().equalsIgnoreCase("MY ADMIN GROUP")) {
mappedAuthorities.add(new GrantedAuthority() {
private static final long serialVersionUID = -5167156646226168080L;
#Override
public String getAuthority() {
return "ROLE_ADMIN";
}
});
}
}
return new User(username, "", true, true, true, true, mappedAuthorities);
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
}
}
You can also inject a GrantedAuthoritiesMapper which was introduced in 3.1 as a general strategy for modifying the authorites. Plus you might want to use SimpleGrantedAuthority for the GrantedAuthority implementation. Alternatively, you could use an enum since you have a fixed set of values:
enum MyAuthority implements GrantedAuthority {
ROLE_ADMIN,
ROLE_USER;
public String getAuthority() {
return name();
}
}
class MyAuthoritiesMapper implements GrantedAuthoritiesMapper {
public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
Set<MyAuthority> roles = EnumSet.noneOf(MyAuthority.class);
for (GrantedAuthority a: authorities) {
if ("MY ADMIN GROUP".equals(a.getAuthority())) {
roles.add(MyAuthority.ROLE_ADMIN);
} else if ("MY USER GROUP".equals(a.getAuthority())) {
roles.add(MyAuthority.ROLE_USER);
}
}
return roles;
}
}
The roles in the beans.xml must be an exact match of the CN (common name) of the memberOf value attribute. You should read a tutorial about directory basics.
Say have this user:
CN=Michael-O,OU=Users,OU=department,DC=sub,DC=company,DC=net
In his context exists this memberOf value CN=Group Name,OU=Permissions,OU=Groups,OU=department,DC=sub,DC=company,DC=net
The Bean will locate this memberOf value and extract Group Name. You beans.xml has to have exactly this value.

Resources