您的位置:首页 > 编程语言 > Java开发

Make your Spring Security @Secured annotations more DRY

2016-04-15 15:51 531 查看
RecentlyauserontheGrailsUsermailinglistwantedtoknowhow
toreducerepetitionwhendefining@Securedannotations.TherulesforspecifyingattributesinJavaannotationsareprettyrestrictive,
soIcouldn’tseeadirectwaytodowhathewasasking.

UsingGroovydoesn’treallyhelpheresinceforthemostpartannotationsinaGroovyclassareprettymuchthesameasinJava(exceptforthesyntaxforarrayvalues).OfcourseGroovynowsupportsclosuresinannotations,butthiswouldrequireacodechange
intheplugin.ButthenIthoughtaboutsomeworkJeffBrowndidrecentlyinthecacheplugin.

Spring’scacheabstractionAPIincludesthreeannotations;
@Cacheable
,
@CacheEvict
,
and
@CachePut
.
Wewerethinkingaheadaboutsupportingmoreconfigurationoptionsthantheseannotationsallow,butsinceyoucan’tsubclassannotationswedecidedtouseanASTtransformationtofindourversionsoftheseannotations(currentlywiththesameattributes
astheSpringannotations)andconvertthemtovalidSpringannotations.SoIlookedatJeff’s
codeanditendedupbeingthebasisforafixforthisproblem.

It’snotpossibletousecodetoexternalizetheauthoritylistsbecauseyoucan’tcontrolthecompilationorder.SoIendedupwithasolutionthatisn’tperfectbutworks–Ilookforapropertiesfileintheprojectroot(
roles.properties
).
Theformatissimple–thekeysarenamesforeachauthoritylistandthevaluesarethelistsofauthoritynames,comma-delimited.Here’sanexample:

1
admins=ROLE_ADMIN,
ROLE_SUPERADMIN
2
switchUser=ROLE_SWITCH_USER
3
editors=ROLE_EDITOR,
ROLE_ADMIN
Thesekeysarethevaluesyouuseforthenew
@Authorities
annotation:

01
package
grails.plugins.springsecurity.annotation;
02
03
import
java.lang.annotation.Documented;
04
import
java.lang.annotation.ElementType;
05
import
java.lang.annotation.Inherited;
06
import
java.lang.annotation.Retention;
07
import
java.lang.annotation.RetentionPolicy;
08
import
java.lang.annotation.Target;
09
10
import
org.codehaus.groovy.transform.GroovyASTTransformationClass;
11
12
/**
13
*
@authorBurtBeckwith
14
*/
15
@Target
({ElementType.FIELD,
ElementType.METHOD,ElementType.TYPE})
16
@Retention
(RetentionPolicy.RUNTIME)
17
@Inherited
18
@Documented
19
@GroovyASTTransformationClass
(
20
"grails.plugins.springsecurity.annotation.AuthoritiesTransformation"
)
21
public
@interface
Authorities
{
22
/**
23
*
Thepropertyfilekey;thepropertyvaluewillbea
24
*
comma-delimitedlistofrolenames.
25
*
@returnthekey
26
*/
27
String
value();
28
}
Forexamplehere’sacontrollerusingthenewannotation:

1
@Authorities
(
'admins'
)
2
class
SecureController
{
3
4
@Authorities
(
'editors'
)
5
def
someAction(){
6
...
7
}
8
}
Thisistheequivalentofthiscontroller(andifyoudecompiletheonewith
@Authorities
you’ll
seebothannotations):

1
@Secured
([
'ROLE_ADMIN'
,
'ROLE_SUPERADMIN'
])
2
class
SecureController
{
3
4
@Secured
([
'ROLE_EDITOR'
,
'ROLE_ADMIN'
])
5
def
someAction(){
6
...
7
}
8
}
TheASTtransformationclasslooksfor
@Authorities
annotations,
loadsthepropertiesfile,andaddsanew
@Secured
annotation
(the
@Authorities
annotation
isn’tremoved)usingtherolenamesspecifiedinthepropertiesfile:

