spring security for certain entity variables - spring-security

Is it possible to secure update of certain entity properties using spring security.? for example if I have a user entity , I want ROLE_USER to be able to modify/update all the properties of user except active column which would be updatable by ROLE_ADMIN.

I haven't found any solution strictly provided by Spring Security Yet. However I have achieved what I wanted as follows by using custom annotation #SecureUpdate() on entity variables:-
Following is my paging and sorting repository:-
#Transactional("jpaTXManager")
public interface ScreenRepo extends PagingAndSortingRepository<Screen, Integer>{
#Override
#PreAuthorize("#patchSecurityService.canUpdate(#screen)")
Screen save(#Param("screen")Screen screen);
}
PatchSecurityService.java
#Service("patchSecurityService")
public class PatchSecurityService {
public boolean canUpdate(Object obj){
List<GrantedAuthority> authorities =
(List<GrantedAuthority>) SecurityContextHolder
.getContext()
.getAuthentication()
.getAuthorities();
if (obj instanceof OEntity){
OEntity oEntity = (OEntity) obj;
return oEntity.canUpdate(authorities);
}else{
return true;
}
}
}
OEntity.java
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean canUpdate(List<GrantedAuthority> authorities){
for (Field field : this.getClass().getDeclaredFields()){
SecureUpdate secureUpdate = field.getAnnotation(SecureUpdate.class);
if (secureUpdate != null){
try{
field.setAccessible(true);
Object persistedField = field.get(this);
Object originalField = field.get(originalObj);
String[] allowedRoles = secureUpdate.value();
if (!persistedField.equals(originalField)){
boolean canUpdate = false;
for (String role : allowedRoles){
for (GrantedAuthority authority : authorities){
if (authority.getAuthority().equalsIgnoreCase(role)){
return true;
}
}
}
return false;
}
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
return true;
}
}
#SecureUpdate
#Documented
#Target(FIELD)
#Retention(RUNTIME)
public #interface SecureUpdate {
String[] value();
}
and finally entity class (Screen.class)
#Entity
#Table(name="screen",schema="public")
#JsonIgnoreProperties(ignoreUnknown=true)
public class Screen extends OEntity<Screen>{
Integer screenId;
String screenName;
#SecureUpdate({"ROLE_CLIENT"})
String address;
ScreenType screenType;
#SecureUpdate({"ROLE_ADMIN"})
ScreenSize screenSize;
BigDecimal latitude;
BigDecimal longitude;
Boolean active;
AppUser appUser;
.......Constructor, Getters and Setters...
}

Related

Can relate to only one element

I am using Neo4j OGM 2.0.4 driver with Java. I have trouble with adding more than one relationship to element.
I do something like this:
Site site1 = new Site();
site1.setTitle("Site 1");
site1.setHtmlCode("Content of site 1");
Site site2 = new Site();
Site subsite1 = new Site();
subsite1.setTitle("Subsite 1");
subsite1.setHtmlCode("Content of subsite 1");
subsite1.setParent(site1);
Site subsite2 = new Site();
subsite2.setTitle("Subsite 2");
subsite2.setHtmlCode("Content of subsite 2");
subsite2.setParent(site1);
session.deleteAll(Site.class);
session.save(site1);
session.save(subsite1);
session.save(subsite2);
When I want to show all Site nodes (on localhost:7474) then "Subsite 1" has no relationship.
#NodeEntity
public class Site extends Entity
{
private String _title;
private String _htmlCode;
#Relationship(type = "SITE_CREATED_BY")
Author _author;
#Relationship(type = "IS_CHILD")
Set<Site> _parentSite;
#Relationship(type = "IS_CHILD", direction = Relationship.INCOMING)
Set<Site> _childSites;
public Site()
{
_parentSite = new HashSet();
_childSites = new HashSet();
}
public void setTitle(String title)
{
_title = title;
}
public String getTitle()
{
return _title;
}
public void setHtmlCode(String htmlCode)
{
_htmlCode = htmlCode;
}
public String getHtmlCode()
{
return _htmlCode;
}
public void setAuthor(Author author)
{
_author = author;
}
public void setParent(Site site)
{
_parentSite.add(site);
}
}
Entity:
public abstract class Entity
{
private Long id;
private final ZonedDateTime _dateOfCreation;
Entity()
{
_dateOfCreation = ZonedDateTime.now();
}
public Long getId()
{
return id;
}
public ZonedDateTime getDateOfCreation()
{
return _dateOfCreation;
}
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || id == null || getClass() != o.getClass()) return false;
Entity entity = (Entity) o;
return id.equals(entity.id);
}
#Override
public int hashCode()
{
return (id == null) ? -1 : id.hashCode();
}
}
What am I doing wrong?
In this case where you have two relationships in different directions between the same type of node, first, make sure that you annotate both the fields as well as setter/accessor methods with the #Relationship,specifying the direction.
Site in your object model has references to both the parent and children, but when you create sites, they do not seem consistent with the model. Subsite1 and Subsite2 both set their parents to site1 but site has no record of its children (should be both subsites). Should work if your object and graph models are consistent.

