Make User available from Authentication via DaoAuthenticationProvider.

This commit is contained in:
Ben Alex 2004-06-21 06:10:14 +00:00
parent 36ad7f3963
commit 1a0bec5bf1
15 changed files with 164 additions and 39 deletions

View File

@ -1,3 +1,10 @@
Changes in version 0.6 (2004-xx-xx)
-----------------------------------
* Added feature so DaoAuthenticationProvider returns User in Authentication
* Fixed Linux compatibility issues (directory case sensitivity etc)
* Documentation improvements
Changes in version 0.51 (2004-06-06)
------------------------------------

View File

@ -51,12 +51,26 @@ import org.springframework.dao.DataAccessException;
* <p>
* Upon successful validation, a
* <code>UsernamePasswordAuthenticationToken</code> will be created and
* returned to the caller. In addition, the {@link User} will be placed in the
* {@link UserCache} so that subsequent requests with the same username can be
* validated without needing to query the {@link AuthenticationDao}. It should
* be noted that if a user appears to present an incorrect password, the
* {@link AuthenticationDao} will be queried to confirm the most up-to-date
* password was used for comparison.
* returned to the caller. The token will include as its principal either a
* <code>String</code> representation of the username, or the {@link User}
* that was returned from the authentication repository. Using
* <code>String</code> is appropriate if a container adapter is being used, as
* it expects <code>String</code> representations of the username. Using
* <code>User</code> is appropriate if you require access to additional
* properties of the authenticated user, such as email addresses,
* human-friendly names etc. As container adapters are not recommended to be
* used, and <code>User</code> provides additional flexibility, by default a
* <code>User</code> is returned. To override this default, set the {@link
* #setForcePrincipalAsString} to <code>true</code>.
* </p>
*
* <P>
* Caching is handled via the <code>User</code> object being placed in the
* {@link UserCache}. This ensures that subsequent requests with the same
* username can be validated without needing to query the {@link
* AuthenticationDao}. It should be noted that if a user appears to present an
* incorrect password, the {@link AuthenticationDao} will be queried to
* confirm the most up-to-date password was used for comparison.
* </p>
*
* <P>
@ -79,6 +93,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
private SaltSource saltSource;
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
//~ Methods ================================================================
@ -95,6 +110,14 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
return authenticationDao;
}
public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
this.forcePrincipalAsString = forcePrincipalAsString;
}
public boolean isForcePrincipalAsString() {
return forcePrincipalAsString;
}
/**
* Sets the PasswordEncoder instance to be used to encode and validate
* passwords. If not set, {@link PlaintextPasswordEncoder} will be used by
@ -148,13 +171,19 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Determine username
String username = authentication.getPrincipal().toString();
if (authentication.getPrincipal() instanceof User) {
username = ((User) authentication.getPrincipal()).getUsername();
}
boolean cacheWasUsed = true;
User user = this.userCache.getUserFromCache(authentication.getPrincipal()
.toString());
User user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
user = getUserFromBackend(authentication);
user = getUserFromBackend(username);
}
if (!user.isEnabled()) {
@ -170,7 +199,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
// Password incorrect, so ensure we're using most current password
if (cacheWasUsed) {
cacheWasUsed = false;
user = getUserFromBackend(authentication);
user = getUserFromBackend(username);
}
if (!isPasswordCorrect(authentication, user)) {
@ -194,9 +223,15 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
}
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords
return new UsernamePasswordAuthenticationToken(user.getUsername(),
return new UsernamePasswordAuthenticationToken(principalToReturn,
authentication.getCredentials(), user.getAuthorities());
}
@ -220,10 +255,9 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
authentication.getCredentials().toString(), salt);
}
private User getUserFromBackend(Authentication authentication) {
private User getUserFromBackend(String username) {
try {
return this.authenticationDao.loadUserByUsername(authentication.getPrincipal()
.toString());
return this.authenticationDao.loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
throw new BadCredentialsException("Bad credentials presented");
} catch (DataAccessException repositoryProblem) {

View File

@ -36,6 +36,7 @@
<!-- Authentication provider that queries our data access object -->
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
<property name="forcePrincipalAsString"><value>true</value></property>
</bean>
<!-- The authentication manager that iterates through our only authentication provider -->

View File

@ -36,6 +36,7 @@
<!-- Authentication provider that queries our data access object -->
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
<property name="forcePrincipalAsString"><value>true</value></property>
</bean>
<!-- The authentication manager that iterates through our only authentication provider -->

View File

@ -166,7 +166,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
}
UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
assertEquals("marissa", castResult.getPrincipal());
assertEquals(User.class, castResult.getPrincipal().getClass());
assertEquals("koala", castResult.getCredentials());
assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
@ -192,7 +192,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
}
UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
assertEquals("marissa", castResult.getPrincipal());
assertEquals(User.class, castResult.getPrincipal().getClass());
// We expect original credentials user submitted to be returned
assertEquals("koala", castResult.getCredentials());

View File

@ -24,6 +24,7 @@ import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockHttpSession;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
import org.apache.commons.codec.binary.Base64;
@ -199,8 +200,8 @@ public class BasicProcessingFilterTests extends TestCase {
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
assertEquals("marissa",
((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
.toString());
((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
.getUsername());
}
public void testOtherAuthorizationSchemeIsIgnored()
@ -291,8 +292,8 @@ public class BasicProcessingFilterTests extends TestCase {
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
assertEquals("marissa",
((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
.toString());
((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
.getUsername());
// NOW PERFORM FAILED AUTHENTICATION
// Setup our HTTP request

View File

@ -20,6 +20,8 @@ import junit.framework.TestCase;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import java.net.URLEncoder;
/**
* Tests {@link CasProcessingFilterEntryPoint}.
@ -99,8 +101,11 @@ public class CasProcessingFilterEntryPointTests extends TestCase {
ep.afterPropertiesSet();
ep.commence(request, response);
assertEquals("https://cas/login?service=https://mycompany.com/bigWebApp/j_acegi_cas_security_check",
response.getRedirect());
assertEquals("https://cas/login?service="
+ URLEncoder.encode(
"https://mycompany.com/bigWebApp/j_acegi_cas_security_check",
"UTF-8"), response.getRedirect());
}
public void testNormalOperationWithRenewTrue() throws Exception {

View File

@ -7,7 +7,7 @@
<subtitle>Reference Documentation</subtitle>
<releaseinfo>0.51</releaseinfo>
<releaseinfo>0.6</releaseinfo>
<authorgroup>
<author>
@ -946,10 +946,24 @@
increased the complexity of the <literal>AuthenticationDao</literal>
interface. For instance, a method would be required to increase the
count of unsuccessful authentication attempts. Such functionality
could be easily provided in a new
<literal>AuthenticationManager</literal> or
<literal>AuthenticationProvider</literal> implementation if it were
desired.</para>
could be easily provided by leveraging the application event
publishing features discussed below.</para>
<para><literal>DaoAuthenticationProvider</literal> returns an
<literal>Authentication</literal> object which in turn has its
<literal>principal</literal> property set. The principal will be
either a <literal>String</literal> (which is essentially the username)
or a <literal>User</literal> object (which was looked up from the
<literal>AuthenticationDao</literal>). By default the
<literal>User</literal> is returned, as this enables applications to
subclass <literal>User</literal> and add extra properties potentially
of use in applications, such as the user's full name, email address
etc. If using container adapters, or if your applications were written
to operate with <literal>String</literal>s (as was the case for
releases prior to Acegi Security 0.6), you should set the
<literal>DaoAuthenticationProvider.forcePrincipalAsString</literal>
property to <literal>true</literal> in your application
context.</para>
</sect2>
<sect2 id="security-authentication-provider-events">
@ -1927,6 +1941,11 @@ public boolean supports(Class clazz);</programlisting></para>
provided below. Once installed, please take the time to try the sample
application to ensure your container adapter is properly
configured.</para>
<para>When using container adapters with the
<literal>DaoAuthenticationProvider</literal>, ensure you set its
<literal>forcePrincipalAsString</literal> property to
<literal>true</literal>.</para>
</sect2>
<sect2 id="security-container-adapters-catalina">
@ -2497,7 +2516,7 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<literal>PasswordHandler</literal> will do).</para>
<para>To install, you will need to download and extract the CAS server
archive. We used version 2.0.12 Beta 3. There will be a
archive. We used version 2.0.12. There will be a
<literal>/web</literal> directory in the root of the deployment. Copy
an <literal>applicationContext.xml</literal> containing your
<literal>AuthenticationManager</literal> as well as the

View File

@ -6,7 +6,7 @@
# $Id$
# Project version
acegi-security-version=0.51
acegi-security-version=0.6
# Project name
name=acegi-security-system-for-spring

View File

@ -33,6 +33,7 @@
<!-- Authentication provider that queries our data access object -->
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
<property name="forcePrincipalAsString"><value>true</value></property>
</bean>
<!-- The authentication manager that iterates through our only authentication provider -->

View File

@ -19,6 +19,7 @@ import net.sf.acegisecurity.AccessDeniedException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.SecureContext;
import net.sf.acegisecurity.providers.dao.User;
import org.springframework.beans.factory.InitializingBean;
@ -88,7 +89,13 @@ public class ContactManagerFacade implements ContactManager, InitializingBean {
Authentication auth = ((SecureContext) ContextHolder.getContext())
.getAuthentication();
if (auth.getPrincipal().toString().equals(result.getOwner())) {
String username = auth.getPrincipal().toString();
if (auth.getPrincipal() instanceof User) {
username = ((User) auth.getPrincipal()).getUsername();
}
if (username.equals(result.getOwner())) {
return result;
} else {
throw new AccessDeniedException(

View File

@ -18,6 +18,7 @@ package sample.contact;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.ConfigAttribute;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.vote.AccessDecisionVoter;
import org.aopalliance.intercept.MethodInvocation;
@ -96,9 +97,15 @@ public class ContactSecurityVoter implements AccessDecisionVoter {
}
if (passedOwner != null) {
String username = authentication.getPrincipal().toString();
if (authentication.getPrincipal() instanceof User) {
username = ((User) authentication.getPrincipal())
.getUsername();
}
// Check the authentication principal matches the passed owner
if (passedOwner.equals(authentication.getPrincipal()
.toString())) {
if (passedOwner.equals(username)) {
return ACCESS_GRANTED;
}
}

View File

@ -20,6 +20,7 @@ import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.SecureContext;
import net.sf.acegisecurity.providers.dao.User;
import org.springframework.beans.factory.InitializingBean;
@ -74,10 +75,17 @@ public class SecureIndexController implements Controller, InitializingBean {
+ "SecureContext");
}
final Authentication currentUser = secureContext.getAuthentication();
// Lookup username. As we must accommodate DaoAuthenticationProvider,
// CAS and container based authentication, we take care with casting
Authentication auth = secureContext.getAuthentication();
String username = auth.getPrincipal().toString();
if (auth.getPrincipal() instanceof User) {
username = ((User) auth.getPrincipal()).getUsername();
}
boolean supervisor = false;
GrantedAuthority[] granted = currentUser.getAuthorities();
GrantedAuthority[] granted = auth.getAuthorities();
for (int i = 0; i < granted.length; i++) {
if (granted[i].getAuthority().equals("ROLE_SUPERVISOR")) {
@ -85,13 +93,12 @@ public class SecureIndexController implements Controller, InitializingBean {
}
}
Contact[] myContacts = contactManager.getAllByOwner(currentUser.getPrincipal()
.toString());
Contact[] myContacts = contactManager.getAllByOwner(username);
Map model = new HashMap();
model.put("contacts", myContacts);
model.put("supervisor", new Boolean(supervisor));
model.put("user", currentUser.getPrincipal().toString());
model.put("user", username);
return new ModelAndView("index", "model", model);
}

View File

@ -15,8 +15,10 @@
package sample.contact;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.SecureContext;
import net.sf.acegisecurity.providers.dao.User;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
@ -54,8 +56,14 @@ public class WebContactAddController extends SimpleFormController {
public ModelAndView onSubmit(Object command) throws ServletException {
String name = ((WebContact) command).getName();
String email = ((WebContact) command).getEmail();
String owner = ((SecureContext) ContextHolder.getContext()).getAuthentication()
.getPrincipal().toString();
Authentication auth = ((SecureContext) ContextHolder.getContext())
.getAuthentication();
String owner = auth.getPrincipal().toString();
if (auth.getPrincipal() instanceof User) {
owner = ((User) auth.getPrincipal()).getUsername();
}
Contact contact = new Contact(contactManager.getNextId(), name, email,
owner);

27
upgrade-05-06.txt Normal file
View File

@ -0,0 +1,27 @@
===============================================================================
ACEGI SECURITY SYSTEM FOR SPRING - UPGRADING FROM 0.5 TO 0.6
===============================================================================
The following should help most casual users of the project update their
applications:
- Locate and remove all property references to
DaoAuthenticationProvider.key and
DaoAuthenticationProvider.refreshTokenInterval.
- If you are using DaoAuthenticationProvider and either (i) you are using
container adapters or (ii) your code relies on the Authentication object
having its getPrincipal() return a String, you must set the new
DaoAuthenticationProvider property, forcePrincipalAsString, to true.
By default DaoAuthenticationProvider returns an Authentication object
containing the relevant User, which allows access to additional properties.
Where possible, we recommend you change your code to something like this,
so that you can leave forcePrincipalAsString to the false default:
String username = authentication.getPrincipal();
if (authentication.getPrincipal() instanceof User) {
username = ((User) authentication.getPrincipal()).getUsername();
}
$Id$