Add ACL Contacts XML sample

Closes gh-32
This commit is contained in:
Marcus Da Coregio
2021-09-03 14:19:19 -03:00
parent f6e3996e86
commit 4c00a8fb4e
58 changed files with 3810 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
/**
* AddDeleteContactController.
*
* @author Luke Taylor
* @since 3.0
*/
@Controller
public class AddDeleteContactController {
@Autowired
private ContactManager contactManager;
private final Validator validator = new WebContactValidator();
@RequestMapping(value = "/secure/add.htm", method = RequestMethod.GET)
public ModelAndView addContactDisplay() {
return new ModelAndView("add", "webContact", new WebContact());
}
@InitBinder
public void initBinder(WebDataBinder binder) {
System.out.println("A binder for object: " + binder.getObjectName());
}
@RequestMapping(value = "/secure/add.htm", method = RequestMethod.POST)
public String addContact(WebContact form, BindingResult result) {
this.validator.validate(form, result);
if (result.hasErrors()) {
return "add";
}
Contact contact = new Contact(form.getName(), form.getEmail());
this.contactManager.create(contact);
return "redirect:/secure/index.htm";
}
@RequestMapping(value = "/secure/del.htm", method = RequestMethod.GET)
public ModelAndView delContact(@RequestParam("contactId") int contactId) {
Contact contact = this.contactManager.getById((long) contactId);
this.contactManager.delete(contact);
return new ModelAndView("deleted", "contact", contact);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import org.springframework.security.acls.domain.BasePermission;
/**
* Model object for add permission use case.
*
* @author Ben Alex
*/
public class AddPermission {
private Contact contact;
private Integer permission = BasePermission.READ.getMask();
private String recipient;
public Contact getContact() {
return this.contact;
}
public Integer getPermission() {
return this.permission;
}
public String getRecipient() {
return this.recipient;
}
public void setContact(Contact contact) {
this.contact = contact;
}
public void setPermission(Integer permission) {
this.permission = permission;
}
public void setRecipient(String recipient) {
this.recipient = recipient;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
/**
* Validates {@link AddPermission}.
*
* @author Ben Alex
*/
public class AddPermissionValidator implements Validator {
@SuppressWarnings("unchecked")
public boolean supports(Class clazz) {
return clazz.equals(AddPermission.class);
}
public void validate(Object obj, Errors errors) {
AddPermission addPermission = (AddPermission) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "permission", "err.permission", "Permission is required. *");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "recipient", "err.recipient", "Recipient is required. *");
if (addPermission.getPermission() != null) {
int permission = addPermission.getPermission();
if ((permission != BasePermission.ADMINISTRATION.getMask()) && (permission != BasePermission.READ.getMask())
&& (permission != BasePermission.DELETE.getMask())) {
errors.rejectValue("permission", "err.permission.invalid", "The indicated permission is invalid. *");
}
}
if (addPermission.getRecipient() != null) {
if (addPermission.getRecipient().length() > 100) {
errors.rejectValue("recipient", "err.recipient.length",
"The recipient is too long (maximum 100 characters). *");
}
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.DefaultPermissionFactory;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PermissionFactory;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
/**
* Web controller to handle <tt>Permission</tt> administration functions - adding and
* deleting permissions for contacts.
*
* @author Luke Taylor
* @since 3.0
*/
@Controller
@SessionAttributes("addPermission")
public final class AdminPermissionController implements MessageSourceAware {
@Autowired
private AclService aclService;
@Autowired
private ContactManager contactManager;
private MessageSourceAccessor messages;
private final Validator addPermissionValidator = new AddPermissionValidator();
private final PermissionFactory permissionFactory = new DefaultPermissionFactory();
@RequestMapping(value = "/secure/adminPermission.htm", method = RequestMethod.GET)
public ModelAndView displayAdminPage(@RequestParam("contactId") int contactId) {
Contact contact = this.contactManager.getById((long) contactId);
Acl acl = this.aclService.readAclById(new ObjectIdentityImpl(contact));
Map<String, Object> model = new HashMap<>();
model.put("contact", contact);
model.put("acl", acl);
return new ModelAndView("adminPermission", "model", model);
}
@RequestMapping(value = "/secure/addPermission.htm", method = RequestMethod.GET)
public ModelAndView displayAddPermissionPageForContact(@RequestParam("contactId") long contactId) {
Contact contact = this.contactManager.getById(contactId);
AddPermission addPermission = new AddPermission();
addPermission.setContact(contact);
Map<String, Object> model = new HashMap<>();
model.put("addPermission", addPermission);
model.put("recipients", listRecipients());
model.put("permissions", listPermissions());
return new ModelAndView("addPermission", model);
}
@InitBinder("addPermission")
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("recipient", "permission");
}
@RequestMapping(value = "/secure/addPermission.htm", method = RequestMethod.POST)
public String addPermission(AddPermission addPermission, BindingResult result, ModelMap model) {
this.addPermissionValidator.validate(addPermission, result);
if (result.hasErrors()) {
model.put("recipients", listRecipients());
model.put("permissions", listPermissions());
return "addPermission";
}
PrincipalSid sid = new PrincipalSid(addPermission.getRecipient());
Permission permission = this.permissionFactory.buildFromMask(addPermission.getPermission());
try {
this.contactManager.addPermission(addPermission.getContact(), sid, permission);
}
catch (DataAccessException existingPermission) {
existingPermission.printStackTrace();
result.rejectValue("recipient", "err.recipientExistsForContact", "Addition failure.");
model.put("recipients", listRecipients());
model.put("permissions", listPermissions());
return "addPermission";
}
return "redirect:/secure/index.htm";
}
@RequestMapping("/secure/deletePermission.htm")
public ModelAndView deletePermission(@RequestParam("contactId") long contactId, @RequestParam("sid") String sid,
@RequestParam("permission") int mask) {
Contact contact = this.contactManager.getById(contactId);
Sid sidObject = new PrincipalSid(sid);
Permission permission = this.permissionFactory.buildFromMask(mask);
this.contactManager.deletePermission(contact, sidObject, permission);
Map<String, Object> model = new HashMap<>();
model.put("contact", contact);
model.put("sid", sidObject);
model.put("permission", permission);
return new ModelAndView("deletePermission", "model", model);
}
private Map<Integer, String> listPermissions() {
Map<Integer, String> map = new LinkedHashMap<>();
map.put(BasePermission.ADMINISTRATION.getMask(), this.messages.getMessage("select.administer", "Administer"));
map.put(BasePermission.READ.getMask(), this.messages.getMessage("select.read", "Read"));
map.put(BasePermission.DELETE.getMask(), this.messages.getMessage("select.delete", "Delete"));
return map;
}
private Map<String, String> listRecipients() {
Map<String, String> map = new LinkedHashMap<>();
map.put("", this.messages.getMessage("select.pleaseSelect", "-- please select --"));
for (String recipient : this.contactManager.getAllRecipients()) {
map.put(recipient, recipient);
}
return map;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StopWatch;
/**
* Demonstrates accessing the {@link ContactManager} via remoting protocols.
* <p>
* Based on Spring's JPetStore sample, written by Juergen Hoeller.
*
* @author Ben Alex
*/
public class ClientApplication {
private final ListableBeanFactory beanFactory;
public ClientApplication(ListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void invokeContactManager(Authentication authentication, int nrOfCalls) {
StopWatch stopWatch = new StopWatch(nrOfCalls + " ContactManager call(s)");
Map<String, ContactManager> contactServices = this.beanFactory.getBeansOfType(ContactManager.class, true, true);
SecurityContextHolder.getContext().setAuthentication(authentication);
for (Map.Entry<String, ContactManager> entry : contactServices.entrySet()) {
String beanName = entry.getKey();
ContactManager remoteContactManager = entry.getValue();
Object object = this.beanFactory.getBean("&" + beanName);
try {
System.out.println("Trying to find setUsername(String) method on: " + object.getClass().getName());
Method method = object.getClass().getMethod("setUsername", new Class[] { String.class });
System.out.println("Found; Trying to setUsername(String) to " + authentication.getPrincipal());
method.invoke(object, authentication.getPrincipal());
}
catch (NoSuchMethodException ignored) {
System.out.println("This client proxy factory does not have a setUsername(String) method");
}
catch (IllegalAccessException | InvocationTargetException ignored) {
ignored.printStackTrace();
}
try {
System.out.println("Trying to find setPassword(String) method on: " + object.getClass().getName());
Method method = object.getClass().getMethod("setPassword", new Class[] { String.class });
method.invoke(object, authentication.getCredentials());
System.out.println("Found; Trying to setPassword(String) to " + authentication.getCredentials());
}
catch (NoSuchMethodException ignored) {
System.out.println("This client proxy factory does not have a setPassword(String) method");
}
catch (IllegalAccessException | InvocationTargetException ignored) {
}
System.out.println("Calling ContactManager '" + beanName + "'");
stopWatch.start(beanName);
List<Contact> contacts = null;
for (int i = 0; i < nrOfCalls; i++) {
contacts = remoteContactManager.getAll();
}
stopWatch.stop();
if (contacts.size() != 0) {
for (Contact contact : contacts) {
System.out.println("Contact: " + contact);
}
}
else {
System.out.println("No contacts found which this user has permission to");
}
System.out.println();
System.out.println(stopWatch.prettyPrint());
}
SecurityContextHolder.clearContext();
}
public static void main(String[] args) {
String username = System.getProperty("username", "");
String password = System.getProperty("password", "");
String nrOfCallsString = System.getProperty("nrOfCalls", "");
if ("".equals(username) || "".equals(password)) {
System.out.println(
"You need to specify the user ID to use, the password to use, and optionally a number of calls "
+ "using the username, password, and nrOfCalls system properties respectively. eg for user rod, "
+ "use: -Dusername=rod -Dpassword=koala' for a single call per service and "
+ "use: -Dusername=rod -Dpassword=koala -DnrOfCalls=10 for ten calls per service.");
System.exit(-1);
}
else {
int nrOfCalls = 1;
if (!"".equals(nrOfCallsString)) {
nrOfCalls = Integer.parseInt(nrOfCallsString);
}
ListableBeanFactory beanFactory = new FileSystemXmlApplicationContext("clientContext.xml");
ClientApplication client = new ClientApplication(beanFactory);
client.invokeContactManager(new UsernamePasswordAuthenticationToken(username, password), nrOfCalls);
System.exit(0);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.io.Serializable;
/**
* Represents a contact.
*
* @author Ben Alex
*/
public class Contact implements Serializable {
private Long id;
private String email;
private String name;
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
public Contact() {
}
public String getEmail() {
return this.email;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setEmail(String email) {
this.email = email;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString() + ": ");
sb.append("Id: " + this.getId() + "; ");
sb.append("Name: " + this.getName() + "; ");
sb.append("Email: " + this.getEmail());
return sb.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.List;
/**
* Provides access to the application's persistence layer.
*
* @author Ben Alex
*/
public interface ContactDao {
void create(Contact contact);
void delete(Long contactId);
List<Contact> findAll();
List<String> findAllPrincipals();
List<String> findAllRoles();
Contact getById(Long id);
void update(Contact contact);
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* Base implementation of {@link ContactDao} that uses Spring's JdbcTemplate.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class ContactDaoSpring extends JdbcDaoSupport implements ContactDao {
public void create(final Contact contact) {
getJdbcTemplate().update("insert into contacts values (?, ?, ?)", (ps) -> {
ps.setLong(1, contact.getId());
ps.setString(2, contact.getName());
ps.setString(3, contact.getEmail());
});
}
public void delete(final Long contactId) {
getJdbcTemplate().update("delete from contacts where id = ?", (ps) -> ps.setLong(1, contactId));
}
public void update(final Contact contact) {
getJdbcTemplate().update("update contacts set contact_name = ?, address = ? where id = ?", (ps) -> {
ps.setString(1, contact.getName());
ps.setString(2, contact.getEmail());
ps.setLong(3, contact.getId());
});
}
public List<Contact> findAll() {
return getJdbcTemplate().query("select id, contact_name, email from contacts order by id",
(rs, rowNum) -> mapContact(rs));
}
public List<String> findAllPrincipals() {
return getJdbcTemplate().queryForList("select username from users order by username", String.class);
}
public List<String> findAllRoles() {
return getJdbcTemplate().queryForList("select distinct authority from authorities order by authority",
String.class);
}
public Contact getById(Long id) {
List<Contact> list = getJdbcTemplate().query(
"select id, contact_name, email from contacts where id = ? order by id", (rs, rowNum) -> mapContact(rs),
id);
if (list.size() == 0) {
return null;
}
else {
return list.get(0);
}
}
private Contact mapContact(ResultSet rs) throws SQLException {
Contact contact = new Contact();
contact.setId(rs.getLong("id"));
contact.setName(rs.getString("contact_name"));
contact.setEmail(rs.getString("email"));
return contact;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.List;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
/**
* Interface for the application's services layer.
*
* @author Ben Alex
*/
public interface ContactManager {
@PreAuthorize("hasPermission(#contact, admin)")
void addPermission(Contact contact, Sid recipient, Permission permission);
@PreAuthorize("hasPermission(#contact, admin)")
void deletePermission(Contact contact, Sid recipient, Permission permission);
@PreAuthorize("hasRole('ROLE_USER')")
void create(Contact contact);
@PreAuthorize("hasPermission(#contact, 'delete') or hasPermission(#contact, admin)")
void delete(Contact contact);
@PreAuthorize("hasRole('ROLE_USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
List<Contact> getAll();
@PreAuthorize("hasRole('ROLE_USER')")
List<String> getAllRecipients();
@PreAuthorize("hasPermission(#id, 'sample.contact.Contact', read) or "
+ "hasPermission(#id, 'sample.contact.Contact', admin)")
Contact getById(Long id);
Contact getRandomContact();
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.List;
import java.util.Random;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
/**
* Concrete implementation of {@link ContactManager}.
*
* @author Ben Alex
*/
@Transactional
public class ContactManagerBackend extends ApplicationObjectSupport implements ContactManager, InitializingBean {
private ContactDao contactDao;
private MutableAclService mutableAclService;
private int counter = 1000;
public void afterPropertiesSet() {
Assert.notNull(this.contactDao, "contactDao required");
Assert.notNull(this.mutableAclService, "mutableAclService required");
}
public void addPermission(Contact contact, Sid recipient, Permission permission) {
MutableAcl acl;
ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
try {
acl = (MutableAcl) this.mutableAclService.readAclById(oid);
}
catch (NotFoundException nfe) {
acl = this.mutableAclService.createAcl(oid);
}
acl.insertAce(acl.getEntries().size(), permission, recipient, true);
this.mutableAclService.updateAcl(acl);
logger.debug("Added permission " + permission + " for Sid " + recipient + " contact " + contact);
}
public void create(Contact contact) {
// Create the Contact itself
contact.setId((long) this.counter++);
this.contactDao.create(contact);
// Grant the current principal administrative permission to the contact
addPermission(contact, new PrincipalSid(getUsername()), BasePermission.ADMINISTRATION);
if (logger.isDebugEnabled()) {
logger.debug("Created contact " + contact + " and granted admin permission to recipient " + getUsername());
}
}
public void delete(Contact contact) {
this.contactDao.delete(contact.getId());
// Delete the ACL information as well
ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
this.mutableAclService.deleteAcl(oid, false);
if (logger.isDebugEnabled()) {
logger.debug("Deleted contact " + contact + " including ACL permissions");
}
}
public void deletePermission(Contact contact, Sid recipient, Permission permission) {
ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contact.getId());
MutableAcl acl = (MutableAcl) this.mutableAclService.readAclById(oid);
// Remove all permissions associated with this particular recipient (string
// equality to KISS)
List<AccessControlEntry> entries = acl.getEntries();
for (int i = 0; i < entries.size(); i++) {
if (entries.get(i).getSid().equals(recipient) && entries.get(i).getPermission().equals(permission)) {
acl.deleteAce(i);
}
}
this.mutableAclService.updateAcl(acl);
if (logger.isDebugEnabled()) {
logger.debug("Deleted contact " + contact + " ACL permissions for recipient " + recipient);
}
}
@Transactional(readOnly = true)
public List<Contact> getAll() {
logger.debug("Returning all contacts");
return this.contactDao.findAll();
}
@Transactional(readOnly = true)
public List<String> getAllRecipients() {
logger.debug("Returning all recipients");
return this.contactDao.findAllPrincipals();
}
@Transactional(readOnly = true)
public Contact getById(Long id) {
if (logger.isDebugEnabled()) {
logger.debug("Returning contact with id: " + id);
}
return this.contactDao.getById(id);
}
@Transactional(readOnly = true)
public Contact getRandomContact() {
logger.debug("Returning random contact");
Random rnd = new Random();
List<Contact> contacts = this.contactDao.findAll();
int getNumber = rnd.nextInt(contacts.size());
return contacts.get(getNumber);
}
protected String getUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() instanceof UserDetails) {
return ((UserDetails) auth.getPrincipal()).getUsername();
}
else {
return auth.getPrincipal().toString();
}
}
public void setContactDao(ContactDao contactDao) {
this.contactDao = contactDao;
}
public void setMutableAclService(MutableAclService mutableAclService) {
this.mutableAclService = mutableAclService;
}
public void update(Contact contact) {
this.contactDao.update(contact);
logger.debug("Updated contact " + contact);
}
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.Random;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
/**
* Populates the Contacts in-memory database with contact and ACL information.
*
* @author Ben Alex
*/
public class DataSourcePopulator implements InitializingBean {
JdbcTemplate template;
private MutableAclService mutableAclService;
final Random rnd = new Random();
TransactionTemplate tt;
final String[] firstNames = { "Bob", "Mary", "James", "Jane", "Kristy", "Kirsty", "Kate", "Jeni", "Angela",
"Melanie", "Kent", "William", "Geoff", "Jeff", "Adrian", "Amanda", "Lisa", "Elizabeth", "Prue", "Richard",
"Darin", "Phillip", "Michael", "Belinda", "Samantha", "Brian", "Greg", "Matthew" };
final String[] lastNames = { "Smith", "Williams", "Jackson", "Rictor", "Nelson", "Fitzgerald", "McAlpine",
"Sutherland", "Abbott", "Hall", "Edwards", "Gates", "Black", "Brown", "Gray", "Marwell", "Booch", "Johnson",
"McTaggart", "Parklin", "Findlay", "Robinson", "Giugni", "Lang", "Chi", "Carmichael" };
private int createEntities = 50;
public void afterPropertiesSet() {
Assert.notNull(this.mutableAclService, "mutableAclService required");
Assert.notNull(this.template, "dataSource required");
Assert.notNull(this.tt, "platformTransactionManager required");
// Set a user account that will initially own all the created data
Authentication authRequest = new UsernamePasswordAuthenticationToken("rod", "koala",
AuthorityUtils.createAuthorityList("ROLE_IGNORED"));
SecurityContextHolder.getContext().setAuthentication(authRequest);
try {
this.template.execute("DROP TABLE CONTACTS");
this.template.execute("DROP TABLE AUTHORITIES");
this.template.execute("DROP TABLE USERS");
this.template.execute("DROP TABLE ACL_ENTRY");
this.template.execute("DROP TABLE ACL_OBJECT_IDENTITY");
this.template.execute("DROP TABLE ACL_CLASS");
this.template.execute("DROP TABLE ACL_SID");
}
catch (Exception ex) {
System.out.println("Failed to drop tables: " + ex.getMessage());
}
this.template.execute("CREATE TABLE ACL_SID("
+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
+ "PRINCIPAL BOOLEAN NOT NULL," + "SID VARCHAR_IGNORECASE(100) NOT NULL,"
+ "CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
this.template.execute("CREATE TABLE ACL_CLASS("
+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
+ "CLASS VARCHAR_IGNORECASE(100) NOT NULL," + "CLASS_ID_TYPE VARCHAR_IGNORECASE(100),"
+ "CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
this.template.execute("CREATE TABLE ACL_OBJECT_IDENTITY("
+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
+ "OBJECT_ID_CLASS BIGINT NOT NULL," + "OBJECT_ID_IDENTITY VARCHAR_IGNORECASE(36) NOT NULL,"
+ "PARENT_OBJECT BIGINT," + "OWNER_SID BIGINT," + "ENTRIES_INHERITING BOOLEAN NOT NULL,"
+ "CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),"
+ "CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),"
+ "CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),"
+ "CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
this.template.execute("CREATE TABLE ACL_ENTRY("
+ "ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,"
+ "ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,"
+ "MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,"
+ "AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),"
+ "CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),"
+ "CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");
this.template.execute(
"CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(500) NOT NULL,ENABLED BOOLEAN NOT NULL);");
this.template.execute(
"CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
this.template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
this.template.execute(
"CREATE TABLE CONTACTS(ID BIGINT NOT NULL PRIMARY KEY, CONTACT_NAME VARCHAR_IGNORECASE(50) NOT NULL, EMAIL VARCHAR_IGNORECASE(50) NOT NULL)");
/*
* Passwords encoded using MD5, NOT in Base64 format, with null as salt Encoded
* password for rod is "koala" Encoded password for dianne is "emu" Encoded
* password for scott is "wombat" Encoded password for peter is "opal" (but user
* is disabled) Encoded password for bill is "wombat" Encoded password for bob is
* "wombat" Encoded password for jane is "wombat"
*/
this.template.execute(
"INSERT INTO USERS VALUES('rod','$2a$10$75pBjapg4Nl8Pzd.3JRnUe7PDJmk9qBGwNEJDAlA3V.dEJxcDKn5O',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('dianne','$2a$04$bCMEyxrdF/7sgfUiUJ6Ose2vh9DAMaVBldS1Bw2fhi1jgutZrr9zm',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('scott','$2a$06$eChwvzAu3TSexnC3ynw4LOSw1qiEbtNItNeYv5uI40w1i3paoSfLu',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('peter','$2a$04$8.H8bCMROLF4CIgd7IpeQ.tcBXLP5w8iplO0n.kCIkISwrIgX28Ii',FALSE);");
this.template.execute(
"INSERT INTO USERS VALUES('bill','$2a$04$8.H8bCMROLF4CIgd7IpeQ.3khQlPVNWbp8kzSQqidQHGFurim7P8O',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('bob','$2a$06$zMgxlMf01SfYNcdx7n4NpeFlAGU8apCETz/i2C7VlYWu6IcNyn4Ay',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('jane','$2a$05$ZrdS7yMhCZ1J.AAidXZhCOxdjD8LO/dhlv4FJzkXA6xh9gdEbBT/u',TRUE);");
this.template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_SUPERVISOR');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('dianne','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('scott','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('peter','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");
this.template.execute("INSERT INTO contacts VALUES (1, 'John Smith', 'john@somewhere.com');");
this.template.execute("INSERT INTO contacts VALUES (2, 'Michael Citizen', 'michael@xyz.com');");
this.template.execute("INSERT INTO contacts VALUES (3, 'Joe Bloggs', 'joe@demo.com');");
this.template.execute("INSERT INTO contacts VALUES (4, 'Karen Sutherland', 'karen@sutherland.com');");
this.template.execute("INSERT INTO contacts VALUES (5, 'Mitchell Howard', 'mitchell@abcdef.com');");
this.template.execute("INSERT INTO contacts VALUES (6, 'Rose Costas', 'rose@xyz.com');");
this.template.execute("INSERT INTO contacts VALUES (7, 'Amanda Smith', 'amanda@abcdef.com');");
this.template.execute("INSERT INTO contacts VALUES (8, 'Cindy Smith', 'cindy@smith.com');");
this.template.execute("INSERT INTO contacts VALUES (9, 'Jonathan Citizen', 'jonathan@xyz.com');");
for (int i = 10; i < this.createEntities; i++) {
String[] person = selectPerson();
this.template.execute("INSERT INTO contacts VALUES (" + i + ", '" + person[2] + "', '"
+ person[0].toLowerCase() + "@" + person[1].toLowerCase() + ".com');");
}
// Create acl_object_identity rows (and also acl_class rows as needed
for (int i = 1; i < this.createEntities; i++) {
final ObjectIdentity objectIdentity = new ObjectIdentityImpl(Contact.class, (long) i);
this.tt.execute((arg0) -> {
this.mutableAclService.createAcl(objectIdentity);
return null;
});
}
// Now grant some permissions
grantPermissions(1, "rod", BasePermission.ADMINISTRATION);
grantPermissions(2, "rod", BasePermission.READ);
grantPermissions(3, "rod", BasePermission.READ);
grantPermissions(3, "rod", BasePermission.WRITE);
grantPermissions(3, "rod", BasePermission.DELETE);
grantPermissions(4, "rod", BasePermission.ADMINISTRATION);
grantPermissions(4, "dianne", BasePermission.ADMINISTRATION);
grantPermissions(4, "scott", BasePermission.READ);
grantPermissions(5, "dianne", BasePermission.ADMINISTRATION);
grantPermissions(5, "dianne", BasePermission.READ);
grantPermissions(6, "dianne", BasePermission.READ);
grantPermissions(6, "dianne", BasePermission.WRITE);
grantPermissions(6, "dianne", BasePermission.DELETE);
grantPermissions(6, "scott", BasePermission.READ);
grantPermissions(7, "scott", BasePermission.ADMINISTRATION);
grantPermissions(8, "dianne", BasePermission.ADMINISTRATION);
grantPermissions(8, "dianne", BasePermission.READ);
grantPermissions(8, "scott", BasePermission.READ);
grantPermissions(9, "scott", BasePermission.ADMINISTRATION);
grantPermissions(9, "scott", BasePermission.READ);
grantPermissions(9, "scott", BasePermission.WRITE);
grantPermissions(9, "scott", BasePermission.DELETE);
// Now expressly change the owner of the first ten contacts
// We have to do this last, because "rod" owns all of them (doing it sooner would
// prevent ACL updates)
// Note that ownership has no impact on permissions - they're separate (ownership
// only allows ACl editing)
changeOwner(5, "dianne");
changeOwner(6, "dianne");
changeOwner(7, "scott");
changeOwner(8, "dianne");
changeOwner(9, "scott");
String[] users = { "bill", "bob", "jane" }; // don't want to mess around with
// consistent sample data
Permission[] permissions = { BasePermission.ADMINISTRATION, BasePermission.READ, BasePermission.DELETE };
for (int i = 10; i < this.createEntities; i++) {
String user = users[this.rnd.nextInt(users.length)];
Permission permission = permissions[this.rnd.nextInt(permissions.length)];
grantPermissions(i, user, permission);
String user2 = users[this.rnd.nextInt(users.length)];
Permission permission2 = permissions[this.rnd.nextInt(permissions.length)];
grantPermissions(i, user2, permission2);
}
SecurityContextHolder.clearContext();
}
private void changeOwner(int contactNumber, String newOwnerUsername) {
AclImpl acl = (AclImpl) this.mutableAclService
.readAclById(new ObjectIdentityImpl(Contact.class, (long) contactNumber));
acl.setOwner(new PrincipalSid(newOwnerUsername));
updateAclInTransaction(acl);
}
public int getCreateEntities() {
return this.createEntities;
}
private void grantPermissions(int contactNumber, String recipientUsername, Permission permission) {
AclImpl acl = (AclImpl) this.mutableAclService
.readAclById(new ObjectIdentityImpl(Contact.class, (long) contactNumber));
acl.insertAce(acl.getEntries().size(), permission, new PrincipalSid(recipientUsername), true);
updateAclInTransaction(acl);
}
private String[] selectPerson() {
String firstName = this.firstNames[this.rnd.nextInt(this.firstNames.length)];
String lastName = this.lastNames[this.rnd.nextInt(this.lastNames.length)];
return new String[] { firstName, lastName, firstName + " " + lastName };
}
public void setCreateEntities(int createEntities) {
this.createEntities = createEntities;
}
public void setDataSource(DataSource dataSource) {
this.template = new JdbcTemplate(dataSource);
}
public void setMutableAclService(MutableAclService mutableAclService) {
this.mutableAclService = mutableAclService;
}
public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
this.tt = new TransactionTemplate(platformTransactionManager);
}
private void updateAclInTransaction(final MutableAcl acl) {
this.tt.execute((arg0) -> {
this.mutableAclService.updateAcl(acl);
return null;
});
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* Controller which handles simple, single request use cases such as index pages and
* contact deletion.
*
* @author Luke Taylor
* @since 3.0
*/
@Controller
public class IndexController {
private static final Permission[] HAS_DELETE = new Permission[] { BasePermission.DELETE,
BasePermission.ADMINISTRATION };
private static final Permission[] HAS_ADMIN = new Permission[] { BasePermission.ADMINISTRATION };
@Autowired
private ContactManager contactManager;
@Autowired
private PermissionEvaluator permissionEvaluator;
/**
* The public index page, used for unauthenticated users.
* @return the public index page
*/
@RequestMapping(value = "/hello.htm", method = RequestMethod.GET)
public ModelAndView displayPublicIndex() {
Contact rnd = this.contactManager.getRandomContact();
return new ModelAndView("hello", "contact", rnd);
}
/**
* The index page for an authenticated user.
* <p>
* This controller displays a list of all the contacts for which the current user has
* read or admin permissions. It makes a call to {@link ContactManager#getAll()} which
* automatically filters the returned list using Spring Security's ACL mechanism (see
* the expression annotations on this interface for the details).
* <p>
* In addition to rendering the list of contacts, the view will also include a "Del"
* or "Admin" link beside the contact, depending on whether the user has the
* corresponding permissions (admin permission is assumed to imply delete here). This
* information is stored in the model using the injected {@link PermissionEvaluator}
* instance. The implementation should be an instance of
* {@link AclPermissionEvaluator} or one which is compatible with Spring Security's
* ACL module.
* @return index page
*/
@RequestMapping(value = "/secure/index.htm", method = RequestMethod.GET)
public ModelAndView displayUserContacts() {
List<Contact> myContactsList = this.contactManager.getAll();
Map<Contact, Boolean> hasDelete = new HashMap<>(myContactsList.size());
Map<Contact, Boolean> hasAdmin = new HashMap<>(myContactsList.size());
Authentication user = SecurityContextHolder.getContext().getAuthentication();
for (Contact contact : myContactsList) {
hasDelete.put(contact, this.permissionEvaluator.hasPermission(user, contact, HAS_DELETE));
hasAdmin.put(contact, this.permissionEvaluator.hasPermission(user, contact, HAS_ADMIN));
}
Map<String, Object> model = new HashMap<>();
model.put("contacts", myContactsList);
model.put("hasDeletePermission", hasDelete);
model.put("hasAdminPermission", hasAdmin);
return new ModelAndView("index", "model", model);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
/**
* An object that represents user-editable sections of a {@link Contact}.
*
* @author Ben Alex
*/
public class WebContact {
private String email;
private String name;
public String getEmail() {
return this.email;
}
public String getName() {
return this.name;
}
public void setEmail(String email) {
this.email = email;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.contact;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* Validates {@link WebContact}.
*
* @author Ben Alex
*/
public class WebContactValidator implements Validator {
@SuppressWarnings("unchecked")
public boolean supports(Class clazz) {
return clazz.equals(WebContact.class);
}
public void validate(Object obj, Errors errors) {
WebContact wc = (WebContact) obj;
if ((wc.getName() == null) || (wc.getName().length() < 3) || (wc.getName().length() > 50)) {
errors.rejectValue("name", "err.name", "Name 3-50 characters is required. *");
}
if ((wc.getEmail() == null) || (wc.getEmail().length() < 3) || (wc.getEmail().length() > 50)) {
errors.rejectValue("email", "err.email", "Email 3-50 characters is required. *");
}
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
- Application context containing the ACL beans.
-
-->
<!-- ========= ACL SERVICE DEFINITIONS ========= -->
<bean id="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName" value="aclCache"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ACL_ADMIN"/>
</bean>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>
</beans>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context containing business beans.
-
- Used by all artifacts.
-
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:test"/>
<!-- <value>jdbc:hsqldb:hsql://localhost/acl</value> -->
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="dataSourcePopulator" class="sample.contact.DataSourcePopulator">
<property name="dataSource" ref="dataSource"/>
<property name="mutableAclService" ref="aclService"/>
<property name="platformTransactionManager" ref="transactionManager"/>
</bean>
<bean id="contactManager" class="sample.contact.ContactManagerBackend">
<property name="contactDao">
<bean class="sample.contact.ContactDaoSpring">
<property name="dataSource" ref="dataSource"/>
</bean>
</property>
<property name="mutableAclService" ref="aclService"/>
</bean>
</beans>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context containing authentication, channel
- security and web URI beans.
-
- Only used by "filter" artifact.
-
-->
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler"/>
</global-method-security>
<http realm="Contacts Realm" use-expressions="false">
<intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/index.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/hello.htm" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/switchuser.jsp" access="ROLE_SUPERVISOR"/>
<intercept-url pattern="/login/impersonate" access="ROLE_SUPERVISOR"/>
<intercept-url pattern="/**" access="ROLE_USER"/>
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1"/>
<http-basic/>
<logout logout-success-url="/index.jsp"/>
<remember-me />
<headers/>
<csrf/>
<custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
</http>
<b:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="encoder"/>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
<!-- Automatically receives AuthenticationEvent messages -->
<b:bean id="loggerListener" class="org.springframework.security.authentication.event.LoggerListener"/>
<!-- Filter used to switch the user context. Note: the switch and exit url must be secured
based on the role granted the ability to 'switch' to another user -->
<!-- In this example 'rod' has ROLE_SUPERVISOR that can switch to regular ROLE_USER(s) -->
<b:bean id="switchUserProcessingFilter" class="org.springframework.security.web.authentication.switchuser.SwitchUserFilter" autowire="byType">
<b:property name="targetUrl" value="/secure/index.htm"/>
</b:bean>
<b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<b:property name="permissionEvaluator" ref="permissionEvaluator"/>
<b:property name="permissionCacheOptimizer">
<b:bean class="org.springframework.security.acls.AclPermissionCacheOptimizer">
<b:constructor-arg ref="aclService"/>
</b:bean>
</b:property>
</b:bean>
<b:bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
<b:constructor-arg ref="aclService"/>
</b:bean>
</b:beans>

View File

@@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.security" level="TRACE"/>-->
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,6 @@
err.name=Name 3-50 characters is required.
err.email=Email 3-50 characters is required.
err.permission=Permission is required.
err.recipient=Recipient is required.
err.permission.invalid=The indicated permission is invalid.
err.recipient.length=The recipient is too long (maximum 100 characters).

View File

@@ -0,0 +1,26 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- ========================== WEB DEFINITIONS ======================= -->
<context:component-scan base-package="sample.contact"/>
<context:annotation-config />
<mvc:annotation-driven/>
<mvc:view-controller path="/frames.htm" view-name="/frames"/>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

View File

@@ -0,0 +1,42 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Add New Contact</title></head>
<body>
<h1>Add Contact</h1>
<form method="post">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td alignment="right" width="20%">Name:</td>
<spring:bind path="webContact.name">
<td width="20%">
<input type="text" name="name" value="<c:out value="${status.value}"/>">
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
<tr>
<td alignment="right" width="20%">Email:</td>
<spring:bind path="webContact.email">
<td width="20%">
<input type="text" name="email" value="<c:out value="${status.value}"/>">
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
</table>
<br>
<spring:hasBindErrors name="webContact">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<br><br>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
<input name="execute" type="submit" alignment="center" value="Execute">
</form>
<a href="<c:url value="../hello.htm"/>">Home</a>
</body>
</html>

View File

@@ -0,0 +1,56 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Add Permission</title></head>
<body>
<h1>Add Permission</h1>
<form method="post">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td alignment="right" width="20%">Contact:</td>
<td width="60%"><c:out value="${addPermission.contact}"/></td>
</tr>
<tr>
<td alignment="right" width="20%">Recipient:</td>
<spring:bind path="addPermission.recipient">
<td width="20%">
<select name="<c:out value="${status.expression}"/>">
<c:forEach var="thisRecipient" items="${recipients}">
<option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>">
<c:out value="${thisRecipient.value}"/></option>
</c:forEach>
</select>
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
<tr>
<td alignment="right" width="20%">Permission:</td>
<spring:bind path="addPermission.permission">
<td width="20%">
<select name="<c:out value="${status.expression}"/>">
<c:forEach var="thisPermission" items="${permissions}">
<option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>">
<c:out value="${thisPermission.value}"/></option>
</c:forEach>
</select>
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</spring:bind>
</tr>
</table>
<br>
<spring:hasBindErrors name="webContact">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<br><br>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
<input name="execute" type="submit" alignment="center" value="Execute">
</form>
<p>
<A HREF="<c:url value="adminPermission.htm"><c:param name="contactId" value="${addPermission.contact.id}"/></c:url>">Admin Permission</A> <a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Administer Permissions</title></head>
<body>
<h1>Administer Permissions</h1>
<p>
<code>
<c:out value="${model.contact}"/>
</code>
</p>
<table cellpadding="3" border="0">
<c:forEach var="acl" items="${model.acl.entries}">
<tr>
<td>
<code>
<c:out value="${acl}"/>
</code>
</td>
<td>
<a href="<c:url value="deletePermission.htm"><c:param name="contactId" value="${model.contact.id}"/><c:param name="sid" value="${acl.sid.principal}"/><c:param name="permission" value="${acl.permission.mask}"/></c:url>">Del</a>
</td>
</tr>
</c:forEach>
</table>
<p>
<a href="<c:url value="addPermission.htm"><c:param name="contactId" value="${model.contact.id}"/></c:url>">Add Permission</a> <a href="<c:url value="index.htm"/>">Manage</a>
</p>
</body>
</html>

View File

@@ -0,0 +1,20 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Permission Deleted</title></head>
<body>
<h1>Permission Deleted</h1>
<P>
<code>
<c:out value="${model.contact}"/>
</code>
<P>
<code>
<c:out value="${model.sid}"/>
</code>
<code>
<c:out value="${model.permission}"/>
</code>
<p><a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Deletion completed</title></head>
<body>
<h1>Deleted</h1>
<P>
<code>
<c:out value="${contact}"/>
</code>
<p><a href="<c:url value="index.htm"/>">Manage</a>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<html>
<head>
<title>Frames</title>
</head>
<body>
<p>This contains frames, but the frames will not be loaded due to the <a href="https://tools.ietf.org/html/draft-ietf-websec-x-frame-options">X-Frame-Options</a>
being specified as denied. This protects against <a href="https://en.wikipedia.org/wiki/Clickjacking">clickjacking attacks</a></p>
<iframe src="./hello.htm" width="500" height="500"></iframe>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Contacts Security Demo</title></head>
<body>
<h1>Contacts Security Demo</h1>
<P>Contacts demonstrates the following central Spring Security capabilities:
<ul>
<li><b>Role-based security</b>. Each principal is a member of certain roles,
which are used to restrict access to certain secure objects.</li>
<li><b>Domain object instance security</b>. The <code>Contact</code>, the
main domain object in the application, has an access control list (ACL)
that indicates who is allowed read, administer and delete the object.</li>
<li><b>Method invocation security</b>. The <code>ContactManager</code> service
layer bean has a number of secured (protected) and public (unprotected)
methods.</li>
<li><b>Web request security</b>. The <code>/secure</code> URI path is protected
by Spring Security from principals not holding the
<code>ROLE_USER</code> granted authority.</li>
<li><b>Security unaware application objects</b>. None of the objects
are aware of the security being implemented by Spring Security. *</li>
<li><b>Security taglib usage</b>. All of the JSPs use Spring Security's
taglib to evaluate security information. *</li>
<li><b>Fully declarative security</b>. Every capability is configured in
the application context using standard Spring Security classes. *</li>
<li><b>Database-sourced security data</b>. All of the user, role and ACL
information is obtained from an in-memory JDBC-compliant database.</li>
<li><b>Integrated form-based and BASIC authentication</b>. Any BASIC
authentication header is detected and used for authentication. Normal
interactive form-based authentication is used by default.</li>
<li><b>Remember-me services</b>. Spring Security's pluggable remember-me
strategy is demonstrated, with a corresponding checkbox on the login form.</li>
</ul>
* As the application provides an "ACL Administration" use case, those
classes are necessarily aware of security. But no business use cases are.
<p>Please excuse the lack of look 'n' feel polish in this application.
It is about security, after all! :-)
<p>To demonstrate a public method on <code>ContactManager</code>,
here's a random <code>Contact</code>:
<p>
<code>
<c:out value="${contact}"/>
</code>
<p>Get started by clicking "Manage"...
<p><A HREF="<c:url value="secure/index.htm"/>">Manage</a>
<a href="<c:url value="secure/debug.jsp"/>">Debug</a>
<a href="<c:url value="./frames.htm"/>">Frames</a>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page pageEncoding="UTF-8" %>

View File

@@ -0,0 +1,37 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Your Contacts</title></head>
<body>
<h1><security:authentication property="principal.username"/>'s Contacts</h1>
<P>
<table cellpadding=3 border=0>
<tr><td><b>id</b></td><td><b>Name</b></td><td><b>Email</b></td></tr>
<c:forEach var="contact" items="${model.contacts}" >
<tr>
<td>
<c:out value="${contact.id}"/>
</td>
<td>
<c:out value="${contact.name}"/>
</td>
<td>
<c:out value="${contact.email}"/>
</td>
<c:if test="${model.hasDeletePermission[contact]}">
<td><a href="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</a></td>
</c:if>
<c:if test="${model.hasAdminPermission[contact]}">
<td><a href="<c:url value="adminPermission.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Admin Permission</a></td>
</c:if>
</tr>
</c:forEach>
</table>
<p><a href="<c:url value="add.htm"/>">Add</a> </p>
<form action="<c:url value="/logout"/>" method="post">
<input type="submit" value="Logoff"/> (also clears any remember-me cookie)
<security:csrfInput/>
</form>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "https://www.springframework.org/dtd/spring-beans.dtd">
<!--
- Contacts web application
-->
<beans>
<!-- RMI exporter for the ContactManager -->
<!-- This could just as easily have been in
applicationContext-common-business.xml, because it doesn't rely on
DispatcherServlet or indeed any other HTTP services. It's in this
application context simply for logical placement with other
remoting exporters. -->
<!-- COMMENTED OUT BY DEFAULT TO AVOID CONFLICTS WITH APPLICATION SERVERS
<bean id="contactManager-rmi" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service"><ref bean="contactManager"/></property>
<property name="serviceInterface">
<value>sample.contact.ContactManager</value>
</property>
<property name="serviceName"><value>contactManager</value></property>
<property name="registryPort"><value>1099</value></property>
</bean>
-->
<!-- HTTP invoker exporter for the ContactManager -->
<!-- Spring's HTTP invoker uses Java serialization via HTTP -->
<bean name="/ContactManager-httpinvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
<!-- Hessian exporter for the ContactManager -->
<!-- Hessian is a slim binary HTTP remoting protocol -->
<!--
<bean name="/ContactManager-hessian" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
-->
<!-- Burlap exporter for the ContactManager -->
<!-- Burlap is a slim XML-based HTTP remoting protocol -->
<!--
<bean name="/ContactManager-burlap" class="org.springframework.remoting.caucho.BurlapServiceExporter">
<property name="service" ref="contactManager"/>
<property name="serviceInterface" value="sample.contact.ContactManager"/>
</bean>
-->
</beans>

View File

@@ -0,0 +1,311 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "https://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.1.1</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>Spring</short-name>
<uri>http://www.springframework.org/tags</uri>
<description>Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller</description>
<tag>
<name>htmlEscape</name>
<tag-class>org.springframework.web.servlet.tags.HtmlEscapeTag</tag-class>
<body-content>JSP</body-content>
<description>
Sets default HTML escape value for the current page.
Overrides a "defaultHtmlEscape" context-param in web.xml, if any.
</description>
<attribute>
<name>defaultHtmlEscape</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>escapeBody</name>
<tag-class>org.springframework.web.servlet.tags.EscapeBodyTag</tag-class>
<body-content>JSP</body-content>
<description>
Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>message</name>
<tag-class>org.springframework.web.servlet.tags.MessageTag</tag-class>
<body-content>JSP</body-content>
<description>
Retrieves the message with the given code, or text if code isn't resolvable.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>code</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>arguments</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>text</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>theme</name>
<tag-class>org.springframework.web.servlet.tags.ThemeTag</tag-class>
<body-content>JSP</body-content>
<description>
Retrieves the theme message with the given code, or text if code isn't resolvable.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>code</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>arguments</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>text</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>hasBindErrors</name>
<tag-class>org.springframework.web.servlet.tags.BindErrorsTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides Errors instance in case of bind errors.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<variable>
<name-given>errors</name-given>
<variable-class>org.springframework.validation.Errors</variable-class>
</variable>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>nestedPath</name>
<tag-class>org.springframework.web.servlet.tags.NestedPathTag</tag-class>
<body-content>JSP</body-content>
<description>
Sets a nested path to be used by the bind tag's path.
</description>
<variable>
<name-given>nestedPath</name-given>
<variable-class>java.lang.String</variable-class>
</variable>
<attribute>
<name>path</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>bind</name>
<tag-class>org.springframework.web.servlet.tags.BindTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides BindStatus object for the given bind path.
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<variable>
<name-given>status</name-given>
<variable-class>org.springframework.web.servlet.support.BindStatus</variable-class>
</variable>
<attribute>
<name>path</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>ignoreNestedPath</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>transform</name>
<tag-class>org.springframework.web.servlet.tags.TransformTag</tag-class>
<body-content>JSP</body-content>
<description>
Provides transformation of variables to Strings, using an appropriate
custom PropertyEditor from BindTag (can only be used inside BindTag).
The HTML escaping flag participates in a page-wide or application-wide setting
(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
</description>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Contacts web application
-
-->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Contacts Sample Application</display-name>
<!--
- Location of the XML file that defines the root application context
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-common-business.xml
classpath:applicationContext-common-authorization.xml
classpath:applicationContext-security.xml
</param-value>
</context-param>
<!-- Nothing below here needs to be modified -->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>contacts.root</param-value>
</context-param>
<filter>
<filter-name>localizationFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>localizationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
- Loads the root application context of this web app at startup.
- The application context is then available via
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
- Provides core MVC application controller. See contacts-servlet.xml.
-->
<servlet>
<servlet-name>contacts</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--
- Provides web services endpoint. See remoting-servlet.xml.
-->
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>contacts</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
</web-app>

View File

@@ -0,0 +1,22 @@
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import="org.springframework.security.core.Authentication" %>
<html>
<head>
<title>Access Denied</title>
</head>
<body>
<h1>Sorry, access is denied</h1>
<p>
<%= request.getAttribute("SPRING_SECURITY_403_EXCEPTION")%>
</p>
<p>
<% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
Authentication object as a String: <%= auth.toString() %><br /><br />
<% } %>
</p>
</body>
</html>

View File

@@ -0,0 +1,5 @@
<html>
<title>Access denied!</title>
<h1>Access Denied</h1>
<p>We're sorry, but you are not authorized to perform the requested operation.</p>
</html>

View File

@@ -0,0 +1,39 @@
<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %>
<%@ page import="org.springframework.security.core.Authentication" %>
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page pageEncoding="UTF-8" %>
<html>
<head>
<title>Exit User</title>
</head>
<body>
<h1>Exit User</h1>
<c:if test="${not empty param.login_error}">
<font color="red">
Your 'Exit User' attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>
</font>
</c:if>
<form action="<c:url value='logout/impersonate'/>" method="POST">
<table>
<tr><td>Current User:</td><td>
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
<%= auth.getPrincipal().toString() %>
<% } %>
</td></tr>
<tr><td colspan='2'><input name="exit" type="submit" value="Exit"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>

View File

@@ -0,0 +1,4 @@
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%-- Redirected because we can't set the welcome page to a virtual URL. --%>
<c:redirect url="/hello.htm"/>

View File

@@ -0,0 +1,47 @@
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page pageEncoding="UTF-8" %>
<html>
<head>
<title>Login</title>
</head>
<body onload="document.f.username.focus();">
<h1>Login</h1>
<p>Valid users:
<p>
<p>username <b>rod</b>, password <b>koala</b>
<p>username <b>dianne</b>, password <b>emu</b>
<p>username <b>scott</b>, password <b>wombat</b>
<p>username <b>peter</b>, password <b>opal</b> (user disabled)
<p>username <b>bill</b>, password <b>wombat</b>
<p>username <b>bob</b>, password <b>wombat</b>
<p>username <b>jane</b>, password <b>wombat</b>
<p>
<p>Locale is: <%= request.getLocale() %></p>
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<font color="red">
Your login attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
</font>
</c:if>
<form name="f" action="<c:url value='login'/>" method="POST">
<table>
<tr><td>User:</td><td><input type='text' name='username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'></td></tr>
<tr><td><input type="checkbox" name="remember-me"></td><td>Don't ask for my password for two weeks</td></tr>
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import="org.springframework.security.core.Authentication" %>
<%@ page import="org.springframework.security.core.GrantedAuthority" %>
<html>
<head>
<title>Security Debug Information</title>
</head>
<body>
<h3>Security Debug Information</h3>
<%
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
<p>
Authentication object is of type: <em><%= auth.getClass().getName() %></em>
</p>
<p>
Authentication object as a String: <br/><br/><%= auth.toString() %>
</p>
Authentication object holds the following granted authorities:<br /><br />
<%
for (GrantedAuthority authority : auth.getAuthorities()) { %>
<%= authority %> (<em>getAuthority()</em>: <%= authority.getAuthority() %>)<br />
<% }
%>
<p><b>Success! Your web filters appear to be properly configured!</b></p>
<%
} else {
%>
Authentication object is null.<br />
This is an error and your Spring Security application will not operate properly until corrected.<br /><br />
<% }
%>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %>
<%@ page import="org.springframework.security.core.AuthenticationException" %>
<%@ page pageEncoding="UTF-8" %>
<html>
<head>
<title>Switch User</title>
</head>
<body>
<h1>Switch to User</h1>
<h3>Valid users:</h3>
<p>username <b>rod</b>, password <b>koala</b></p>
<p>username <b>dianne</b>, password <b>emu</b></p>
<p>username <b>scott</b>, password <b>wombat</b></p>
<p>username <b>bill</b>, password <b>wombat</b></p>
<p>username <b>bob</b>, password <b>wombat</b></p>
<p>username <b>jane</b>, password <b>wombat</b></p>
<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<p>
<font color="red">
Your 'su' attempt was not successful, try again.<br/>
</font>
</p>
</c:if>
<form action="<c:url value='login/impersonate'/>" method="POST">
<table>
<tr><td>User:</td><td><input type='text' name='username'></td></tr>
<tr><td colspan='2'><input name="switch" type="submit" value="Switch to User"></td></tr>
</table>
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
</form>
</body>
</html>