Using the "version" annotation in a document with spring boot elasticearch

i'm using spring-boot-starter-data-elasticsearch (1.4.0.M3).
I'm unable to get the version (_version in elasticsearch query result) of a document using the annoation "version".
Any idea why the annotation isn't working ?
f.e.:
#GwtCompatible
#Document(indexName = "myIndexName")
public class Catalog implements Serializable {
private List<GroupProduct> groups;
#Id
private String uuid;
#Version
private Long version;
#Field(type = FieldType.Nested)
private List<Product> products;
private String label;
#NotEmpty
private String organizationUuid;
private List<String> organizationUnitUuids;
private Date updateDate;
private List<VAT> vats;
public Catalog() {
}
public List<GroupProduct> getGroups() {
return groups;
}
public List<Product> getProducts() {
return products;
}
public Date getUpdateDate() {
return updateDate;
}
public void setGroups(List<GroupProduct> groups) {
this.groups = groups;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public List<VAT> getVats() {
return vats;
}
public void setVats(List<VAT> vats) {
this.vats = vats;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getOrganizationUuid() {
return organizationUuid;
}
public void setOrganizationUuid(String organizationUuid) {
this.organizationUuid = organizationUuid;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List<String> getOrganizationUnitUuids() {
return organizationUnitUuids;
}
public void setOrganizationUnitUuids(List<String> organizationUnitUuids) {
this.organizationUnitUuids = organizationUnitUuids;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
Spring Data Elasticsearch (as of version 2.0.2) seems to have only partial support for the #Version annotation. If you annotate a document with a version field, it will be used when indexing a document. It will tell Elasticsearch that the document being saved is that specified version. If the new version is less than or equal to the version of the current document, Elasticsearch will throw a VersionConflictEngineException.
Unfortunately, Spring does not appear to populate this version field when a document is retrieved. As far as I can tell, this makes the version annotation useless. Perhaps the project will add this support in the near future. In the meantime, I have found a workaround by extending the default ResultMapper that Spring uses:
public class ExtendedResultMapper extends DefaultResultMapper {
protected MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
public ExtendedResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(mappingContext);
this.mappingContext = mappingContext;
}
#Override
public <T> T mapResult(GetResponse response, Class<T> clazz) {
T result = super.mapResult(response, clazz);
if (result != null) {
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
#Override
public <T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
LinkedList<T> results = super.mapResults(responses, clazz);
if (results != null) {
for (int i = 0; i < results.size(); i++) {
setPersistentEntityVersion(results.get(i), responses.getResponses()[i].getResponse().getVersion(), clazz);
}
}
return results;
}
private <T> void setPersistentEntityVersion(T result, Long version, Class<T> clazz) {
if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) {
PersistentProperty<ElasticsearchPersistentProperty> versionProperty = mappingContext.getPersistentEntity(clazz).getVersionProperty();
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
Method setter = versionProperty.getSetter();
if (setter != null) {
try {
setter.invoke(result, version);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
}
You can tell Spring to use this version instead of the default mapper as follows:
#Autowired
private Client client;
#Bean
public ElasticsearchTemplate elasticsearchTemplate() {
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
ExtendedResultMapper mapper = new ExtendedResultMapper(converter.getMappingContext());
return new ElasticsearchTemplate(client, converter, mapper);
}
Note that the version is only populated for Get or Multi-Get requests. Search results do not include version information.
You could also use this same approach to extract other information from the GetResponse objects.
Using this code, if you get a document and then try to save it back, it will fail unless you increment the version.

ResultSet mapping to object dynamically in dropwizard

I was trying to map ResultSet data to an object and returning it. Here is how i'm mapping data to an object. Now i'm having only 7 columns in resultset so this is working fine but what if i'm having 20 or 30 columns. How can i map dynamically those columns.
public class ProductsWrapperMapper implements ResultSetMapper<ProductsWrapper> {
public ProductsWrapper map(int i, ResultSet resultSet,
StatementContext statementContext) throws SQLException {
ProductsWrapper product = new ProductsWrapper();
if ((isColumnPresent(resultSet,"a_productid"))) {
product.setId(resultSet.getInt("a_productid"));
}
if ((isColumnPresent(resultSet,"a_productname"))) {
product.setProductName(resultSet.getString("a_productname"));
}
if ((isColumnPresent(resultSet,"a_productlink"))) {
product.setLink(resultSet.getString("a_productlink"));
}
if ((isColumnPresent(resultSet,"a_productimagelink"))) {
product.setImageLink(resultSet.getString("a_productimagelink"));
}
if ((isColumnPresent(resultSet,"a_websiteid"))) {
product.setWebsiteId(resultSet.getInt("a_websiteid"));
}
if ((isColumnPresent(resultSet,"a_productidentification"))) {
product.setProductIdentification(resultSet
.getString("a_productidentification"));
}
if ((isColumnPresent(resultSet,"a_adddate"))) {
product.setAddDate(resultSet.getString("a_adddate"));
}
return product;
}
public boolean isColumnPresent(ResultSet resultSet,String column) {
try {
#SuppressWarnings("unused")
int index = resultSet.findColumn(column);
return true;
} catch (SQLException e) {
// TODO Auto-generated catch block
return false;
}
}
}
Below one is my class which i was returning the object from mapper class above.
#JsonInclude(Include.NON_NULL)
public class ProductsWrapper {
private int id;
private String productName;
private String link;
private String imageLink;
private int websiteId;
private String productIdentification;
private String addDate;
int getWebsiteId() {
return websiteId;
}
public void setWebsiteId(int websiteId) {
this.websiteId = websiteId;
}
public String getProductIdentification() {
return productIdentification;
}
public void setProductIdentification(String productIdentification) {
this.productIdentification = productIdentification;
}
public String getAddDate() {
return addDate;
}
public void setAddDate(String addDate) {
this.addDate = addDate;
}`enter code here`
public ProductsWrapper(int id) {
this.setId(id);
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getImageLink() {
return imageLink;
}
public void setImageLink(String imageLink) {
this.imageLink = imageLink;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
You can also try Jdbi-folder. It automatically takes care of dynamic bynding and also it provides one to many mapping relationship.
You can add Rosetta as a mapper for your JDBI result sets (it also works for bindings). Have a look at the advanced features to map column names with underscores to snake snake case java names.
Beware that there is no warning message if Rosetta is unable to map a value: any missed property in the target bean will just be empty. I found that my database returned column names in capital letters, therefore the LowerCaseWithUnderscoresStrategy in the example didn't work for me. I created a UpperCaseWithUnderscoresStrategy.
To skip writing getters and setters in ProductsWrapper have a look at Lombok's #Data annotation.

Simple relationships not mapped with queryForObject

I have one question. I am using neo4j-ogm snapshot 1.5 .
I have the following classes:
#NodeEntity
public abstract class Entity {
#GraphId
protected Long id;
#Expose
protected String code = null;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || id == null || getClass() != o.getClass())
return false;
Entity entity = (Entity) o;
if (!id.equals(entity.id))
return false;
return true;
}
#Override
public int hashCode() {
return (id == null) ? -1 : id.hashCode();
}
public Long getId(){
return id;
}
public void setId(Long neo4jId){
this.id = neo4jId;
}
public String getCode(){
return code;
}
public void setCode(String code){
this.code = code;
}
}
public class PropertyGroup extends Entity{
#Expose
private String name;
public PropertyGroup(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class User extends Entity {
private Long registration_date;
private Long last_login_date;
private Boolean is_admin = false;
private String push_dev;
private String push_id;
private Boolean push_enabled = false;
#Expose
private String avatar;
#Expose
private String avatarUrl;
#Expose
private String name;
#Expose
private volatile String password;
#Expose
private int likes = 0;
#Expose
private int questionCount = 0;
#Expose
private int followersCount = 0;
#Expose
private boolean isFollowing = false;
// public Set<UserPropertyRelation> properties;
// #Relationship(type = ModelRelType.ANSWERED)
// public Set<UserAnsweredRelation> userAnswers;
//
// #Relationship(type = ModelRelType.LIKES)
// private Set<LikesQuestionRelation> questionsLiked;
#Expose
#Relationship(type = ModelRelType.HAS_PROPERTY)
private Set<PropertyGroup> properties;
// private Profile userProfile;
// private List<Fact> facts;
// #Expose
// #Relationship(type = ModelRelType.OWNS)
// private List<Question> questions;
public User(){
// this.properties = new LinkedHashSet<UserPropertyRelation>();
// this.userAnswers = new LinkedHashSet<UserAnsweredRelation>();
// this.userProperties = new HashSet<PropertyGroup>();
// this.setFacts(new ArrayList<Fact>());
this.properties = new HashSet<PropertyGroup>();
}
public User(long regDate, long lastLoginDate, boolean isAdmin,
String pushDev, String pushId, boolean pushEnabled){
this();
this.registration_date = regDate;
this.last_login_date = lastLoginDate;
this.is_admin = isAdmin;
this.push_dev = pushDev;
this.push_id = pushId;
this.push_enabled = pushEnabled;
}
// public void addUserAnsweredRelation(UserAnsweredRelation answer){
// answer.setStartNode(this);
// this.userAnswers.add(answer);
// }
//
// public Set<UserAnsweredRelation> getUserAnsweredRelations() {
// return this.userAnswers;
// }
// public void setUserAnsweredRelations(Set<UserAnsweredRelation> userAnswers){
// for(UserAnsweredRelation a : userAnswers){
// a.setStartNode(this);
// }
//
// this.userAnswers = userAnswers;
// }
//
// public void addUserPropertyRelation(UserPropertyRelation rel){
// rel.setUser(this);
// properties.add(rel);
// }
//
// public void setUserPropertyRelations(Set<UserPropertyRelation> properties){
// for(UserPropertyRelation r: properties){
// r.setUser(this);
// }
//
// this.properties = properties;
// }
// public Set<UserPropertyRelation> getUserPropertyRelations(){
// return this.properties;
// }
public long getRegistrationDate() {
return registration_date;
}
public void setRegistrationDate(long registrationDate) {
this.registration_date = registrationDate;
}
public long getLastLoginDate() {
return last_login_date;
}
public void setLastLoginDate(long lastLoginDate) {
this.last_login_date = lastLoginDate;
}
public boolean isAdmin() {
return is_admin;
}
public void setAdmin(boolean isAdmin) {
this.is_admin = isAdmin;
}
public String getPushDev() {
return push_dev;
}
public void setPushDev(String pushDev) {
this.push_dev = pushDev;
}
public String getPushId() {
return push_id;
}
public void setPushId(String pushId) {
this.push_id = pushId;
}
public boolean isPushEnabled() {
return push_enabled;
}
public void setPushEnabled(boolean pushEnabled) {
this.push_enabled = pushEnabled;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<PropertyGroup> getProperties() {
return properties;
}
public void setProperties(Set<PropertyGroup> properties) {
this.properties = properties;
}
// public Profile getUserProfile() {
// return userProfile;
// }
//
// public void setUserProfile(Profile userProfile) {
// this.userProfile = userProfile;
// }
// public Set<LikesQuestionRelation> getQuestionsLiked() {
// return questionsLiked;
// }
//
// public void setQuestionsLiked(Set<LikesQuestionRelation> likes) {
// this.questionsLiked = likes;
// }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// public List<Fact> getFacts() {
// return facts;
// }
//
// public void setFacts(List<Fact> facts) {
// this.facts = facts;
// }
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public int getLikes() {
return likes;
}
public void setLikes(int likes) {
this.likes = likes;
}
public int getQuestionCount() {
return questionCount;
}
public void setQuestionCount(int questionCount) {
this.questionCount = questionCount;
}
public int getFollowersCount() {
return followersCount;
}
public void setFollowersCount(int followersCount) {
this.followersCount = followersCount;
}
public boolean isFollowing() {
return isFollowing;
}
public void setFollowing(boolean isFollowing) {
this.isFollowing = isFollowing;
}
// public List<Question> getQuestions() {
// return questions;
// }
//
// public void setQuestions(List<Question> questions) {
// this.questions = questions;
// }
When I am trying to do the following:
SessionFactory sessionFactory = new SessionFactory(modelsPackageName);
Session session = sessionFactory.openSession(url);
String cypher = "MATCH (u:User {code: {CODE}})-[h:HAS_PROPERTY]->(pg:PropertyGroup) " +
"RETURN u, h, pg";
Map<String, Object> params = new HashMap<String, Object>();
params.put("CODE", "fc48b19ba6f8427a03d6e5990bcef99a28f55592b80fe38731cf805ed188cabf");
// System.out.println(Util.mergeParamsWithCypher(cypher, params));
User u = session.queryForObject(User.class, cypher, params);
The user Object (u) never contains any properties (PropertyGroup entity is not mapped).
What am I doing wrong?
Any help would be appreciated.
Regards,
Alex
If you're using queryForObject return just one column- the object, in your case u.
Neo4j OGM 1.x does not support mapping of custom query results to domain entities, so you will have to return the entity ID, and then do an additional load-by-id specifying a custom depth.
OGM 2.0 (currently 2.0.0-M01) does support mapping custom query results to entities. Your query will remain the same (i.e. return u,h,pg) but instead you'll use the query() method that returns a Result. From the result, you'll be able to get your User entity by column-name u and it'll be hydrated with the PropertyGroups it is related to.
Update:
The dependencies for OGM 2.0.0-M01 are
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-api</artifactId>
<version>2.0.0-M01</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-core</artifactId>
<version>2.0.0-M01</version>
</dependency>
Be sure to read the about the configuration changes since you're upgrading from OGM 1.x http://neo4j.com/docs/ogm/java/2.0.0-M01/#reference_setup
A summary of new features: http://neo4j.com/blog/neo4j-ogm-2-0-milestone-1/

Grails Spring Security: how can I allow the password null?

I'm using Grails Spring Security Plugin. I have a requirement that for a kind of user, the password is not needed. So I override the constraints of password in default User domain:
class SecUser {
...
static constraints = {
username blank: false, unique: true
password blank: false, nullable: true
}
...
}
But this causes a lot of problems:
beforeInsert in User domain breaks because springSecurityService.encodePassword(password) couldn't accept a null value; then I override the beforeInsert:
def beforeInsert() {
if (someCondition) {
super.beforeInsert()
passwordChangeDate = new Date()
}
}
UserDetails class breaks because the constructor couldn't accept password as a null value, so I override the UserDetails:
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User.AuthorityComparator
import org.springframework.util.Assert
class ILUserDetails extends GrailsUser {
final String name
ILUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String name) {
//Override User
if (((username == null) || "".equals(username))) { // Allow null for password
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.id=id
this.name = name
}
private static SortedSet<GrantedAuthority> sortAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<GrantedAuthority>(
new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority,
"GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
}
Then I override the beforeInsert and the UserDetails, bug a strange bug happens:
java.lang.NoSuchMethodError: grails.plugin.springsecurity.userdetails.GrailsUser: method <init>()V not found
com.app.security.ILUserDetails.<init>(ILUserDetails.groovy)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
java.lang.reflect.Constructor.newInstance(Constructor.java:526)
org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:986)
org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102)
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57)
Now, I'm not sure letting the password be nullable is a good idea or not. Could someone give me some advice?
The previous ILUserDetails has errors because the private attribute is not accessible for subclass. So I rewrote the whole User class according to GrailsUser.java and User.java.
//Rewrite according to GrailsUser and User
//https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/User.java
//https://github.com/grails-plugins/grails-spring-security-core/blob/master/src/java/grails/plugin/springsecurity/userdetails/GrailsUser.java
public class ILUser implements UserDetails, CredentialsContainer {
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
private static final long serialVersionUID = 1;
private final Object id;
private final String name;
// ~ Constructors
// ===================================================================================================
/**
* Construct the <code>User</code> with the details required by
* {#link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
*
* #param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
*/
public ILUser(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities, Object id, String name) {
if (((username == null) || "".equals(username))) { // Here is the key line
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.id = id;
this.name = name;
}
// ~ Methods
// ========================================================================================================
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isEnabled() {
return enabled;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void eraseCredentials() {
password = null;
}
private static SortedSet<GrantedAuthority> sortAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<GrantedAuthority>(
new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority,
"GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>,
Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set.
// If the authority is null, it is a custom authority and should precede
// others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
/**
* Returns {#code true} if the supplied object is a {#code User} instance with the
* same {#code username} value.
* <p>
* In other words, the objects are equal if they have the same username, representing
* the same principal.
*/
#Override
public boolean equals(Object rhs) {
if (rhs instanceof ILUser) {
return username.equals(((ILUser) rhs).username);
}
return false;
}
/**
* Returns the hashcode of the {#code username}.
*/
#Override
public int hashCode() {
return username.hashCode();
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired)
.append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (!authorities.isEmpty()) {
sb.append("Granted Authorities: ");
boolean first = true;
for (GrantedAuthority auth : authorities) {
if (!first) {
sb.append(",");
}
first = false;
sb.append(auth);
}
}
else {
sb.append("Not granted any authorities");
}
return sb.toString();
}
/**
* Get the id.
* #return the id
*/
public Object getId() {
return id;
}
}

Resources