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.
Related
I want to detect when a user enter in my web page with Login form or remember-me cookie, and i want to execute code for register login, but this dont work. I dont know if exists other method for get my goal.
I test it this solutions:
http://learn.eastros.com/2012/07/11/springsecurity-capturing-rememberme-success-event/ . Here, its necessary define all class manually, filters, services and handler for login, logout and remember-me.
Recording logins with Spring Security. This post try a easier solution, only defines a same handler for success Login and success remember-me. But dont work.
For other hand, here remember-me and authentication-success-handler say that the handler for form-login and remember-me cant be same class, and i will should implements AuthenticationSuccessHandler for remmeber-me handler. Didnt work too.
I tried define filter before and after position of REMEMBER_ME_FILTER too. Nothing.
QUESTIONS
Is possible use a filter REMEMBER-ME using form-login?
security:http configuration needs indicate auto-config="false" and implement filters for login, logout and remember me as first link?
Is it Necessary that I implement my Service, and provider or I can implement filter only?
My configuration:
security-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
ref="customAuthenticationProvider">
</security:authentication-provider>
</security:authentication-manager>
<bean id="customAuthenticationProvider" class="com.treebuk.config.CustomAuthenticationProvider"></bean>
<bean id="customUserDetailsService" class="com.treebuk.config.CustomUserDetailsService"></bean>
<bean id="customAuthenticationSuccessHandler" class="com.treebuk.config.CustomAuthenticationSuccessHandler"></bean>
<bean id="customRememberMeAuthenticationSuccessHandler" class="com.treebuk.config.CustomRememberMeAuthenticationSuccessHandler"></bean>
<bean id="customLogoutSuccessHandler" class="com.treebuk.config.CustomLogoutSuccessHandler"></bean>
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"></bean>
<bean id="customRememberMeService" class="com.treebuk.config.CustomRememberMeService">
<constructor-arg value="MYKEY" />
<constructor-arg ref="customUserDetailsService"/>
</bean>
<bean id="customRememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
<!-- key needs to be same as that provided to customRememberMeService. -->
<constructor-arg value="MYKEY"/>
</bean>
<bean id="rememberMeFilter" class="com.treebuk.config.CustomRememberMeAuthenticationFilter">
<constructor-arg ref="authenticationManager"/>
<constructor-arg ref="customRememberMeService"/>
</bean>
<security:http >
<security:intercept-url pattern="/" access="permitAll" />
<security:intercept-url pattern="/login/**" access="permitAll" />
<security:intercept-url pattern="/logout" access="permitAll" />
<security:intercept-url pattern="/**" access="denyAll" />
<security:form-login
authentication-success-handler-ref="customAuthenticationSuccessHandler"
authentication-failure-url="/login?error=true"
login-page="/login"
password-parameter="lgPassword"
username-parameter="lgUsername" />
<security:logout
success-handler-ref="customLogoutSuccessHandler"
logout-url="/logout"
invalidate-session="true" />
<security:csrf disabled="true" />
<security:remember-me
user-service-ref="customUserDetailsService"
remember-me-parameter="lgRememberMe"
token-validity-seconds="100" />
<security:custom-filter ref="rememberMeFilter" after="REMEMBER_ME_FILTER" />
<security:session-management>
<security:concurrency-control max-sessions="1" />
</security:session-management>
</security:http>
</beans>
login.jsp
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl}" method="post">
<input type="hidden" name="spring-security-redirect" value="<c:out value="${param.r}" />">
<label for="txt_username">Usuario:</label>
<input type="text" id="txt_username" name="lgUsername" />
<br />
<label for="txt_password">Contraseña:</label>
<input type="password" id="txt_password" name="lgPassword">
<br />
<label for="chck_remeberme">Recordarme:</label>
<input type="checkbox" id="chck_remeberme" name="lgRememberMe" checked="checked" />
<br />
<input name="submit" type="submit" value="Identificarse" />
</form>
CustomRememberMeAuthenticationFilter.java
public class CustomRememberMeAuthenticationFilter extends RememberMeAuthenticationFilter {
public CustomRememberMeAuthenticationFilter(AuthenticationManager authenticationManager, RememberMeServices rememberMeServices) {
super(authenticationManager, rememberMeServices);
// TODO Auto-generated constructor stub
}
#Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
// TODO: User has been auto-logged using a remember me cookie, do your stuff here
String x = "OK";
x += "";
String y = x;
}
}
CustomRememberMeService.java ( I comment body extractRememberMeCookie because else request.getHeader("remember-me") == null and throw a null exception)
public class CustomRememberMeService extends TokenBasedRememberMeServices{
public static final int TOKEN_VALIDITY_SECONDS = 15; // 60*30=30 minutes
public CustomRememberMeService(String key, UserDetailsService userDetailsService) {
super(key, userDetailsService);
}
#Override
protected String extractRememberMeCookie(HttpServletRequest request) {
// String rememberMe = request.getHeader("remember-me");
// int startIndex = "remember-me=".length();
// int endIndex = rememberMe.indexOf("; ", startIndex);
// return rememberMe.substring(startIndex, endIndex);
return "";
}
#Override
protected int calculateLoginLifetime(HttpServletRequest request, Authentication authentication) {
return TOKEN_VALIDITY_SECONDS;
}
}
CustomAuthenticationProvider.java, (**here is where i decrypt and compare the user password)
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider{
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authLoginForm) throws AuthenticationException {
String principal = authLoginForm.getName();
String credenctials = (String) authLoginForm.getCredentials();
User user = (User) customUserDetailsService.loadUserByUsername(principal);
if (user != null) {
if (passwordEncoder.matches(credenctials, user.getPassword())) {
System.out.println("Usuario identificado correctamente");
return new UsernamePasswordAuthenticationToken(principal.toLowerCase(), user.getPassword(), user.getAuthorities());
}
else
{
System.out.println("Contraseña incorrecta");
throw new BadCredentialsException("Error de autentificación");
}
}
else
{
throw new BadCredentialsException("Error de autentificación");
}
}
#Override
public boolean supports(Class<?> arg0) {
return true;
}
}
CustomAuthenticationSuccessHandler.java
#Component
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
User user = userService.findByAlias(authentication.getName().toLowerCase());
**// CODE FOR REGISTER LOGIN USER**
setDefaultTargetUrl("URL");
super.onAuthenticationSuccess(request, response, authentication);
}
}
CustomLogoutSuccessHandler.java
#Component
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication != null) {
// do something
}
setDefaultTargetUrl("/login");
super.onLogoutSuccess(request, response, authentication);
}
}
CustomRememberMeAuthenticationSuccessHandler,java (this code used when tried a solution where use a hablder for success form-login and other for success remember-me --> remember-me and authentication-success-handler)
#Service
public class CustomRememberMeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
User user = userService.findByAlias(authentication.getName().toLowerCase());
**// CODE FOR REGISTER LOGIN USER BY REMEMBER-ME**
}
}
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;
}
}
I am trying to implement AJAX login. Here is my spring-security.xml
<http auto-config="true" use-expressions="true">
<form-login />
</http>
<beans:bean id="pwdEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11" />
</beans:bean>
<beans:bean id="appUserDetailService" class="com.mobapp.security.AppUserDetailService"></beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="appUserDetailService">
<password-encoder ref="pwdEncoder"/>
</authentication-provider>
</authentication-manager>
instance initializer.xml
<bean id="instanceInitializer" class="com.mobapp.appinstances.AppInstances" destroy-method="shutDownInstances">
<constructor-arg>
<array value-type="java.lang.String">
<value>127.0.0.1</value>
</array>
</constructor-arg>
<constructor-arg type="java.lang.String">
<value>127.0.0.1</value>
</constructor-arg>
<constructor-arg type="int">
<value>9300</value>
</constructor-arg>
</bean>
Here is applicationContext
<context:component-scan base-package="com.mobapp.controllers" />
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/" cache-period="31556926"/>
<bean id="loginSuccessHandler" class="com.mobapp.security.LoginSuccessHandler"></bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<context:annotation-config />
<task:annotation-driven />
<import resource="spring/instanceInitializer.xml"/>
<import resource="spring/security-context.xml"/>
Here userdetailsservice implementation
#Service
public class AppUserDetailService implements UserDetailsService{
private static final Logger logger = Logger.getLogger(AppUserDetailService.class);
#Override
public UserDetails loadUserByUsername(String loginUserName) throws UsernameNotFoundException {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
System.out.println("inside load user by user name ........++++++++");
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(11);
return new LoggedInUser("test", passwordEncoder.encode("123"), true,true,true,true,authList);
}
}
Here is Login controller
#RestController
public class LoginController {
private static final Logger logger = Logger.getLogger(LoginController.class);
#Autowired
private AppInstances appInstances;
#Autowired
AppUserDetailService appUserDetails;
#RequestMapping(method = RequestMethod.POST, value = "/signup")
public ResponseEntity<?> signup(#RequestBody SignupForm form) throws Exception
{
ResponseEntity<?> validate = FormFieldValidator.validate(form);
if(validate.getStatusCode() != HttpStatus.OK)
return validate;
else
{
return SignupService.signup(form, appInstances);
}
}
#RequestMapping(method = RequestMethod.GET, value = "/login")
public ResponseEntity<?> login()
{
UserDetails loadUserByUsername = appUserDetails.loadUserByUsername("test");
logger.info("loadUserByUsername ========= "+loadUserByUsername);
return new ResponseEntity("Hello",HttpStatus.OK);
}
}
Here the logger.info never get called.
Here is my login form
<form id="loginForm" method="post">
<input type="text" class="form-control" placeholder="your email" name='j_username'>
<input type="password" size="10" class="form-control" placeholder="password" name='j_password'>
</div>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
$.ajax({
url:"${pageContext.servletContext.contextPath}/login",
type:"POST",
beforeSend: function( xhr ) {xhr.setRequestHeader($("meta[name='_csrf_header']").attr("content"), $("meta[name='_csrf']").attr("content"));},
data:{j_username:$("#loginForm :input[name=j_username]").val(),j_password:$("#loginForm :input[name=j_password]").val()}
}).done(function(d){
alert("OK : "+d);
}).error(function(jqXHR, textStatus, errorThrown){
alert("Error: "+textStatus);
});
LoggedInUSer.java
public class LoggedInUser extends User{
private String name;
private String userId;
private String userName;
public String getName() {
return name;
}
public String getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
public boolean isBlock() {
return block;
}
private boolean block;
public LoggedInUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired,credentialsNonExpired, accountNonLocked, authorities);
//super(username, password, authorities);
this.name = name;
this.userId = userId;
this.userName=userName;
this.block=block;
}
}
Whenever i enter username: test and password : 123 i got
Your login attempt was not successful, try again.
Reason: Bad credentials
as response. What i am missing ?
Here is full log
https://drive.google.com/file/d/0B9pquofzd_JYTlVPZ3ZvRF83S1k/view?usp=sharing
Based on the incomplete logs provided by you (as indicated by Rob who is the Project Lead for Spring Security and would have been the best person on Earth to help you out!), a few things:
You are using Spring Security 4.0.1 with <form-login>: The Bad credentials is because SS 4+ will look for attribute names as 'username' & 'password' but you are providing 'j_username' & 'j_password' instead. At that time only,
Your logger.info never gets called: Rightly so. There is no reason it should get called! First up, whenever you make a POST request to /login, it will match with the default login processing URL in the spring security filter chain and you'll get Bad credentials. Secondly, it has a request method GET associated with it and whenever it will be requested directly, the default SS login page will appear (as it is provided by the DefaultLoginPageGeneratingFilter even before hitting your dispatcher.
You get a login form in response: Which is the expected behaviour here as SS is configured to use a login form.
If you still need some help, please provide the DEBUG log (set log4j log level to DEBUG; from your logger invocation it looks like you're using Log4J).
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>
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;
}
}