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.
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)
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.
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;
}
}