001
package
grails.plugins.springsecurity.annotation;
002
003
import
grails.plugins.springsecurity.Secured;
004
005
import
java.io.File;
006
import
java.io.FileReader;
007
import
java.io.IOException;
008
import
java.util.ArrayList;
009
import
java.util.List;
010
import
java.util.Properties;
011
012
import
org.codehaus.groovy.ast.ASTNode;
013
import
org.codehaus.groovy.ast.AnnotatedNode;
014
import
org.codehaus.groovy.ast.AnnotationNode;
015
import
org.codehaus.groovy.ast.ClassNode;
016
import
org.codehaus.groovy.ast.expr.ConstantExpression;
017
import
org.codehaus.groovy.ast.expr.Expression;
018
import
org.codehaus.groovy.ast.expr.ListExpression;
019
import
org.codehaus.groovy.control.CompilePhase;
020
import
org.codehaus.groovy.control.SourceUnit;
021
import
org.codehaus.groovy.transform.ASTTransformation;
022
import
org.codehaus.groovy.transform.GroovyASTTransformation;
023
import
org.springframework.util.StringUtils;
024
025
/**
026
*
@authorBurtBeckwith
027
*/
028
@GroovyASTTransformation
(phase=CompilePhase.CANONICALIZATION)
029
public
class
AuthoritiesTransformation
implements
ASTTransformation
{
030
031
protected
static
final
ClassNode
SECURED=
032
new
ClassNode(Secured.
class
);
033
034
public
void
visit(ASTNode[]
astNodes,SourceUnitsourceUnit){
035
try
{
036
ASTNode
firstNode=astNodes[
0
];
037
ASTNode
secondNode=astNodes[
1
];
038
if
(!(firstNode
instanceof
AnnotationNode)
||
039
!(secondNode
instanceof
AnnotatedNode))
{
040
throw
new
RuntimeException(
"Internal
error:wrongtypes:"
+
041
firstNode.getClass().getName()
+
042
"
/"
+
secondNode.getClass().getName());
043
}
044
045
AnnotationNode
rolesAnnotationNode=(AnnotationNode)firstNode;
046
AnnotatedNode
annotatedNode=(AnnotatedNode)secondNode;
047
048
AnnotationNode
secured=createAnnotation(rolesAnnotationNode);
049
if
(secured
!=
null
)
{
050
annotatedNode.addAnnotation(secured);
051
}
052
}
053
catch
(Exception
e){
054
//
TODO
055
e.printStackTrace();
056
}
057
}
058
059
protected
AnnotationNode
createAnnotation(AnnotationNoderolesNode)
060
throws
IOException
{
061
Expression
value=rolesNode.getMembers().get(
"value"
);
062
if
(!(value
instanceof
ConstantExpression))
{
063
//
TODO
064
System.out.println(
065
"annotation
@Authoritiesvalueisn'taConstantExpression:"
+
066
value);
067
return
null
;
068
}
069
070
String
fieldName=value.getText();
071
String[]
authorityNames=getAuthorityNames(fieldName);
072
if
(authorityNames
==
null
)
{
073
return
null
;
074
}
075
076
return
buildAnnotationNode(authorityNames);
077
}
078
079
protected
AnnotationNode
buildAnnotationNode(String[]names){
080
AnnotationNode
securedAnnotationNode=
new
AnnotationNode(SECURED);
081
List<Expression>
nameExpressions=
new
ArrayList<Expression>();
082
for
(String
authorityName:names){
083
nameExpressions.add(
new
ConstantExpression(authorityName));
084
}
085
securedAnnotationNode.addMember(
"value"
,
086
new
ListExpression(nameExpressions));
087
return
securedAnnotationNode;
088
}
089
090
protected
String[]
getAuthorityNames(StringfieldName)
091
throws
IOException
{
092
093
Properties
properties=
new
Properties();
094
File
propertyFile=
new
File(
"roles.properties"
);
095
if
(!propertyFile.exists())
{
096
//
TODO
097
System.out.println(
"Property
fileroles.propertiesnotfound"
);
098
return
null
;
099
}
100
101
properties.load(
new
FileReader(propertyFile));
102
103
Object
value=properties.getProperty(fieldName);
104
if
(value
==
null
)
{
105
//
TODO
106
System.out.println(
"No
valueforproperty'"
+
fieldName+
"'"
);
107
return
null
;
108
}
109
110
List<String>
names=
new
ArrayList<String>();
111
String[]
nameArray=StringUtils.commaDelimitedListToStringArray(
112
value.toString())
113
for
(String
auth:nameArray){
114
auth
=auth.trim();
115
if
(auth.length()
>
0
)
{
116
names.add(auth);
117
}
118
}
119
120
return
names.toArray(
new
String[names.size()]);
121
}
122
}
I’llprobablyincludethisinthepluginatsomepoint–IcreatedaJIRA
issue

asareminder–butfornowyoucanjustcopythesetwoclassesinto
yourapplication’ssrc/javafolderandcreatea
roles.properties
file
intheprojectroot.Anytimeyouwanttoaddorremoveanentryoraddorremovearolenamefromanentry,updatethepropertiesfile,run
grails
clean
and
grails
compile
tobesurethatthelatestvaluesareused.

Reference:Make
yourSpringSecurity@SecuredannotationsmoreDRYfromourJCG
partnerBurtBeckwithattheAnArmyofSolipsistsblog.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: